问题
I'm trying to implement streaming proxy. I faced with the issue with WebClient from spring reactive.
Could anyone help me to understand Do I some wrong way or it's just a bug from WebClient side?
Stack:
reactor-netty 0.7.8.RELEASE
spring-boot 2.0.4.RELEASE
Desc:
I want to proxy a long stream to external service and then forward stream of responses to the requester. Streaming is happening using chunks (HTTP 1.1 Transfer-Encoding : chunked). External service processes every chunk and sends to the response result.
Expected behaviour:
WebClient should read every received part of response immediately.
Actual behaviour:
WebClient doesn't start process response until request write is completed.
Code:
return client
.post()
.header("Transfer-Encoding", "chunked")
//because I want to flush each received part
.body((outputMessage, context) -> outputMessage.writeAndFlushWith(
request.body(BodyExtractors.toDataBuffers())
.map(dataBuffer -> Mono.just(dataBuffer))))
.exchange()
.flatMap(clientResponse -> {
ServerResponse.BodyBuilder bodyBuilder = ServerResponse.status(clientResponse.statusCode());
bodyBuilder.contentType(MediaType.APPLICATION_STREAM_JSON);
return bodyBuilder.body((outputMessage, context) ->
outputMessage.writeAndFlushWith(
clientResponse.body(BodyExtractors.toDataBuffers())
.map(dataBuffer -> Mono.just(dataBuffer))
));}
);
回答1:
I've looked it up and it seems that by design, both Spring WebFlux's WebClient
and Reactor Netty HttpClient
are designed to first handle the request processing (sending the request body), and then reading the response body.
Other HTTP clients might allow this, but I think that in this case this is a way to link backpressure on both read/write operations and chain everything as a single reactive pipeline.
You might be looking for a message-oriented, bi-directionnal transport protocol with backpressure support. You can take a look at WebSockets (although you'll need to define your own message semantics there) or keep an eye on RSocket.
If you're just looking for an efficient reactive gateway, then Spring Cloud Gateway is your best bet since it's reactive all the way down and supports interesting additional features.
A few additional notes:
Spring WebFlux (at both client and server levels) uses Encoder
and Decoder
implementations, adapting to the message Content-Type. Some specific content-types, such as application/streaming+json
or text/event-stream
are implemented with streaming scenarios in mind. This means that encoders are writing messages as they come, separated with specific characters, and flushing on the network. Using regular media types such as application/octet-stream
or application/json
will not trigger that behavior. For those cases, proxies and intermediaries might buffer the message body and deliver bigger/smaller windows. This is why such mechanisms require a separator between messages and proper codecs.
As far as I understand, you're using HTTP 1.1 which is using a request/response mechanism - the HTTP spec doesn’t explicitly forbid te server from writing the response before reading the full request, but it does say that it has to read the full request body (or close the connection) no matter what. See https://tools.ietf.org/html/rfc7230#section-3.4
As always, you can request enhancements on https://jira.spring.io, although in this case, I think this is done by design.
回答2:
Just tested Jetty based WebClient implementation and it behaves as you expect. It can start reading response before all request content has been sent. It should have been released in Spring Framework 5.1 WebClient on Jetty new feature issue
来源:https://stackoverflow.com/questions/52113235/webclient-doesnt-read-response-until-request-write-is-completed