简体   繁体   English

当服务器提前响应时,Spring WebClient / Reactor-netty 坏了?

[英]Spring WebClient / Reactor-netty broken when a server responds early?

I don't know much about Webflux / Reactor / Netty.我对 Webflux / Reactor / Netty 了解不多。 I'm using Spring's WebClient to do all the heavy lifting.我正在使用 Spring 的 WebClient 来完成所有繁重的工作。 But it appears not to work correctly when a server responds back early with an error.但是当服务器提前响应错误时,它似乎无法正常工作。

My understanding is when you are POSTing data to a server, the server can respond at any time with an HTTP 4XX error.我的理解是,当您将数据发布到服务器时,服务器可以随时响应 HTTP 4XX 错误。 The client is supposed to stop sending the HTTP body and read that error.客户端应该停止发送 HTTP 正文并读取该错误。

I have a very simply WebClient that POSTs data to a server.我有一个非常简单的 WebClient,它可以将数据发布到服务器。 It looks like this:它看起来像这样:

FileResponse resp = client.post().uri(uri)
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .header("Authorization", "Bearer " + authorizationToken)
        .accept(MediaType.APPLICATION_JSON)
        .bodyValue(data)
        .retrieve()
        .bodyToMono(FileResponse.class)
        .block();

The body can contain a large amount of data (100+KB).正文可以包含大量数据 (100+KB)。 Apparently the server looks at the header, validates the authorization token, and only if it's valid, reads the body.显然,服务器查看标头,验证授权令牌,只有在有效时才读取正文。 If the authorization token is not valid (expired, etc) it immediately responds with an "HTTP 401 Unauthorized" with the response body "{"message": "Invalid user/password"}" while the client is still sending the body.如果授权令牌无效(过期等),它会立即响应“HTTP 401 Unauthorized”,响应主体为“{"message": "Invalid user/password"}”,而客户端仍在发送主体。 The server then closes the socket which results in the WebClient throwing this:服务器然后关闭导致 WebClient 抛出此的套接字:

2022-08-10 15:56:03,474 WARN  [reactor.netty.http.client.HttpClientConnect] (reactor-http-nio-1) [id: 0xa7b48bb8, L:/5.6.7.8:51122 - R:dubcleoa030/1.2.3.4:5443] The connection observed an error: java.io.IOException: An existing connection was forcibly closed by the remote host
                at java.base/sun.nio.ch.SocketDispatcher.read0(Native Method)
                at java.base/sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:43)
                at java.base/sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:276)
                at java.base/sun.nio.ch.IOUtil.read(IOUtil.java:233)
                at java.base/sun.nio.ch.IOUtil.read(IOUtil.java:223)
                at java.base/sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:358)
                at deployment.bp-global.war//io.netty.buffer.PooledByteBuf.setBytes(PooledByteBuf.java:253)
                at deployment.bp-global.war//io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1133)
                at deployment.bp-global.war//io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:350)
                at deployment.bp-global.war//io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:148)
                at deployment.bp-global.war//io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
                at deployment.bp-global.war//io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
                at deployment.bp-global.war//io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
                at deployment.bp-global.war//io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
                at deployment.bp-global.war//io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
                at deployment.bp-global.war//io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
                at deployment.bp-global.war//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
                at java.base/java.lang.Thread.run(Thread.java:834)

I've made the same request with curl, and it's handled properly.我用 curl 提出了相同的请求,并且处理得当。 Curl sees the server's early response, stops sending the body and processes the response from the server. Curl 看到服务器的早期响应,停止发送正文并处理来自服务器的响应。 I've chopped out a lot of fluff from the curl output but here is the important stuff...我从卷曲输出中切掉了很多绒毛,但这是重要的东西......

   Trying 1.2.3.4...

