我刚刚升级到Webflux 5.3.0,注意到WebClient.exchange()方法现在已弃用(link)支持新方法.exchangeToMono()和.exchangeToFlux()
我有这样的代码:
webClient
.method(request.method)
.uri(request.path)
.body(request.bodyToMono())
.exchange()
.flatMap { response ->
ServerResponse.
.status(response.statusCode())
.headers { it.addAll(response.headers().asHttpHeaders()) }
.body(response.bodyToMono())
}
我不得不把它重构成这样:
.exchangeToMono { response ->
ServerResponse.
.status(response.statusCode())
.headers { it.addAll(response.headers().asHttpHeaders()) }
.body(response.bodyToMono())
}
但是,显然,.exchangeToMono()调用.releaseIfNotConsumed(),这将释放未处理的响应主体,并基本上使服务器返回一个空主体
所以我不得不进一步重构我的代码:
.exchangeToMono { response ->
response.bodyToMono()
.defaultIfEmpty(ByteArray(0))
.flatMap { body ->
ServerResponse.
.status(response.statusCode())
.headers { it.addAll(response.headers().asHttpHeaders()) }
.bodyValue(body)
}
}
据我所知,.exchange()允许我的代理服务器在没有实际处理的情况下传输响应正文,而.exchangeToMono()强制我处理(buffer?)它。这是正确的吗?
如果是这样,其影响是什么?我应该接受更改,还是应该以某种方式调整代码,使其在不处理响应正文的情况下传输响应正文?我该怎么做呢?
=
tl;dr通过的实际区别是什么?.body(response.bodyToMono())
和.bodyValue(body)
发布于 2020-11-03 20:39:00
在通读了更改并试图理解您的问题后,我将尝试回答这个问题。我不确定这是不是正确的答案,我将根据我对reactor、webflux和webclient的了解做出一些逻辑假设。
自从WebClient发布以来,主要的主力应该是retrieve()
能够针对完全异步的but客户端提供简单但稳定的API。
问题是大多数人习惯于使用ResponseEntities
由旧的已弃用的RestTemplate
所以ppl转而使用exchange()
函数。
但问题就在这里。当您获得对Response
你也有一个与之相关的责任。您有义务使用response
以便服务器可以关闭TCP连接。这通常意味着您需要读取头部和正文,然后我们才能关闭连接。
如果您不使用响应,您将拥有一个打开的连接,这会导致内存泄漏。
Spring通过提供如下函数解决了这个问题response#bodyToMono
和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)
}
新的exchangeToMono
实现基本上会强制您使用正文,以避免内存泄漏。如果您想要处理原始响应,您将被迫使用正文。
下面我们来谈谈你的例子和你的需求。
您只需要将请求从一台服务器代理到另一台服务器即可。你确实消耗了身体,但不是在flatMap
离WebClient很近。
.exchange()
.flatMap { response ->
ServerResponse.
.status(response.statusCode())
.headers { it.addAll(response.headers().asHttpHeaders()) }
.body(response.bodyToMono())
// Here you are declaring you want to consume but it isn't consumed right here, its not consumed until much later.
}
在您的代码中,您将返回一个ServerResponse
但你必须时刻想着。
订阅之前什么都不会发生..。您基本上是在传递一个很长的ServerResponse
但你还没有吃掉尸体。您只声明了当服务器需要正文时,它将不得不使用获取新正文的最后一个响应的正文。
这样想,您将返回一个ServerResponse
它只包含我们想要的东西的声明,而不是实际的东西。
方法返回此参数时,flatMap
它将一直传输到应用程序之外,直到我们将其编写为对客户端打开的TCP连接的响应。
只有在那时才会构建响应,也就是使用和关闭来自WebClient的第一个响应的时候。
因此,您的原始代码是有效的,因为您确实使用了WebClient响应,只是在向调用客户端编写响应之前并没有这样做。
你所做的并不是天生的错误,只是以这种方式使用WebClient API会增加人们错误使用它的风险,并且可能会发生内存泄漏。
我希望这至少回答了你的一些问题,我主要是写下我对这个变化的解释。
发布于 2021-02-27 00:25:50
作为向后兼容性的安全解决方法:
exchangeToMono(rs -> Mono.just(rs.mutate().build)))
你仍然会收到ClientResponse
使用原始数据消耗/释放所有数据的下游类型ClientResponse
期间mutate()
..。别忘了释放变异的一个。
https://stackoverflow.com/questions/64650820
复制相似问题