Skip to content

Commit d098a4b

Browse files
committed
Make CodecException handling consistent
This commit makes CodecException handling consistent between functional and annotation-based APIs. It now returns by default 4xx status code for decoding error and 5xx for encoding error + print the error reason in logs without the full stack trace in both variants. Issue: SPR-15355
1 parent 15b5dd9 commit d098a4b

File tree

6 files changed

+46
-11
lines changed

6 files changed

+46
-11
lines changed

spring-web/src/main/java/org/springframework/http/codec/DecoderHttpMessageReader.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
import java.util.List;
2222
import java.util.Map;
2323

24+
import org.springframework.core.codec.CodecException;
25+
import org.springframework.http.HttpStatus;
26+
import org.springframework.web.server.ResponseStatusException;
2427
import reactor.core.publisher.Flux;
2528
import reactor.core.publisher.Mono;
2629

@@ -85,22 +88,36 @@ public Flux<T> read(ResolvableType elementType, ReactiveHttpInputMessage message
8588
Map<String, Object> hints) {
8689

8790
MediaType contentType = getContentType(message);
88-
return this.decoder.decode(message.getBody(), elementType, contentType, hints);
91+
return this.decoder
92+
.decode(message.getBody(), elementType, contentType, hints)
93+
.mapError(this::mapError);
8994
}
9095

9196
@Override
9297
public Mono<T> readMono(ResolvableType elementType, ReactiveHttpInputMessage message,
9398
Map<String, Object> hints) {
9499

95100
MediaType contentType = getContentType(message);
96-
return this.decoder.decodeToMono(message.getBody(), elementType, contentType, hints);
101+
return this.decoder
102+
.decodeToMono(message.getBody(), elementType, contentType, hints)
103+
.mapError(this::mapError);
97104
}
98105

99106
private MediaType getContentType(HttpMessage inputMessage) {
100107
MediaType contentType = inputMessage.getHeaders().getContentType();
101108
return (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM);
102109
}
103110

111+
private Throwable mapError(Throwable ex) {
112+
if (ex instanceof ResponseStatusException) {
113+
return ex;
114+
}
115+
else if (ex instanceof CodecException) {
116+
return new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to decode HTTP message", ex);
117+
}
118+
return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to decode HTTP message", ex);
119+
}
120+
104121

105122
// Server-side only...
106123

spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.Map;
2323

2424
import org.reactivestreams.Publisher;
25+
import org.springframework.http.HttpStatus;
26+
import org.springframework.web.server.ResponseStatusException;
2527
import reactor.core.publisher.Flux;
2628
import reactor.core.publisher.Mono;
2729

@@ -95,8 +97,9 @@ public Mono<Void> write(Publisher<? extends T> inputStream, ResolvableType eleme
9597

9698
MediaType contentType = updateContentType(message, mediaType);
9799

98-
Flux<DataBuffer> body = this.encoder.encode(
99-
inputStream, message.bufferFactory(), elementType, contentType, hints);
100+
Flux<DataBuffer> body = this.encoder
101+
.encode(inputStream, message.bufferFactory(), elementType, contentType, hints)
102+
.mapError(this::mapError);
100103

101104
return isStreamingMediaType(contentType) ?
102105
message.writeAndFlushWith(body.map(Flux::just)) :
@@ -135,6 +138,13 @@ private boolean isStreamingMediaType(MediaType contentType) {
135138
.anyMatch(contentType::isCompatibleWith);
136139
}
137140

141+
private Throwable mapError(Throwable ex) {
142+
if (ex instanceof ResponseStatusException) {
143+
return ex;
144+
}
145+
return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to encode HTTP message", ex);
146+
}
147+
138148

139149
// Server side only...
140150

spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.web.server.handler;
1818

19+
import org.apache.commons.logging.Log;
20+
import org.apache.commons.logging.LogFactory;
1921
import reactor.core.publisher.Mono;
2022

2123
import org.springframework.web.server.ResponseStatusException;
@@ -26,14 +28,20 @@
2628
* Handle {@link ResponseStatusException} by setting the response status.
2729
*
2830
* @author Rossen Stoyanchev
31+
* @author Sebastien Deleuze
2932
* @since 5.0
3033
*/
3134
public class ResponseStatusExceptionHandler implements WebExceptionHandler {
3235

36+
private static final Log logger = LogFactory.getLog(ResponseStatusExceptionHandler.class);
37+
3338
@Override
3439
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
3540
if (ex instanceof ResponseStatusException) {
3641
exchange.getResponse().setStatusCode(((ResponseStatusException) ex).getStatus());
42+
if (ex.getMessage() != null) {
43+
logger.error(ex.getMessage());
44+
}
3745
return Mono.empty();
3846
}
3947
return Mono.error(ex);

spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RouterFunctions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ public static HttpWebHandlerAdapter toHttpHandler(RouterFunction<?> routerFuncti
237237
.otherwise(ResponseStatusException.class,
238238
ex -> {
239239
exchange.getResponse().setStatusCode(ex.getStatus());
240+
if (ex.getMessage() != null) {
241+
logger.error(ex.getMessage());
242+
}
240243
return Mono.empty();
241244
});
242245
});

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,11 @@
2222
import java.util.Map;
2323
import java.util.stream.Collectors;
2424

25+
import org.springframework.core.*;
26+
import org.springframework.web.server.ResponseStatusException;
2527
import reactor.core.publisher.Flux;
2628
import reactor.core.publisher.Mono;
2729

28-
import org.springframework.core.Conventions;
29-
import org.springframework.core.MethodParameter;
30-
import org.springframework.core.ReactiveAdapter;
31-
import org.springframework.core.ReactiveAdapterRegistry;
32-
import org.springframework.core.ResolvableType;
3330
import org.springframework.core.annotation.AnnotationUtils;
3431
import org.springframework.http.MediaType;
3532
import org.springframework.http.codec.HttpMessageReader;
@@ -154,7 +151,7 @@ protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyReq
154151
}
155152

156153
private ServerWebInputException getReadError(MethodParameter parameter, Throwable ex) {
157-
return new ServerWebInputException("Failed to read HTTP message", parameter, ex);
154+
return new ServerWebInputException("Failed to read HTTP message", parameter, ex instanceof ResponseStatusException ? ex.getCause() : ex);
158155
}
159156

160157
private ServerWebInputException getRequiredBodyError(MethodParameter parameter) {

spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerErrorTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public void controllerReturnsMonoError() throws Exception {
102102
Mono<Void> publisher = this.dispatcherHandler.handle(exchange);
103103

104104
StepVerifier.create(publisher)
105-
.consumeErrorWith(error -> assertSame(EXCEPTION, error))
105+
.consumeErrorWith(error -> assertSame(EXCEPTION, error.getCause()))
106106
.verify();
107107
}
108108

0 commit comments

Comments
 (0)