Spring Cloud Gateway 不小心换了个 Web 容器就不能用了,我 TM 人傻了
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud Gateway 不小心换了个 Web 容器就不能用了,我 TM 人傻了相关的知识,希望对你有一定的参考价值。
参考技术A最近组员修改微服务的一些公共依赖,在某个依赖中需要针对我们微服务使用的 Undertow 容器做一些订制,所以加入了 web 容器 Undertow 的依赖。但是,一般这种底层框架依赖,是要兼顾当前使用的这个项目的 web 容器是否是 Undertow,这位同学在配置类上写了 @Conditional:
但是加的 undetow 依赖的 scope 没有设置为 provided ,导致只要加入这个依赖就会加入 Undertow 的依赖。正好网关也用到了这个依赖,并且我们的网关使用的是 Spring-Cloud-Gateway。这就导致了 Spring-Cloud-Gateway 本身的 Netty 的 Reactive 的 web 容器被替换成了 Undertow 的 Reactive 的 web 容器,从而导致了一系列的 Spring-Cloud-Gateway 不兼容的问题。
我们知道,Spring-Cloud-Gateway 其实底层也是基于 Spring Boot 的。首先来看下 Spring Boot 中初始化哪种 web 容器的选择原理:首先第一步是根据类是否存在确定是哪种 WebApplicationType:
WebApplicationType
从源码中可以看出,当有 WEBFLUX_INDICATOR_CLASS 并且没有 WEBMVC_INDICATOR_CLASS 以及 JERSEY_INDICATOR_CLASS 的时候,判断为 REACTIVE 环境。如果所有 SERVLET_INDICATOR_CLASSES 就认为是 SERVLET 环境。其实这样也可以看出, 如果又引入 spring-web 又引入 spring-webflux 的依赖,其实还是 SERVLET 环境 。如果以上都没有,那么就是无 web 容器的环境。在 Spring-Cloud-Gateway 中,是 REACTIVE 环境。
如果是 REACTIVE 环境,就会使用 org.springframework.boot.web.reactive.server.ReactiveWebServerFactory 的实现 Bean 创建 web 容器。那么究竟是哪个实现呢?目前有四个实现(Spring-boot 2.7.x):
实际会用哪个,看到底哪个 Bean 会注册到 ApplicationContext 中:
ReactiveWebServerFactoryConfiguration
从原码可以看出,每种配置上都有 @ConditionalOnMissingBean(ReactiveWebServerFactory.class) 以及判断是否有对应容器的 class 的条件,例如:@ConditionalOnClass( Undertow.class ),@Configuration(proxyBeanMethods = false)是关闭这个配置中 Bean 之间的代理加快加载速度。
由于每个配置都有 @ConditionalOnMissingBean(ReactiveWebServerFactory.class),那么其实能保证就算满足多个配置的条件,最后也只有一个 ReactiveWebServerFactory,那么当满足多个条件时,哪个优先加载呢?这就要看这里的源码:
ReactiveWebServerFactoryAutoConfiguration
从这里可以看出,是按照 EmbeddedTomcat,EmbeddedJetty,EmbeddedUndertow,EmbeddedNetty 的顺序 Import 的,也就是: 只要你的依赖中加入了任何 Web 容器(例如 Undertow),那么最后创建的就是基于那个 web 容器的异步容器,而不是基于 netty 的
首先, Spring Cloud Gateway 的官方文档中就说了:
就是 Spring Cloud Gateway 只能在 Netty 的环境中运行。这是为什么呢。当初在设计的时候,就假定了容器只能是 Netty,后续开发各种 Spring Cloud Gateway 的内置 Filter 以及 Filter 插件的时候,有很多假设当前就是 Netty 的代码,例如缓存 Body 的 Filter 使用的工具类 ServerWebExchangeUtils:
ServerWebExchangeUtils
从源码中可以看到,代码直接认为 response 中的 BufferFactory 就是 NettyDataBufferFactory,其实在其他 Web 容器的情况下,目前应该是 DefaultDataBufferFactory,这样就会有异常。不过在 v3.0.5 之后的版本,已经修复了这个强转,参考:https://github.com/spring-cloud/spring-cloud-gateway/commit/68dcc355119e057af1e4f664c81f77714c5a8a16
这其实也是为兼容所有的 Web 容器进行铺路。那么, 究竟有计划兼容所有的 Web 容器么 ?是有计划的,还在做,已经做了快 4 年了,应该快做好了,相当于所有的单元测试要重新跑甚至重新设计,可以通过这个 ISSUE:Support running the gateway with other reactive containers besides netty #145
来查看兼容的进度。
Spring Cloud Gateway 默认路由不起作用
【中文标题】Spring Cloud Gateway 默认路由不起作用【英文标题】:Spring cloud gateway default routing doesn't work 【发布时间】:2020-04-01 03:49:13 【问题描述】:我想通过在 eureka 中注册的服务 ID(应用程序名称)在我的 spring 云网关(无 zuul)中启用默认路由,但我总是收到 404 错误。
在我的聊天服务的 bootstrap.yml 我已经定义了应用名称
spring:
application:
name: chat-service
在应用程序属性中:
eureka:
instance:
preferIpAddress: true
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://$EUREKA_HOST:localhost:$EUREKA_PORT:8761/eureka/
当我转到 eureka 的仪表板时,我可以看到我的聊天服务和网关也已注册。
网关应用中Eureka的配置和聊天服务一样,但是我也有这个:
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
enabled: true
接下来我还尝试添加不能正常工作的显式路由,但如果我将发现定位器启用设置为true
,这应该不需要吧?
routes:
- id: chat-service-route
uri: lb://chat-service
predicates:
- Path=**
我创建了测试端点,我尝试直接在聊天服务和网关上调用。直接呼叫工作正常,因此问题将与路由有关。
@RestController
@RequestMapping
public class TestController
@GetMapping
public String test()
return "chat-service ready";
我做错了什么?我有点绝望。我正在使用 spring boot 2.2.2 和 Hoxton.RELEASE 云依赖版本
【问题讨论】:
你能解释一下“不起作用”是什么意思吗?你试过什么网址? @spencergibb 对不起,我应该写得更干净。当我从网关调用时,聊天服务中的休息端点总是出现 404 错误 你在网关上调用什么url? @spencergibb 网关在 8080 上运行,所以我调用 localhost:8080/chat-service 聊天服务是否响应/chat-service?否则需要重写路径 【参考方案1】:尝试删除显式路由并将以下属性添加到应用程序 yml。这对我有用。
spring:
cloud:
gateway:
discovery:
locator:
lower-case-service-id: true
【讨论】:
以上是关于Spring Cloud Gateway 不小心换了个 Web 容器就不能用了,我 TM 人傻了的主要内容,如果未能解决你的问题,请参考以下文章
Spring Cloud Gateway 不小心换了个 Web 容器就不能用了,我 TM 人傻了
网关 zuul 与 spring-cloud gateway的区别
spring-cloud-gateway之GatewayFilterFactory