Skip to content

Commit 23e35c0

Browse files
committed
Handle Jackson InvalidDefinitionException with 5xx status in WebFlux
Issue: SPR-14925
1 parent c4e0d6c commit 23e35c0

File tree

5 files changed

+95
-6
lines changed

5 files changed

+95
-6
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.codec;
18+
19+
/**
20+
* Codec exception suitable for internal errors, like those not related to invalid data. It can be used to make sure
21+
* such error will produce a 5xx status code and not a 4xx one when reading HTTP messages for example.
22+
*
23+
* @author Sebastien Deleuze
24+
* @since 5.0
25+
*/
26+
@SuppressWarnings("serial")
27+
public class InternalCodecException extends CodecException {
28+
29+
public InternalCodecException(String msg) {
30+
super(msg);
31+
}
32+
33+
public InternalCodecException(String msg, Throwable cause) {
34+
super(msg, cause);
35+
}
36+
37+
}

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

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

24-
import org.springframework.core.codec.CodecException;
24+
import org.springframework.core.codec.InternalCodecException;
2525
import org.springframework.http.HttpStatus;
2626
import org.springframework.web.server.ResponseStatusException;
2727
import reactor.core.publisher.Flux;
@@ -112,10 +112,10 @@ private Throwable mapError(Throwable ex) {
112112
if (ex instanceof ResponseStatusException) {
113113
return ex;
114114
}
115-
else if (ex instanceof CodecException) {
116-
return new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to decode HTTP message", ex);
115+
else if (ex instanceof InternalCodecException) {
116+
return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to decode HTTP message", ex);
117117
}
118-
return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to decode HTTP message", ex);
118+
return new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to decode HTTP message", ex);
119119
}
120120

121121

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.fasterxml.jackson.databind.JavaType;
2525
import com.fasterxml.jackson.databind.ObjectMapper;
2626
import com.fasterxml.jackson.databind.ObjectReader;
27+
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
2728
import org.reactivestreams.Publisher;
2829
import reactor.core.publisher.Flux;
2930
import reactor.core.publisher.Mono;
@@ -34,6 +35,7 @@
3435
import org.springframework.core.io.buffer.DataBuffer;
3536
import org.springframework.core.io.buffer.DataBufferUtils;
3637
import org.springframework.http.codec.HttpMessageDecoder;
38+
import org.springframework.core.codec.InternalCodecException;
3739
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
3840
import org.springframework.http.server.reactive.ServerHttpRequest;
3941
import org.springframework.http.server.reactive.ServerHttpResponse;
@@ -113,6 +115,9 @@ private Flux<Object> decodeInternal(JsonObjectDecoder objectDecoder, Publisher<D
113115
DataBufferUtils.release(dataBuffer);
114116
return value;
115117
}
118+
catch (InvalidDefinitionException ex) {
119+
throw new InternalCodecException("Error while reading the data", ex);
120+
}
116121
catch (IOException ex) {
117122
throw new CodecException("Error while reading the data", ex);
118123
}

spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import org.springframework.core.ResolvableType;
3535
import org.springframework.core.codec.CodecException;
36+
import org.springframework.core.codec.InternalCodecException;
3637
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
3738
import org.springframework.core.io.buffer.DataBuffer;
3839
import org.springframework.http.codec.Pojo;
@@ -160,4 +161,44 @@ public void decodeEmptyBodyToMono() throws Exception {
160161
.verifyComplete();
161162
}
162163

164+
@Test
165+
public void invalidData() throws Exception {
166+
Flux<DataBuffer> source = Flux.just(stringBuffer( "{\"property1\":\"foo\""));
167+
ResolvableType elementType = forClass(BeanWithNoDefaultConstructor.class);
168+
Flux<Object> flux = new Jackson2JsonDecoder().decode(source, elementType, null, emptyMap());
169+
StepVerifier.create(flux).expectError(InternalCodecException.class);
170+
}
171+
172+
@Test
173+
public void noDefaultConstructor() throws Exception {
174+
Flux<DataBuffer> source = Flux.just(stringBuffer( "{\"property1\":\"foo\",\"property2\":\"bar\"}"));
175+
ResolvableType elementType = forClass(BeanWithNoDefaultConstructor.class);
176+
Flux<Object> flux = new Jackson2JsonDecoder().decode(source, elementType, null, emptyMap());
177+
StepVerifier
178+
.create(flux)
179+
.verifyErrorMatches(ex -> ex instanceof CodecException && !(ex instanceof InternalCodecException));
180+
}
181+
182+
183+
private static class BeanWithNoDefaultConstructor {
184+
185+
private final String property1;
186+
187+
private final String property2;
188+
189+
public BeanWithNoDefaultConstructor(String property1, String property2) {
190+
this.property1 = property1;
191+
this.property2 = property2;
192+
}
193+
194+
public String getProperty1() {
195+
return property1;
196+
}
197+
198+
public String getProperty2() {
199+
return property2;
200+
}
201+
202+
}
203+
163204
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.stream.Collectors;
2424

2525
import org.springframework.core.*;
26+
import org.springframework.core.codec.InternalCodecException;
27+
import org.springframework.http.HttpStatus;
2628
import org.springframework.web.server.ResponseStatusException;
2729
import reactor.core.publisher.Flux;
2830
import reactor.core.publisher.Mono;
@@ -150,8 +152,12 @@ protected Mono<Object> readBody(MethodParameter bodyParameter, boolean isBodyReq
150152
return Mono.error(new UnsupportedMediaTypeStatusException(mediaType, this.supportedMediaTypes));
151153
}
152154

153-
private ServerWebInputException getReadError(MethodParameter parameter, Throwable ex) {
154-
return new ServerWebInputException("Failed to read HTTP message", parameter, ex instanceof ResponseStatusException ? ex.getCause() : ex);
155+
private ResponseStatusException getReadError(MethodParameter parameter, Throwable ex) {
156+
Throwable cause = ex instanceof ResponseStatusException ? ex.getCause() : ex;
157+
158+
return cause instanceof InternalCodecException ?
159+
new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to read HTTP message", cause) :
160+
new ServerWebInputException("Failed to read HTTP message", parameter, cause);
155161
}
156162

157163
private ServerWebInputException getRequiredBodyError(MethodParameter parameter) {

0 commit comments

Comments
 (0)