Affects: 5.3.1
Motivation We have an internal website for our administrators, and if our website sends an illegal request to the server, the server should respond with both the status code and the error details so that we can know what happens. But ResponseStatusExceptionHandler just sends the status code to clients currently.
Solution Allow developers to choose whether to respond with the error message to clients.
Comment From: rstoyanchev
You can use a ResponseEntityExceptionHandler to define the format and content of an error response which is application specific. Also Spring Boot provides such support out of the box.
Comment From: JamesChenX
@rstoyanchev ResponseEntityExceptionHandler belongs to WebMVC but our server uses WebFlux so we cannot use ResponseEntityExceptionHandler.
And the problem is that we have our own WebExceptionHandler instance, but its priority is lower than ResponseStatusExceptionHandler. If ResponseStatusExceptionHandler thinks that it can handle the exception, it won't pass the exception to our own handler so we cannot customize the response to clients.
The following code snippet is how ResponseStatusExceptionHandler creates an HTTP response from an exception and the reason why it won't pass the exception to our own handler.
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (!updateResponse(exchange.getResponse(), ex)) { // updateResponse returns true if it can handle the exception. As a result, we cannot get the exception and cannot customize the response.
return Mono.error(ex);
}
// Mirrors AbstractHandlerExceptionResolver in spring-webmvc
String logPrefix = exchange.getLogPrefix();
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(logPrefix + formatError(ex, exchange.getRequest()), ex);
}
else if (logger.isDebugEnabled()) {
logger.debug(logPrefix + formatError(ex, exchange.getRequest()));
}
return exchange.getResponse().setComplete(); // We, developers, cannot update the response
}
private boolean updateResponse(ServerHttpResponse response, Throwable ex) {
boolean result = false;
HttpStatus httpStatus = determineStatus(ex);
int code = (httpStatus != null ? httpStatus.value() : determineRawStatusCode(ex));
if (code != -1) {
if (response.setRawStatusCode(code)) {
if (ex instanceof ResponseStatusException) {
((ResponseStatusException) ex).getResponseHeaders()
.forEach((name, values) ->
values.forEach(value -> response.getHeaders().add(name, value)));
}
result = true;
}
}
else {
Throwable cause = ex.getCause();
if (cause != null) {
result = updateResponse(response, cause);
}
}
return result;
}
Comment From: rstoyanchev
Right, this is in WebFlux. By default we register the ResponseStatusExceptionHandler
at 0. You could register your own WebExceptionHandler
at -1.
Comment From: JamesChenX
Thanks for your help. We can handle the exception by our own handler after we added "@Order(Ordered.HIGHEST_PRECEDENCE)" to our own ErrorWebExceptionHandler.
And I didn't notice that the default exception handler is registered in:
public class WebFluxConfigurationSupport implements ApplicationContextAware {
...
...
@Bean
@Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {
return new WebFluxResponseStatusExceptionHandler();
}
}