Spring WebFlux 5.3.0 - WebClient.exchangeToMono()

Lar*_*rts 7 spring-webflux spring-webclient

我刚刚升级到 Webflux 5.3.0,并注意到 WebClient.exchange() 方法现在已被弃用(链接)以支持新方法 .exchangeToMono() 和 .exchangeToFlux()

我有这个代码:

webClient
   .method(request.method)
   .uri(request.path)
   .body(request.bodyToMono<ByteArray>())
   .exchange()
   .flatMap { response ->
      ServerResponse.
         .status(response.statusCode())
         .headers { it.addAll(response.headers().asHttpHeaders()) }
         .body(response.bodyToMono<ByteArray>())
   }
Run Code Online (Sandbox Code Playgroud)

我不得不将其重构为:

   .exchangeToMono { response ->
      ServerResponse.
         .status(response.statusCode())
         .headers { it.addAll(response.headers().asHttpHeaders()) }
         .body(response.bodyToMono<ByteArray>())
   }
Run Code Online (Sandbox Code Playgroud)

然而,显然 .exchangeToMono() 调用了 .releaseIfNotConsumed(),它释放了未处理的响应体,并且基本上使服务器返回一个空体

所以我不得不进一步重构我的代码:

   .exchangeToMono { response ->
      response.bodyToMono<ByteArray>()
         .defaultIfEmpty(ByteArray(0))
         .flatMap { body ->
            ServerResponse.
               .status(response.statusCode())
               .headers { it.addAll(response.headers().asHttpHeaders()) }
               .bodyValue(body)
         }
   }
Run Code Online (Sandbox Code Playgroud)

据我了解, .exchange() 允许我的代理服务器传输响应正文而不实际处理它,而 .exchangeToMono() 强制我处理(缓冲?)它。这样对吗?

如果是这样,有什么影响?我应该接受更改,还是应该以某种方式调整代码以使其传输响应主体而不处理它?我该怎么做?

==========

tl;dr通过.body(response.bodyToMono())和之间的实际区别是什么.bodyValue(body)

Tho*_*olf 11

在阅读更改并尝试理解您的问题后,我将尝试回答这个问题。我无论如何都不确定这是正确的答案,我将根据我对 reactor、webflux 和 webclient 的了解做出一些合乎逻辑的假设。

自从 WebClient 发布以来,主要的主力应该retrieve()是能够针对完全异步的 Web 客户端提供简单但稳定的 API。

问题是大多数人都习惯于使用ResponseEntities旧的已弃用的返回值,RestTemplate因此 ppl 转而使用该exchange()函数。

但问题就在这里。当您获得对 的访问权时,Response您也有责任与之相关。您有义务消耗response以便服务器可以关闭 TCP 连接。这通常意味着您需要读取标题和正文,然后我们才能关闭连接。

如果您不使用响应,您将拥有一个打开的连接,从而导致内存泄漏。

Spring 通过提供像response#bodyToMonoand 之类的函数来解决这个问题,response#bodyToFlux它消耗主体然后关闭响应(反过来关闭连接,从而消耗响应)。

但事实证明,人们编写不消耗响应的代码非常容易(因为开发人员是狡猾的混蛋),因此提供悬空的 TCP 连接。

webclient.url( ... )
    .exchange(response -> {

        // This is just an example but, but here i just re-return the response
        // which means that the server will keep the connection open, until i 
        // actually consume the body. I could send this all over my application
        // but never consume it and the server will keep the connection open as
        // long as i do, could be a potential memory leak.

        return Mono.just(response)
    }
Run Code Online (Sandbox Code Playgroud)

新的exchangeToMono实现基本上会强制您使用 body 来避免内存泄漏。如果你想处理原始反应,你将被迫消耗身体。

所以lats谈论你的例子和你的需求。

您只想将请求从一台服务器代理到另一台服务器。实际上,您确实消耗了身体,只是flatMap在靠近 WebClient 的地方不这样做。

.exchange()
   .flatMap { response ->
      ServerResponse.
         .status(response.statusCode())
         .headers { it.addAll(response.headers().asHttpHeaders()) }
         .body(response.bodyToMono<ByteArray>()) 
         // Here you are declaring you want to consume but it isn't consumed right here, its not consumed until much later.
   }
Run Code Online (Sandbox Code Playgroud)

在您的代码中,您正在返回 aServerResponse但您必须始终考虑。在您订阅之前什么都不会发生。你基本上是通过了很长时间,ServerResponse但你还没有消耗身体。您只声明当服务器需要正文时,它必须消耗最后一个响应的正文以获取新正文。

这样想,您将返回一个ServerResponse只包含关于我们想要的内容的声明,而不是其中实际内容的声明。

flatMap它从它返回时,它会一路离开应用程序,直到我们将它写为对我们针对客户端的开放 TCP 连接的响应。

只有在那里,然后才会构建响应,那时您来自 WebClient 的第一个响应将被使用和关闭。

所以您的原始代码确实有效,因为您确实使用了 WebClient 响应,您只是在向调用客户端写入响应之前不会这样做。

您所做的并非本质上是错误的,只是以这种方式使用 WebClient API 会增加 ppl 错误使用它的风险,并且可能会发生内存泄漏。

我希望这至少能回答你的一些问题,我主要是写下我对变化的解释。

  • 我同意这个答案。至于问题的最后一部分(“Mono&lt;ByteArray&gt;”与“ByteArray”):在每种情况下,完整的响应正文将被读取并缓冲在单个“ByteArray”实例的内存中,因此从这个意义上说,它们“重新等效。如果您想提高内存效率,那么处理 `Flux&lt;DataBuffer&gt;` 会更好。 (3认同)
  • 请不要在评论中提出新问题 (2认同)