过滤器
关于过滤器这块我举个例子,更多的内容请小伙伴自己查阅文档
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
举一个用的比较多的过滤器:
StripPrefix
顾名思义,除去前缀的过滤器,将匹配的请求的前缀去除,将去除后的请求转发给下游服务
spring: cloud: gateway: # 路由配置 routes: # 路由id, 保证唯一性 - id: my-goods # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名 uri: lb://my-goods # 断言 predicates: # 匹配goods开头的请求 - Path=/api/goods/** filters: # 1表示去除一个前缀 - StripPrefix=1
组合来看,意思是当客户端发起请求:http://localhost:5555/api/goods/get-goods, 匹配该路由,然后将第一个前缀
api
去除,然后转发给商品服务,转发的路径为:/goods/get-goods
测试
自定义断言工厂
上面提到过:所有的断言工厂都是继承于AbstractRoutePredicateFactory
, 并且命名规则为:XxxRoutePredicateFactory
, 比如Path的类名为:PathRoutePredicateFactory
我们现在就来尝试实现一个自定义的请求头断言工厂吧
编写代码
package com.my.micro.service.gateway.filter; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Predicate; /** * @author Zijian Liao * @since 1.0.0 */ @Component public class MyHeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<MyHeaderRoutePredicateFactory.Config> { /** * Header key. */ public static final String HEADER_KEY = "header"; /** * Regexp key. */ public static final String REGEXP_KEY = "regexp"; public MyHeaderRoutePredicateFactory() { super(MyHeaderRoutePredicateFactory.Config.class); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList(HEADER_KEY, REGEXP_KEY); } @Override public Predicate<ServerWebExchange> apply(MyHeaderRoutePredicateFactory.Config config) { return new GatewayPredicate() { @Override public boolean test(ServerWebExchange exchange) { // 获取请求头 List<String> values = exchange.getRequest().getHeaders() .getOrDefault(config.header, Collections.emptyList()); if (values.isEmpty()) { return false; } // 判断请求头中的值是否与配置匹配 return values.stream() .anyMatch(value -> value.matches(config.regexp)); } @Override public String toString() { return String.format("Header: %s=%s ", config.header, config.regexp); } }; } public static class Config { private String header; private String regexp; public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } public String getRegexp() { return regexp; } public void setRegexp(String regexp) { this.regexp = regexp; } } }
编写配置
spring: cloud: gateway: # 路由配置 routes: # 路由id, 保证唯一性 - id: my-goods # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名 uri: lb://my-goods # 断言 predicates: # 匹配goods开头的请求 - Path=/api/goods/** # 匹配header为name=aljian的请求 - MyHeader=name,ajian filters: # 1表示去除一个前缀 - StripPrefix=1
测试
直接在浏览器中访问
改用postman访问
自定义过滤器
自定义过滤器的方式与自定义断言工厂的方式大致相同,所以过滤器继承于AbstractGatewayFilterFactory
或者AbstractNameValueGatewayFilterFactory
, 命名规则为XxxGatewayFilterFactory
比如内置的添加请求头过滤器
public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override public GatewayFilter apply(NameValueConfig config) { return new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取到需要添加的header value String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); // 将header添加到request中 ServerHttpRequest request = exchange.getRequest().mutate() .header(config.getName(), value).build(); // 重新构建出一个exchange return chain.filter(exchange.mutate().request(request).build()); } @Override public String toString() { return filterToStringCreator(AddRequestHeaderGatewayFilterFactory.this) .append(config.getName(), config.getValue()).toString(); } }; } }
全局过滤器
以上内容都是针对于每一个router,Spring Cloud Gateway提供了一个针对所有router的全局过滤器
实现方式如下
package com.my.micro.service.gateway.filter; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @author Zijian Liao * @since 1.0.0 */ @Slf4j @Component public class MyGlobalFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String path = exchange.getRequest().getURI().getPath(); log.info("进入全局过滤器,请求路径为:{}", path); // 编写任何你想要实现的逻辑,比如权限校验 return chain.filter(exchange); } }
测试
自定义异常处理器
小伙伴应该发现了,在遇到错误时,Spring Cloud Gateway返回给客户端的异常并不优雅,所以我们需要自定义异常处理
编写自定义异常处理器
package com.my.micro.service.gateway.exception; import com.my.micro.service.gateway.result.BaseResult; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.web.ErrorProperties; import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler; import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.lang.NonNull; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.RequestPredicates; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; /** * @author Zijian Liao */ @Slf4j public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler { /** * Create a new {@code DefaultErrorWebExceptionHandler} instance. * * @param errorAttributes the error attributes * @param resourceProperties the resources configuration properties * @param errorProperties the error configuration properties * @param applicationContext the current application context */ public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) { super(errorAttributes, resourceProperties, errorProperties, applicationContext); } @Override protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse); } @NonNull @Override protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) { Throwable throwable = getError(request); return ServerResponse.status(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(this.handleError(throwable))); } private BaseResult<Void> handleError(Throwable throwable){ return BaseResult.failure(throwable.getMessage()); } }
BaseResult
package com.my.micro.service.gateway.result; import lombok.Data; /** * @author Zijian Liao * @since 1.0.0 */ @Data public class BaseResult<T> { private Integer code; private String message; public BaseResult(Integer code, String message){ this.code = code; this.message = message; } public static <T> BaseResult<T> failure(String message){ return new BaseResult<>(-1, message); } }
编写配置类
package com.my.micro.service.gateway.exception; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.web.ResourceProperties; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler; import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.reactive.result.view.ViewResolver; import java.util.stream.Collectors; /** * @author Zijian Liao * @since 1.0.0 */ @Configuration public class ExceptionConfiguration { @Primary @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider<ViewResolver> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) { DefaultErrorWebExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes, resourceProperties, serverProperties.getError(), applicationContext); exceptionHandler.setViewResolvers(viewResolversProvider.orderedStream().collect(Collectors.toList())); exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters()); exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders()); return exceptionHandler; } }
测试
小结
本编介绍了关于微服务架构中——客户端如何访问的解决方案:Spring Cloud Gateway
其中介绍了Gateway的三个核心概念:Route,Predicate,Filter。并演示了如何配置及使用他们,还讲解了如何自定义Predicate和Filter。
最后介绍了Spring Cloud Gateway的全局过滤器,以及如何实现自定义异常处理。
以上
希望大家有所收获,我们下期再见~