Improve error handling when response is committed

ResponseStatusExceptionHandler lets the error through if it can't
change the status while HttpWebHandlerAdapter logs a more helpful
message (including status code) but without a full stack trace.

Issue: SPR-16231
This commit is contained in:
Rossen Stoyanchev 2017-11-29 12:39:06 -05:00
parent 873cb4e58b
commit dd0d270ba2
3 changed files with 19 additions and 14 deletions

View File

@ -71,7 +71,7 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
* <p>TODO:
* This definition is currently duplicated between HttpWebHandlerAdapter
* and AbstractSockJsSession. It is a candidate for a common utility class.
* @see #indicatesDisconnectedClient(Throwable)
* @see #isDisconnectedClientError(Throwable)
*/
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS =
new HashSet<>(Arrays.asList("ClientAbortException", "EOFException", "EofException"));
@ -158,8 +158,7 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
ServerWebExchange exchange = createExchange(request, response);
return getDelegate().handle(exchange)
.onErrorResume(ex -> {
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
logHandleFailure(ex);
handleFailure(response, ex);
return Mono.empty();
})
.then(Mono.defer(response::setComplete));
@ -170,8 +169,9 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
getCodecConfigurer(), getLocaleContextResolver());
}
private void logHandleFailure(Throwable ex) {
if (indicatesDisconnectedClient(ex)) {
private void handleFailure(ServerHttpResponse response, Throwable ex) {
boolean statusCodeChanged = response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
if (isDisconnectedClientError(ex)) {
if (disconnectedClientLogger.isTraceEnabled()) {
disconnectedClientLogger.trace("Looks like the client has gone away", ex);
}
@ -181,12 +181,16 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
"' to TRACE level.)");
}
}
else if (!statusCodeChanged) {
logger.error("Unhandled failure: " + ex.getMessage() + ", " +
"response already committed with status=" + response.getStatusCode());
}
else {
logger.error("Failed to handle request", ex);
}
}
private boolean indicatesDisconnectedClient(Throwable ex) {
private boolean isDisconnectedClientError(Throwable ex) {
String message = NestedExceptionUtils.getMostSpecificCause(ex).getMessage();
message = (message != null ? message.toLowerCase() : "");
String className = ex.getClass().getSimpleName();

View File

@ -20,6 +20,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
@ -39,10 +40,11 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof ResponseStatusException) {
// Response may be committed but we'll try..
logger.debug(ex.getMessage());
exchange.getResponse().setStatusCode(((ResponseStatusException) ex).getStatus());
return exchange.getResponse().setComplete();
HttpStatus status = ((ResponseStatusException) ex).getStatus();
if (exchange.getResponse().setStatusCode(status)) {
logger.trace(ex.getMessage());
return exchange.getResponse().setComplete();
}
}
return Mono.error(ex);
}

View File

@ -59,10 +59,9 @@ public class ResponseStatusExceptionHandlerTests {
public void responseCommitted() throws Exception {
Throwable ex = new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Oops");
this.exchange.getResponse().setStatusCode(HttpStatus.CREATED);
this.exchange.getResponse().setComplete()
.then(this.handler.handle(this.exchange, ex))
.block(Duration.ofSeconds(5));
assertEquals(HttpStatus.CREATED, this.exchange.getResponse().getStatusCode());
Mono<Void> mono = this.exchange.getResponse().setComplete()
.then(this.handler.handle(this.exchange, ex));
StepVerifier.create(mono).consumeErrorWith(actual -> assertSame(ex, actual)).verify();
}
}