在 Spring-Boot 的嵌入式 Netty 中增加或移除内容长度限制

Posted

技术标签:

【中文标题】在 Spring-Boot 的嵌入式 Netty 中增加或移除内容长度限制【英文标题】:Increase or remove content length restrictions in Spring-Boot's embedded Netty 【发布时间】:2018-12-02 10:34:14 【问题描述】:

我在一些现有的微服务前面放置了一个 Spring Cloud Gateway。它主要工作,但我有一个 websocket (SockJS) 连接(显然)传输大量数据。

事实证明,Netty 显然有一个内容长度最大值——当我在我的 SockJS 路由中超出该限制时,我收到了这个错误:

2018-06-22 16:47:58.740 ERROR 11164 --- [ctor-http-nio-5] r.ipc.netty.channel.ContextHandler       : Error cannot be forwarded to user-facing Mono

io.netty.handler.codec.TooLongFrameException: content length exceeded 65536 bytes.
    at io.netty.handler.codec.MessageAggregator.handleOversizedMessage(MessageAggregator.java:399) [netty-codec-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.handler.codec.MessageAggregator.invokeHandleOversizedMessage(MessageAggregator.java:383) [netty-codec-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.handler.codec.MessageAggregator.decode(MessageAggregator.java:277) [netty-codec-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:88) [netty-codec-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310) [netty-codec-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284) [netty-codec-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459) [netty-transport-4.1.24.Final.jar:4.1.24.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) [netty-common-4.1.24.Final.jar:4.1.24.Final]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]

如何将 spring-boot 中的嵌入式 Netty 配置为没有内容长度限制?我无法控制响应中返回的消息的长度。

网关中的路由如下所示:

- id: check_status_sockjs
  uri: http://foo.example.com:8080
  predicates:
    - Path=/foo/status/**
  filters:
    - RewritePath=/foo/status/(?<segment>.*), /status/$\segment

我将 sockJS 请求路由到的另一端的服务也是一个 Spring-Boot 应用程序,在 Tomcat 8.5 服务器中部署为一个战争。当我直接与支持服务交互时,我不会遇到与内容长度有关的任何问题 - 只有当我尝试通过网关应用程序中的嵌入式 Netty 进行路由时。

我还成功地通过这条路线发送和接收较小的消息 - 只有当我达到这个明显的长度限制时它才会爆炸。

我在网关中的依赖是这样的:

spring-cloud-starter-gateway (2.0.0.RELEASE) spring-boot-starter-webflux (2.0.2.RELEASE) spring-boot-starter-actuator (2.0.2.RELEASE)

【问题讨论】:

你试过-Dhttp.netty.maxInitialLineLength吗?请参阅github.com/playframework/playframework/issues/5036、***.com/questions/43813715/… 和 ***.com/questions/29189713/… 我没有得到关于标题长度的异常......我的异常是由于内容长度。 【参考方案1】:

如何在 spring-boot 中配置嵌入式 Netty,使其没有内容长度限制?

目前不可能。 见reactor-netty #223 和Spring Framework #16228。


分析:

原来 Netty 显然有一个内容长度最大值

限制来自reactor-netty,而不是直接来自Netty

限制来自reactor. ... .WebsocketInbound,这是一个 Java 接口,每帧最多可聚合 65,536 个字节

我只能找到一个实现WebsocketInbound 的类:HttpServerWSOperations。此类不会更改默认方法WebsocketInbound.aggregateFrames,因此保留了 65k 字节的限制。

HttpServerWSOperationsfinal方法HttpServerOperations.withWebsocketSupport使用,直接实例化,不能更改实现。

【讨论】:

我害怕这个。我是否正确理解这只是 websockets 的限制?【参考方案2】:

现在可以使用以下 bom:

dependencyManagement 
  imports 
    mavenBom("org.springframework.boot:spring-boot-dependencies:2.2.2.RELEASE")
    mavenBom("org.springframework.cloud:spring-cloud-dependencies:Hoxton.RELEASE") 
  

然后在您的网关应用程序配置中,您可以设置:

spring.cloud.gateway.httpclient.websocket.max-frame-payload-length: <bytes_new_limit>

我为此苦苦挣扎了一段时间,我的弹簧靴在“2.1.10.RELEASE”上。他们在这些托管依赖项附带的新版本 webflux 中修复了它。

【讨论】:

【参考方案3】:
@Configuration
public class NettyConfiguration implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> 

    @Value("$server.max-initial-line-length:65536")
    private int maxInitialLingLength;
    @Value("$server.max-http-header-size:65536")
    private int maxHttpHeaderSize;

    public void customize(NettyReactiveWebServerFactory container) 
        container.addServerCustomizers(
                httpServer -> httpServer.httpRequestDecoder(
                        httpRequestDecoderSpec -> 
                            httpRequestDecoderSpec.maxHeaderSize(maxHttpHeaderSize);
                            httpRequestDecoderSpec.maxInitialLineLength(maxInitialLingLength);
                            return httpRequestDecoderSpec;
                        
                )
        );
    

这对我有用,我设置了 maxHeaderSize 和 maxInitialLineLength 参数

【讨论】:

如 cmets 中所述,这些属性都不是问题。是max-frame-payload-length,当时没有曝光。此外,不需要执行所有这些代码:只需在应用程序属性中设置适当的 spring.cloud.gateway.httpclient.websocket.* 值,spring boot 就会为您自动配置 netty。

以上是关于在 Spring-Boot 的嵌入式 Netty 中增加或移除内容长度限制的主要内容,如果未能解决你的问题,请参考以下文章

netty事件模型实现原理

如何在 spring-boot 中拦截嵌入式 Tomcat 上的“全局”404

如何避免使用 Spring-Boot 下载嵌入式 MongoDb

在 spring-boot 中配置嵌入式 apache tomcat 内的“tomcat-server.xml”文件

使用 IntelliJ 部署启用嵌入式 tomcat 的 spring-boot 应用程序

如何禁用嵌入式数据库 Spring-boot spring-data-jpa