spring cloud gateway java.lang.IllegalStateException: Only one connection receive subscriber allowed.
spring-cloud-gateway反向代理的原理是,首先读取原请求的数据,然后构造一个新的请求,将原请求的数据封装到新的请求中,然后再转发出去。然而我们在他封装之前读取了一次request body,而request body只能读取一次。因此就出现了上面的错误。
大意就是netty的request body只能读取一次,第二次读取就报这个错误了。
问题原因
翻查GitHub终于找到,spring boot在2.0.5版本如果使用了WebFlux就自动配置HiddenHttpMethodFilter过滤器。
查看源码发现,这个过滤器的作用是,针对当前的浏览器一般只支持GET和POST表单提交方法,如果想使用其他HTTP方法(如:PUT、DELETE、PATCH),就只能通过一个隐藏的属性如(_method=PUT)来表示,那么HiddenHttpMethodFilter的作用是将POST请求的_method参数里面的value替换掉http请求的方法。
想法是很好的,用一种折中的方法来支持使浏览器支持restful方法。
如果只是使用spring boot,一切都是没有问题的,因为使用的过程中,不需要我们自己解析request body,到controller这一层,这一切就已经完成的了。
但是spring cloud gateway需要,因为它的做法就是拿到原始请求信息(包括request body),再重新封装一个request路由到下游微服务,所以上面的问题就在于:
HiddenHttpMethodFilter读取了一次request body;
gateway的封装自己的request时,去读取request body,就报错了。
所以这个是spring cloud gateway和spring boot开发者没协商好,都去读取request body的问题。
问题解决方案
HiddenHttpMethodFilter是spring boot在2.0.5版本自动引入的,将版本降到2.0.4即可。
如果不降版本,也可以自己重写HiddenHttpMethodFilter来覆盖原来的实现,如下:
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new HiddenHttpMethodFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange);
}
};
}
filter方法什么都不做,自然就不会读取request body了,你可能会想,那不就不支持HiddenHttpMethodFilter的功能了吗,其实gateway本身就不应该做这种事情,原始请求是怎样的,转发给下游的请求就应该是怎样的。下游服务如果使用的是也是spring boot的服务,那么下游服务自己会做HiddenHttpMethodFilter的功能。
参考资料
- https://github.com/spring-cloud/spring-cloud-gateway/issues/541
- https://blog.csdn.net/hong10086/article/details/92396319