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:
parent
873cb4e58b
commit
dd0d270ba2
|
|
@ -71,7 +71,7 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||||
* <p>TODO:
|
* <p>TODO:
|
||||||
* This definition is currently duplicated between HttpWebHandlerAdapter
|
* This definition is currently duplicated between HttpWebHandlerAdapter
|
||||||
* and AbstractSockJsSession. It is a candidate for a common utility class.
|
* 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 =
|
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS =
|
||||||
new HashSet<>(Arrays.asList("ClientAbortException", "EOFException", "EofException"));
|
new HashSet<>(Arrays.asList("ClientAbortException", "EOFException", "EofException"));
|
||||||
|
|
@ -158,8 +158,7 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||||
ServerWebExchange exchange = createExchange(request, response);
|
ServerWebExchange exchange = createExchange(request, response);
|
||||||
return getDelegate().handle(exchange)
|
return getDelegate().handle(exchange)
|
||||||
.onErrorResume(ex -> {
|
.onErrorResume(ex -> {
|
||||||
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
handleFailure(response, ex);
|
||||||
logHandleFailure(ex);
|
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
})
|
})
|
||||||
.then(Mono.defer(response::setComplete));
|
.then(Mono.defer(response::setComplete));
|
||||||
|
|
@ -170,8 +169,9 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||||
getCodecConfigurer(), getLocaleContextResolver());
|
getCodecConfigurer(), getLocaleContextResolver());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logHandleFailure(Throwable ex) {
|
private void handleFailure(ServerHttpResponse response, Throwable ex) {
|
||||||
if (indicatesDisconnectedClient(ex)) {
|
boolean statusCodeChanged = response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
if (isDisconnectedClientError(ex)) {
|
||||||
if (disconnectedClientLogger.isTraceEnabled()) {
|
if (disconnectedClientLogger.isTraceEnabled()) {
|
||||||
disconnectedClientLogger.trace("Looks like the client has gone away", ex);
|
disconnectedClientLogger.trace("Looks like the client has gone away", ex);
|
||||||
}
|
}
|
||||||
|
|
@ -181,12 +181,16 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa
|
||||||
"' to TRACE level.)");
|
"' to TRACE level.)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (!statusCodeChanged) {
|
||||||
|
logger.error("Unhandled failure: " + ex.getMessage() + ", " +
|
||||||
|
"response already committed with status=" + response.getStatusCode());
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
logger.error("Failed to handle request", ex);
|
logger.error("Failed to handle request", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean indicatesDisconnectedClient(Throwable ex) {
|
private boolean isDisconnectedClientError(Throwable ex) {
|
||||||
String message = NestedExceptionUtils.getMostSpecificCause(ex).getMessage();
|
String message = NestedExceptionUtils.getMostSpecificCause(ex).getMessage();
|
||||||
message = (message != null ? message.toLowerCase() : "");
|
message = (message != null ? message.toLowerCase() : "");
|
||||||
String className = ex.getClass().getSimpleName();
|
String className = ex.getClass().getSimpleName();
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
import org.springframework.web.server.WebExceptionHandler;
|
import org.springframework.web.server.WebExceptionHandler;
|
||||||
|
|
@ -39,10 +40,11 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler {
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||||
if (ex instanceof ResponseStatusException) {
|
if (ex instanceof ResponseStatusException) {
|
||||||
// Response may be committed but we'll try..
|
HttpStatus status = ((ResponseStatusException) ex).getStatus();
|
||||||
logger.debug(ex.getMessage());
|
if (exchange.getResponse().setStatusCode(status)) {
|
||||||
exchange.getResponse().setStatusCode(((ResponseStatusException) ex).getStatus());
|
logger.trace(ex.getMessage());
|
||||||
return exchange.getResponse().setComplete();
|
return exchange.getResponse().setComplete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Mono.error(ex);
|
return Mono.error(ex);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,9 @@ public class ResponseStatusExceptionHandlerTests {
|
||||||
public void responseCommitted() throws Exception {
|
public void responseCommitted() throws Exception {
|
||||||
Throwable ex = new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Oops");
|
Throwable ex = new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Oops");
|
||||||
this.exchange.getResponse().setStatusCode(HttpStatus.CREATED);
|
this.exchange.getResponse().setStatusCode(HttpStatus.CREATED);
|
||||||
this.exchange.getResponse().setComplete()
|
Mono<Void> mono = this.exchange.getResponse().setComplete()
|
||||||
.then(this.handler.handle(this.exchange, ex))
|
.then(this.handler.handle(this.exchange, ex));
|
||||||
.block(Duration.ofSeconds(5));
|
StepVerifier.create(mono).consumeErrorWith(actual -> assertSame(ex, actual)).verify();
|
||||||
assertEquals(HttpStatus.CREATED, this.exchange.getResponse().getStatusCode());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue