自定义断言
SpringCloud Gateway支持自定义断言功能,我们可以在具体业务中,基于SpringCloud Gateway自定义特定的断言功能。
自定义断言概述
SpringCloud Gateway虽然提供了多种内置的断言功能,但是在某些场景下无法满足业务的需要,此时,我们就可以基于SpringCloud Gateway自定义断言功能,以此来满足我们的业务场景。
实现自定义断言
这里,我们基于SpringCloud Gateway实现断言功能,实现后的效果是在服务网关的application.yml文件中的spring.cloud.gateway.routes
节点下的- id: user-gateway
下面进行如下配置。
spring: cloud: gateway: routes: - id: user-gateway uri: http://localhost:8060 order: 1 predicates: - Path=/server-user/** - Name=binghe filters: - StripPrefix=1
通过服务网关访问用户微服务时,只有在访问的链接后面添加?name=binghe
参数时才能正确访问用户微服务。
(1)在网关服务shop-gateway中新建io.binghe.shop.predicate
包,在包下新建NameRoutePredicateConfig类,主要定义一个Spring类型的name成员变量,用来接收配置文件中的参数,源码如下所示。
/** * @author binghe * @version 1.0.0 * @description 接收配置文件中的参数 */ @Data public class NameRoutePredicateConfig implements Serializable { private static final long serialVersionUID = -3289515863427972825L; private String name; }
(2)实现自定义断言时,需要新建类继承org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory
类,在io.binghe.shop.predicate
包下新建NameRoutePredicateFactory类,继承org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory
类,并覆写相关的方法,源码如下所示。
/** * @author binghe * @version 1.0.0 * @description 自定义断言功能 */ @Component public class NameRoutePredicateFactory extends AbstractRoutePredicateFactory<NameRoutePredicateConfig> { public NameRoutePredicateFactory() { super(NameRoutePredicateConfig.class); } @Override public Predicate<ServerWebExchange> apply(NameRoutePredicateConfig config) { return (serverWebExchange)->{ String name = serverWebExchange.getRequest().getQueryParams().getFirst("name"); if (StringUtils.isEmpty(name)){ name = ""; } return name.equals(config.getName()); }; } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("name"); } }
(3)在服务网关的application.yml文件中的spring.cloud.gateway.routes
节点下的- id: user-gateway
下面进行如下配置。
spring: cloud: gateway: routes: - id: user-gateway uri: http://localhost:8060 order: 1 predicates: - Path=/server-user/** - Name=binghe filters: - StripPrefix=1
(4)分别启动用户微服务与网关服务,在浏览器中输入http://localhost:10001/server-user/user/get/1001
,如下所示。
可以看到,在浏览器中输入http://localhost:10001/server-user/user/get/1001
,无法获取到用户信息。
(5)在浏览器中输入http://localhost:10001/server-user/user/get/1001?name=binghe
,如下所示。
可以看到,在访问链接后添加?name=binghe
参数后,能够正确获取到用户信息。
至此,我们实现了自定义断言功能。
网关过滤器
过滤器可以在请求过程中,修改请求的参数和响应的结果等信息。在生命周期的角度总体上可以分为前置过滤器(Pre)和后置过滤器(Post)。在实现的过滤范围角度可以分为局部过滤器(GatewayFilter)和全局过滤器(GlobalFilter)。局部过滤器作用的范围是某一个路由,全局过滤器作用的范围是全部路由。
- Pre前置过滤器:在请求被网关路由之前调用,可以利用这种过滤器实现认证、鉴权、路由等功能,也可以记录访问时间等信息。
- Post后置过滤器:在请求被网关路由到微服务之后执行。可以利用这种过滤器修改HTTP的响应Header信息,修改返回的结果数据(例如对于一些敏感的数据,可以在此过滤器中统一处理后返回),收集一些统计信息等。
- 局部过滤器(GatewayFilter):也可以称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组。
- 全局过滤器(GlobalFilter):这种过滤器主要作用于所有的路由。
局部过滤器
局部过滤器又称为网关过滤器,这种过滤器主要是作用于单一路由或者某个路由分组。
局部过滤器概述
在SpringCloud Gateway中内置了很多不同类型的局部过滤器,主要如下所示。
演示内部过滤器
演示内部过滤器时,我们为原始请求添加一个名称为IP的Header,值为localhost,并添加一个名称为name的参数,参数值为binghe。同时修改响应的结果状态,将结果状态修改为1001。
(1)在服务网关的application.yml文件中的spring.cloud.gateway.routes
节点下的- id: user-gateway
下面进行如下配置。
spring: cloud: gateway: routes: - id: user-gateway uri: http://localhost:8060 order: 1 predicates: - Path=/server-user/** filters: - StripPrefix=1 - AddRequestHeader=IP,localhost - AddRequestParameter=name,binghe - SetStatus=1001
(2)在用户微服务的io.binghe.shop.user.controller.UserController
类中新增apiFilter1()方法,如下所示。
@GetMapping(value = "/api/filter1") public String apiFilter1(HttpServletRequest request, HttpServletResponse response){ log.info("访问了apiFilter1接口"); String ip = request.getHeader("IP"); String name = request.getParameter("name"); log.info("ip = " + ip + ", name = " + name); return "apiFilter1"; }
可以看到,在新增加的apiFilter1()方法中,获取到新增加的Header与参数,并将获取出来的参数与Header打印出来。并且方法返回的是字符串apiFilter1。
(3)分别启动用户微服务与网关服务,在浏览器中输入http://localhost:10001/server-user/user/api/filter1
,如下所示。
此时,查看浏览器中的响应状态码,如下所示。
可以看到,此时的状态码已经被修改为1001。
接下来,查看下用户微服务的控制台输出的信息,发现在输出的信息中存在如下数据。
访问了apiFilter1接口 ip = localhost, name = binghe
说明使用SpringCloud Gateway的内置过滤器成功为原始请求添加了一个名称为IP的Header,值为localhost,并添加了一个名称为name的参数,参数值为binghe。同时修改了响应的结果状态,将结果状态修改为1001,符合预期效果。
自定义局部过滤器
这里,我们基于SpringCloud Gateway自定义局部过滤器实现是否开启灰度发布的功能,整个实现过程如下所示。
(1)在服务网关的application.yml文件中的spring.cloud.gateway.routes
节点下的- id: user-gateway
下面进行如下配置。
spring: cloud: gateway: routes: - id: user-gateway uri: http://localhost:8060 order: 1 predicates: - Path=/server-user/** filters: - StripPrefix=1 - Grayscale=true
(2)在网关服务模块shop-gateway中新建io.binghe.shop.filter
包,在包下新建GrayscaleGatewayFilterConfig类,用于接收配置中的参数,如下所示。
/** * @author binghe * @version 1.0.0 * @description 接收配置参数 */ @Data public class GrayscaleGatewayFilterConfig implements Serializable { private static final long serialVersionUID = 983019309000445082L; private boolean grayscale; }
(3)在io.binghe.shop.filter
包下GrayscaleGatewayFilterFactory类,继承org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory
类,主要是实现自定义过滤器,模拟实现灰度发布。代码如下所示。
/** * @author binghe * @version 1.0.0 * @description 自定义过滤器模拟实现灰度发布 */ @Component public class GrayscaleGatewayFilterFactory extends AbstractGatewayFilterFactory<GrayscaleGatewayFilterConfig> { public GrayscaleGatewayFilterFactory(){ super(GrayscaleGatewayFilterConfig.class); } @Override public GatewayFilter apply(GrayscaleGatewayFilterConfig config) { return (exchange, chain) -> { if (config.isGrayscale()){ System.out.println("开启了灰度发布功能..."); }else{ System.out.println("关闭了灰度发布功能..."); } return chain.filter(exchange); }; } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("grayscale"); } }
(4)分别启动用户微服务和服务网关,在浏览器中输入http://localhost:10001/server-user/user/get/1001
,如下所示。
可以看到,通过服务网关正确访问到了用户微服务,并正确获取到了用户信息。
接下来,查看下服务网关的终端,发现已经成功输出了如下信息。
开启了灰度发布功能...
说明正确实现了自定义的局部过滤器。
全局过滤器
全局过滤器是一系列特殊的过滤器,会根据条件应用到所有路由中。
全局过滤器概述
在SpringCloud Gateway中内置了多种不同的全局过滤器,如下所示。
演示全局过滤器
(1)在服务网关模块shop-gateway模块下的io.binghe.shop.config
包下新建GatewayFilterConfig类,并在类中配置几个全局过滤器,如下所示。
/** * @author binghe * @version 1.0.0 * @description 网关过滤器配置 */ @Configuration @Slf4j public class GatewayFilterConfig { @Bean @Order(-1) public GlobalFilter globalFilter() { return (exchange, chain) -> { log.info("执行前置过滤器逻辑"); return chain.filter(exchange).then(Mono.fromRunnable(() -> { log.info("执行后置过滤器逻辑"); })); }; } }
注意:@Order注解中的数字越小,执行的优先级越高。
(2)启动用户微服务与服务网关,在浏览器中访问http://localhost:10001/server-user/user/get/1001
,如下所示。
在服务网关终端输出如下信息。
执行前置过滤器逻辑 执行后置过滤器逻辑
说明我们演示的全局过滤器生效了。
自定义全局过滤器
SpringCloud Gateway内置了很多全局过滤器,一般情况下能够满足实际开发需要,但是对于某些特殊的业务场景,还是需要我们自己实现自定义全局过滤器。
这里,我们就模拟实现一个获取客户端访问信息,并统计访问接口时长的全局过滤器。
(1)在网关服务模块shop-order的io.binghe.shop.filter
包下,新建GlobalGatewayLogFilter类,实现org.springframework.cloud.gateway.filter.GlobalFilter
接口和org.springframework.core.Ordered
接口,代码如下所示。
/** * @author binghe * @version 1.0.0 * @description 自定义全局过滤器,模拟实现获取客户端信息并统计接口访问时长 */ @Slf4j @Component public class GlobalGatewayLogFilter implements GlobalFilter, Ordered { /** * 开始访问时间 */ private static final String BEGIN_VISIT_TIME = "begin_visit_time"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //先记录下访问接口的开始时间 exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis()); return chain.filter(exchange).then(Mono.fromRunnable(()->{ Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME); if (beginVisitTime != null){ log.info("访问接口主机: " + exchange.getRequest().getURI().getHost()); log.info("访问接口端口: " + exchange.getRequest().getURI().getPort()); log.info("访问接口URL: " + exchange.getRequest().getURI().getPath()); log.info("访问接口URL参数: " + exchange.getRequest().getURI().getRawQuery()); log.info("访问接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "ms"); } })); } @Override public int getOrder() { return 0; } }
上述代码的实现逻辑还是比较简单的,这里就不再赘述了。
(2)启动用户微服务与网关服务,在浏览器中输入http://localhost:10001/server-user/user/api/filter1?name=binghe
,如下所示。
接下来,查看服务网关的终端日志,可以发现已经输出了如下信息。
访问接口主机: localhost 访问接口端口: 10001 访问接口URL: /server-user/user/api/filter1 访问接口URL参数: name=binghe 访问接口时长: 126ms
说明我们自定义的全局过滤器生效了。
网关熔断机制
其实熔断机制在《SA实战 ·《SpringCloud Alibaba实战》第13章-服务网关:项目整合SpringCloud Gateway网关》一文中就基于SpringCloud Gateway整合Sentinel实现了。大家可以参见《SA实战 ·《SpringCloud Alibaba实战》第13章-服务网关:项目整合SpringCloud Gateway网关》一文。
注意:整个实战案例基于SpringCloud Alibaba技术栈实现,所以,整个案例专栏也是偏向于使用SpringCloud Alibaba技术栈的。