最近在使用SpringCloud搭建微服务,注册中心使用Eureka,网关使用Gateway,在配置Gateway网关的时候遇到一个问题,导致请求通过网关无法转发到对应的服务上去。花了一些时间调试排查出问题的原因,Gateway中配置route uri时不能包含下划线,否则网关无法转发成功。
抛出问题
Gateway网关中配置各个微服务的route,uri使用lb协议开启负载均衡功能,但是有些route可以正确的转发到对应的服务,有些route却无法转发到对应的服务,通过调试提示Invalid host。Gateway中route的配置部分示例如下:
spring:
application:
name: feixiao-gateway
cloud:
gateway:
discovery:
locator:
enabled: false #开启从注册中心动态创建路由的功能
lower-case-service-id: true #使用小写服务名,默认是大写
routes:
- id: servera
uri: lb://servera
predicates:
- Path=/servera/** #发送指定路径的请求会匹配该路由
filters:
- StripPrefix=1 #对指定数量的路径前缀进行去除的过滤器,将/servera/开头的请求路劲去除1位
- id: server_b
uri: lb://server_b
predicates:
- Path=/serverb/** #发送指定路径的请求会匹配该路由
filters:
- StripPrefix=1 #对指定数量的路径前缀进行去除的过滤器,将/serverb/开头的请求路劲去除1位
# 其他配置...
配置如上,route中我们使用lb协议开启了负载均衡功能,通过lb配置的服务名从注册中心获取微服务然后进行转发。访问网关地址http://***/servera/user/1可以正常转发到servera的服务上。但是访问网关地址http://***/serverb/product/1无法正常转发到server_b的服务上。
分析问题
遇到该问题后,首先初步分析问题,Gateway网关中两个服务的route配置几乎没什么差别,首先把网关配置引起的问题给排除了。推测可能是服务servera和server_b自身的问题,分别检查servera和server_b的配置以及注册情况,注册中心中可以正常看到servera和server_b,并且不通过网关单独访问servera和server_b可以正常工作。通过一番排查后,初步判断servera和server_b各种的配置以及注册都没问题。
初步分析没得到问题的原因,这时再仔细看下Gateway的官方文档,官方文档上也没获得任何线索。这时只能debug结合源码的方式逐步分析问题了。最开始debug从断路器的服务降级接口中获取到一个重要的信息Invalid host,跟踪到Invalid host是从 RouteToRequestUrlFilter中的filter过滤方法抛出:
通过调试以及分析,原来是处在server_b对应的Route中URI的host为null导致,我们对比了下Gateway中route的uri配置lb://servera和lb://server_b好像没啥区别,我们只有继续调试跟踪找出根本原因。通过调试我们发现Route是从ServerWebExchange中获取,那么我们需要找出什么时候设置的ServerWebExchange的Route。这时我们需要理清楚Gateway处理请求的流程:
流程图来源于:https://blog.csdn.net/qq_35599414/article/details/109518962
请求进来之后会通过DispatcherHandler的handler方法调用所有注册的HandlerMapping的mapping.getHandler(exchange)方法。Gateway中RoutePredicateHandlerMapping作为HandlerMapping实现注册进Spring IOC中,所以会调用RoutePredicateHandlerMapping的getHandler(exchange)方法,而该方法在被继承的抽象类AbstractHandlerMapping中实现。
在被继承的抽象类AbstractHandlerMapping的getHandler(exchange)方法中调用RoutePredicateHandlerMapping的getHandlerInternal(exchange)方法,最终在RoutePredicateHandlerMapping的lookupRoute方法中设置了ServerWebExchange的Route。
到这里我们知道Route是什么时候设置进ServerWebExchange中,从上图我们可以看到设置的Route是从this.routeLocator.getRoutes()而来。我们现在只需要知道routeLocator是如何设置routes的即可找到根源。在GatewayAutoConfiguration配置类中,注册了RouteLocator的实现类RouteDefinitionRouteLocator,RouteDefinitionLocator的实现类PropertiesRouteDefinitionLocator。我们主要关注RouteDefinitionRouteLocator和PropertiesRouteDefinitionLocator。
RoutePredicateHandlerMapping中routeLocator成员的接口实现有很多,在RouteLocator的实现类RouteDefinitionRouteLocator中,我们看到getRoutes是从this.routeDefinitionLocator.getRouteDefinitions()而来,而routeDefinitionLocator是通过构造方法传递进来的,所以我们需要继续追踪routeDefinitionLocator的getRouteDefinitions。
在RouteDefinitionLocator接口的实现类PropertiesRouteDefinitionLocator中,我们可以得知getRouteDefinitions方法实际上是获取GatewayProperties类的getRoutes方法。
继续跟进GatewayProperties类,我们可以发现该类是读取的spring.cloud.gateway配置,而类型为RouteDefinition的列表属性routes则是读取的spring.cloud.gateway.routes配置。
继续跟进RouteDefinition类,在该类中我们获取到URI类型的uri属性,那么该属性则是读取spring.cloud.gateway.routes[index].uri配置。
到此我们终于弄清楚Gateway中route的加载,以及请求中ServerWebExchange中route的设置以及获取。项目启动的时候GatewayProperties类将routes从spring.cloud.gateway.routes配置中初始化,每个请求通过RoutePredicateHandlerMapping将routes设置进ServerWebExchange的attributes中,在RouteToRequestUrlFilter中从ServerWebExchange的attributes中获取进行后续的转发处理。回到我们这次遇到的问题上RouteToRequestUrlFilter中filter方法里面route.getUri().getHost()为null,我们已经知道route.uri是直接通过配置spring.cloud.gateway.routes[index].uri获取的。配置中我们设置的uri: lb://server_b无法正常转发,配置中uri为字符串,而route中为URI类型的对象。那么问题的原因基本上确定了,在通过字符串构造URI类型对象的时候,uri的host未正常生成。这是因为构造URI时,字符串如果有下划线是无法解析host的,URI解析host源码如下。同时可以参考:https://stackoverflow.com/questions/25993225/uri-gethost-returns-null-why。
解决问题
现在我们分享了问题的真正原因,是因为Gateway中route的uri中包含了下划线“_”,导致网关无法转发请求到对应的服务上。解决的方案是,使用Gateway作为网关,route配置中uri不能包含下划线“_”。那么对应的注册到注册中心的服务的服务名不能包含下划线,否则Gateway结合注册中心无法转发给含下划线的服务。
总结
本文记录了SpringCloud Gateway网关中route如果配置了下划线的uri导致请求无法转发的问题的解决过程。分析过程中我们先初步分析,在细看官方文档,最后调试查阅源码,最终锁定问题的原因,最后解决了问题。在熟悉Gateway源码的时候参考了其他网友的博客:https://blog.csdn.net/qq_35599414/article/details/109518962。文中如果有描述不正确的地方,希望各位博友反馈。该篇文章主要记录一下问题的解决过程,同时希望其他网友遇到该问题可以提供一些帮助。