diff --git a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java index 159eb503af..ab9a92a3b6 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java @@ -21,6 +21,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.springframework.core.codec.CodecException; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -85,7 +88,9 @@ public class DecoderHttpMessageReader implements HttpMessageReader { Map hints) { MediaType contentType = getContentType(message); - return this.decoder.decode(message.getBody(), elementType, contentType, hints); + return this.decoder + .decode(message.getBody(), elementType, contentType, hints) + .mapError(this::mapError); } @Override @@ -93,7 +98,9 @@ public class DecoderHttpMessageReader implements HttpMessageReader { Map hints) { MediaType contentType = getContentType(message); - return this.decoder.decodeToMono(message.getBody(), elementType, contentType, hints); + return this.decoder + .decodeToMono(message.getBody(), elementType, contentType, hints) + .mapError(this::mapError); } private MediaType getContentType(HttpMessage inputMessage) { @@ -101,6 +108,16 @@ public class DecoderHttpMessageReader implements HttpMessageReader { return (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM); } + private Throwable mapError(Throwable ex) { + if (ex instanceof ResponseStatusException) { + return ex; + } + else if (ex instanceof CodecException) { + return new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to decode HTTP message", ex); + } + return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to decode HTTP message", ex); + } + // Server-side only... diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 52258f4b1c..b93d986256 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import org.reactivestreams.Publisher; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -95,8 +97,9 @@ public class EncoderHttpMessageWriter implements HttpMessageWriter { MediaType contentType = updateContentType(message, mediaType); - Flux body = this.encoder.encode( - inputStream, message.bufferFactory(), elementType, contentType, hints); + Flux body = this.encoder + .encode(inputStream, message.bufferFactory(), elementType, contentType, hints) + .mapError(this::mapError); return isStreamingMediaType(contentType) ? message.writeAndFlushWith(body.map(Flux::just)) : @@ -135,6 +138,13 @@ public class EncoderHttpMessageWriter implements HttpMessageWriter { .anyMatch(contentType::isCompatibleWith); } + private Throwable mapError(Throwable ex) { + if (ex instanceof ResponseStatusException) { + return ex; + } + return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to encode HTTP message", ex); + } + // Server side only... diff --git a/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java b/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java index cb08f28885..a9797287cb 100644 --- a/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java +++ b/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java @@ -16,6 +16,8 @@ package org.springframework.web.server.handler; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import org.springframework.web.server.ResponseStatusException; @@ -26,14 +28,20 @@ import org.springframework.web.server.WebExceptionHandler; * Handle {@link ResponseStatusException} by setting the response status. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze * @since 5.0 */ public class ResponseStatusExceptionHandler implements WebExceptionHandler { + private static final Log logger = LogFactory.getLog(ResponseStatusExceptionHandler.class); + @Override public Mono handle(ServerWebExchange exchange, Throwable ex) { if (ex instanceof ResponseStatusException) { exchange.getResponse().setStatusCode(((ResponseStatusException) ex).getStatus()); + if (ex.getMessage() != null) { + logger.error(ex.getMessage()); + } return Mono.empty(); } return Mono.error(ex); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java index 072eee0e3e..1993f8fe92 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java @@ -237,6 +237,9 @@ public abstract class RouterFunctions { .otherwise(ResponseStatusException.class, ex -> { exchange.getResponse().setStatusCode(ex.getStatus()); + if (ex.getMessage() != null) { + logger.error(ex.getMessage()); + } return Mono.empty(); }); }); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index b450bf727a..cb6f45c72c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -22,14 +22,11 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.springframework.core.*; +import org.springframework.web.server.ResponseStatusException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import org.springframework.core.Conventions; -import org.springframework.core.MethodParameter; -import org.springframework.core.ReactiveAdapter; -import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; @@ -154,7 +151,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho } private ServerWebInputException getReadError(MethodParameter parameter, Throwable ex) { - return new ServerWebInputException("Failed to read HTTP message", parameter, ex); + return new ServerWebInputException("Failed to read HTTP message", parameter, ex instanceof ResponseStatusException ? ex.getCause() : ex); } private ServerWebInputException getRequiredBodyError(MethodParameter parameter) { diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java index 6c013cd074..8c32c02931 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java @@ -102,7 +102,7 @@ public class DispatcherHandlerErrorTests { Mono publisher = this.dispatcherHandler.handle(exchange); StepVerifier.create(publisher) - .consumeErrorWith(error -> assertSame(EXCEPTION, error)) + .consumeErrorWith(error -> assertSame(EXCEPTION, error.getCause())) .verify(); }