* TCP_NODELAY set
* Connected to 1.2.3.4 port 5443 (#0)


> POST /api/folders/file/?path=/out HTTP/1.1
> Host: 1.2.3.4:5443
> User-Agent: curl/7.61.1
> accept-encoding: gzip
> Content-Type: application/octet-stream
> Authorization: Bearer youshallnotpass
> accept: application/json
> Content-Length: 298190
> 

< HTTP/1.1 401 Unauthorized
< Server: Cleo Harmony/5.7.0.3 (Linux)
< Date: Wed, 10 Aug 2022 21:59:23 GMT
< Content-Length: 36
< Content-Language: en
< Content-Type: application/json
< Connection: keep-alive

* HTTP error before end of send, stop sending

* Closing connection 0

{"message": "Invalid user/password"}

I'm not sure if the issue is with Spring's WebClient or the underlying reactor-netty stuff.我不确定问题是出在 Spring 的 WebClient 还是底层的 reactor-netty 东西上。 But am I crazy or does it just look broken if the server responds early?但是我是不是疯了,或者如果服务器响应早,它看起来就坏了? If I am correct that it's broken, any thoughts on a work-around?如果我认为它坏了是正确的,那么有什么解决方法吗?

Thank you!谢谢! Todd托德

I setup a small stand-alone command line Spring Boot program so I could test various aspects of this issue.我设置了一个小的独立命令行 Spring 引导程序,以便我可以测试这个问题的各个方面。 What I found is that if I send the body from memory (byte[]) the issue occurs.我发现如果我从 memory (byte[]) 发送正文,就会出现问题。 If I send the body from a file resource as shown below, everything works correctly.如果我从如下所示的文件资源发送正文,则一切正常。

Our current very large product is using Spring Boot 2.3.3.我们目前的超大型产品使用的是 Spring Boot 2.3.3。 My stand-alone test program gave me the ability to quickly upgrade Spring Boot to 2.7.2.我的独立测试程序让我能够快速将 Spring Boot 升级到 2.7.2。 Everything works correctly in Spring Boot 2.7.2. Spring Boot 2.7.2 中一切正常。 So it was definitely a bug that was fixed at some point.所以这绝对是一个在某个时候修复的错误。

Unfortunately our large project cannot be upgraded to Spring Boot 2.7.2 overnight.不幸的是,我们的大型项目无法在一夜之间升级到 Spring Boot 2.7.2。 It will be a large effort which will require extensive testing from our QA department.这将是一项巨大的工作,需要我们的 QA 部门进行广泛的测试。 So for now, as a work-around, I'll write the body payload to a temporary file so I can get WebClient to read it as a file resource which works in 2.3.3 as shown below.所以现在,作为一种变通方法,我会将正文有效负载写入一个临时文件,以便让 WebClient 将其作为文件资源读取,该文件资源在 2.3.3 中工作,如下所示。 Just in case any other poor developer runs into this and needs a work-around, try this...以防万一任何其他可怜的开发人员遇到这种情况并需要解决方法,试试这个......

    InputStreamResource resource = new InputStreamResource(new FileInputStream(tempFilename));
    
    
    String resp = client.post().uri(uri)
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header("Authorization", "Bearer youshallnotpass")
            .accept(MediaType.APPLICATION_JSON)
            .body(BodyInserters.fromResource(resource))
            .retrieve()
            .bodyToMono(String.class)
            .block();

EDIT: I started going through the releases to determine when this was fixed.编辑:我开始浏览版本以确定何时修复。 This issues appears to be fixed in Spring Boot 2.3.4 (Netty 4.1.52).此问题似乎已在 Spring Boot 2.3.4 (Netty 4.1.52) 中得到修复。 I cannot find any reference to this bug in the release notes of Spring Boot or Netty.我在 Spring Boot 或 Netty 的发行说明中找不到对此错误的任何引用。 So I'm not sure where the issue was.所以我不确定问题出在哪里。 Perhaps it was in a deeper dependency.也许它处于更深层次的依赖中。 But it was definitely fixed with Spring Boot 2.3.4.但它肯定是用 Spring Boot 2.3.4 修复的。

It may happened because there is a conflict in dependencies (may create conflict with azure dependency for same package): update your spring boot version I ahve updted to latest and works for me.这可能是因为依赖项存在冲突(可能会与同一包的 azure 依赖项发生冲突):更新你的 spring boot 版本我已经更新到最新版本并且对我有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 spring webflux:将websocket适配器连接到reactor-netty服务器的纯功能方式 - spring webflux: purely functional way to attach websocket adapter to reactor-netty server 使用 spring HATEOAS 和 spring webflux 功能 Web 框架 (reactor.netty) - Using spring HATEOAS with spring webflux Functional Web Framework (reactor-netty) 如何配置反应堆网络以使用SSL? - How do I configure reactor-netty to use SSL? SOS HELP.. 求助: reactor-netty 总是 io.netty.channel.ConnectionTimeoutException - SOS HELP!! Ask for help: reactor-netty always io.netty.channel.ConnectionTimeoutException 使用 Reactor Netty 配置 Spring Boot 以侦听 2 个端口 - Configure Spring Boot with Reactor Netty to Listen on 2 Ports Reactor Netty 中的线性化(Spring Boot Webflux) - Linearization in Reactor Netty (Spring Boot Webflux) Spring 5 Reactor Netty日志垃圾邮件(太冗长) - Spring 5 Reactor Netty log spam (too verbose) Spring Reactor WebClient具有Flatmap和Takewhile的顺序请求 - Spring reactor webclient sequential requests with flatmap and takewhile 从 spring webclient.netty 层抑制堆栈跟踪 - suppress stacktrace from spring webclient/netty layer Java/Spring 错误:“reactor.netty.http.server.HttpServer”中的“create()”不能应用于“(java.lang.String, int)” - Java/Spring error: 'create()' in 'reactor.netty.http.server.HttpServer' cannot be applied to '(java.lang.String, int)'
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM