-
Notifications
You must be signed in to change notification settings - Fork 38.5k
WebFlux controller successfully completes response if Flux emits error [SPR-16051] #20600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Ivan Pavlukhin commented No comments so far? This problem could lead to unexpected consequences in my opinion. |
Rossen Stoyanchev commented Sorry for the delay, somehow I missed this one. We intentionally delay committing the response until the first item, because there is still an opportunity to change to an error status. For example an I am not sure what you mean about the end chunk. What is the actual issue here? What should happen next is you should see an error message about the error in the server logs. However I did find an issue in Reactor Netty which simply sends an onComplete, and we complete quietly, rather than propagating the error signal which would log the error message. If you switch to another server, Tomcat/Jetty/Undertow you will see the error message. |
Ivan Pavlukhin commented Well. I will describe an actual story. We have a controller serving files from server's file system. If you wonder why we do not use simply public static Flux<ByteBuffer> read(ReadableByteChannel channel) {
return Flux.generate(
() -> channel,
(ch, sink) -> {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
if (ch.read(byteBuffer) >= 0) {
byteBuffer.flip();
sink.next(byteBuffer);
} else {
sink.complete();
}
} catch (Throwable ex) {
sink.error(ex);
}
return channel;
}
);
} Then after mapping to |
Rossen Stoyanchev commented Okay I understand now. To the best of my knowledge this is not something we can influence, i.e. what the server does with the underlying connection. You can try asking in reactor-netty for more details especially if you find that RFC text. Technically the server is fully aware of the error (well, after #231 is fixed) since it is left to propagate all the way. However I suspect the answer has to do with persistent connections. For what it's worth I verified the same behavior with other servers (Tomcat, Jetty, and Undertow). The only difference is that Tomcat produced a response with a Content-Length but still only partial data. BTW you should return FileSystemResource because we support a much more efficient, zero-copy file transfer on Netty. This is in |
Rossen Stoyanchev commented Resolving for now since I don't think there is anything we can do from our end. |
Ivan Pavlukhin commented Hi Rossen. Thanks for suggesting zero-copy approach. I walked around source code (I used version 5.0.2.RELEASE) with Jetty running under the hood this time and found some odd things. In @Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
ServerWebExchange exchange = createExchange(request, response);
return getDelegate().handle(exchange)
.onErrorResume(ex -> {
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
logHandleFailure(ex);
return Mono.empty();
})
.then(Mono.defer(response::setComplete));
} Stange thing here is @Override
public void onError(Throwable ex) {
runIfAsyncNotComplete(this.asyncContext, () -> {
logger.error("Could not complete request", ex);
HttpServletResponse response = (HttpServletResponse) this.asyncContext.getResponse();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
this.asyncContext.complete();
});
} And similar situation again |
Rossen Stoyanchev commented Yes you're right the error signal does stop in That said keep in mind the way For the other servers, we use our own Reactive Streams bridge (so onError is processed in AbstractListenerProcessor). If the error is an IOError the server will know about it but if it comes from the write publisher, I'm not sure what we can do, besides call Note also in this particular use case, i.e. sending a file, setting the Content-Length ahead of time is feasible and that should tip off the client that not all content was received. Returning a In general however, based on your comments, I think we need to double check the current behavior in case of an error when the response is committed and see if there is anything else we can do. I am going to re-open. |
Ivan Pavlukhin commented I experimented a bit with failing async servlet request after it is already committed. And I achieved expected result with calling Regarding reactor-netty. I spent some time debugging same issue with reactor-netty server. And I have doubts that |
Rossen Stoyanchev commented Did you see this ticket reactor/reactor-netty#231? |
Ivan Pavlukhin commented Yes I did. I will describe how I understand the story. Publisher produced by controller method is passed to following method: public final Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
return new ChannelSendOperator<>(body,
writePublisher -> doCommit(() -> writeWithInternal(writePublisher)));
} You say that publisher, returned from @Override
protected Mono<Void> writeWithInternal(Publisher<? extends DataBuffer> publisher) {
Publisher<ByteBuf> body = toByteBufs(publisher);
return this.response.send(body).then();
} But what happens inside In my mind it seems that calling |
Ivan Pavlukhin commented But perhaps I am not right and |
Rossen Stoyanchev commented One way or another we need to pass the error signal to the writeSubscriber (i.e. server). That will propagate the error to the completionSubscriber (effectively the remaining request processing chain). We don't want to shortcircuit this process and also the Reactive Streams spec forbids making any more calls on a Subscriber once it's complete (rule 1.7). |
Ivan Pavlukhin commented Sounds reasonable. During debugging I got an impression that |
Rossen Stoyanchev commented Scheduling tentatively for 5.0.3 (as I had intended to) while we investigate the options. |
Rossen Stoyanchev commented I have committed a fix and verified it works as expected for Tomcat, Jetty, and Undertow. For Reactor Netty I need to wait until issue #231 is resolved to confirm the behavior. I will resolve the ticket for the time being since we now do make sure that any late error (after the response is committed) is propagated to the server. That's as much as we can do from our end. However I will test again when #231 is fixed, which should be soon, and comment here. Note also that I created an additional issue #237 since the |
Ivan Pavlukhin commented Thanks! |
Rossen Stoyanchev commented Oops forgot this, but once #213 is fixed there is also issue #101 (scheduled for 0.8). |
Ivan Pavlukhin opened SPR-16051 and commented
I observed such behaviour with reactor-netty HttpServer and ReactorHttpHandlerAdapter.
Actually if error is first emitted event then will controller return HTTP error code. But if some event was emitted before error, then response will complete successfully (ending with zero chunk).
See reproducing gist https://gist.github.com/TanyaGaleyev/83ad550cf7221ef84a3bfe6df26eec3c
I suppose that proper behaviour here is to close connection when error occurs without writing end chunk.
Affects: 5.0.2
The text was updated successfully, but these errors were encountered: