Refine encoding/decoding exception handling

Starting with removing a package cycle on the use of
ResponseStatusException in the codec package, this commit generally
refines codec exception handling.

The new [Encoding|Decoding]Exception mirror the existing
HttpMessageNot[Readable|Writable]Exception and are used similarly
especially to differentiate betwen 400 and 500 errors when parsing
server request body content.

The commit also aligns some of the exception handling of JSON and XML
on the WebFlux side with that on the Spring MVC side.

Issue: SPR-15516
This commit is contained in:
Rossen Stoyanchev
2017-05-05 14:22:27 -04:00
parent d7e54cea84
commit 83e0e1604a
12 changed files with 147 additions and 87 deletions

View File

@@ -21,9 +21,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.codec.InternalCodecException;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -88,9 +85,7 @@ public class DecoderHttpMessageReader<T> implements HttpMessageReader<T> {
Map<String, Object> hints) {
MediaType contentType = getContentType(message);
return this.decoder
.decode(message.getBody(), elementType, contentType, hints)
.onErrorMap(this::mapError);
return this.decoder.decode(message.getBody(), elementType, contentType, hints);
}
@Override
@@ -98,9 +93,7 @@ public class DecoderHttpMessageReader<T> implements HttpMessageReader<T> {
Map<String, Object> hints) {
MediaType contentType = getContentType(message);
return this.decoder
.decodeToMono(message.getBody(), elementType, contentType, hints)
.onErrorMap(this::mapError);
return this.decoder.decodeToMono(message.getBody(), elementType, contentType, hints);
}
private MediaType getContentType(HttpMessage inputMessage) {
@@ -108,16 +101,6 @@ public class DecoderHttpMessageReader<T> implements HttpMessageReader<T> {
return (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM);
}
private Throwable mapError(Throwable ex) {
if (ex instanceof ResponseStatusException) {
return ex;
}
else if (ex instanceof InternalCodecException) {
return new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Failed to decode HTTP message", ex);
}
return new ResponseStatusException(HttpStatus.BAD_REQUEST, "Failed to decode HTTP message", ex);
}
// Server-side only...

View File

@@ -22,14 +22,13 @@ 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;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Encoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpOutputMessage;
import org.springframework.http.server.reactive.ServerHttpRequest;
@@ -97,10 +96,9 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
Map<String, Object> hints) {
MediaType contentType = updateContentType(message, mediaType);
DataBufferFactory factory = message.bufferFactory();
Flux<DataBuffer> body = this.encoder
.encode(inputStream, message.bufferFactory(), elementType, contentType, hints)
.onErrorMap(this::mapError);
Flux<DataBuffer> body = this.encoder.encode(inputStream, factory, elementType, contentType, hints);
return isStreamingMediaType(contentType) ?
message.writeAndFlushWith(body.map(Flux::just)) :
@@ -139,13 +137,6 @@ public class EncoderHttpMessageWriter<T> implements HttpMessageWriter<T> {
.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...

View File

@@ -21,6 +21,7 @@ import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
@@ -32,10 +33,10 @@ import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.CodecException;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.codec.HttpMessageDecoder;
import org.springframework.core.codec.InternalCodecException;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
@@ -116,10 +117,13 @@ public class Jackson2JsonDecoder extends Jackson2CodecSupport implements HttpMes
return value;
}
catch (InvalidDefinitionException ex) {
throw new InternalCodecException("Error while reading the data", ex);
throw new CodecException("Type definition error: " + ex.getMessage(), ex);
}
catch (JsonProcessingException ex) {
throw new DecodingException("JSON parse error: " + ex.getMessage(), ex);
}
catch (IOException ex) {
throw new CodecException("Error while reading the data", ex);
throw new CodecException("I/O error while reading: " + ex.getMessage(), ex);
}
});
}

View File

@@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.PrettyPrinter;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
@@ -39,6 +40,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.CodecException;
import org.springframework.core.codec.EncodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
@@ -164,8 +166,11 @@ public class Jackson2JsonEncoder extends Jackson2CodecSupport implements HttpMes
try {
writer.writeValue(outputStream, value);
}
catch (JsonProcessingException ex) {
throw new EncodingException("JSON encoding error: " + ex.getMessage(), ex);
}
catch (IOException ex) {
throw new CodecException("Error while writing the data", ex);
throw new CodecException("I/O error while writing: " + ex.getMessage(), ex);
}
return buffer;

View File

@@ -37,7 +37,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.AbstractDecoder;
import org.springframework.core.codec.CodecException;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
@@ -216,7 +216,7 @@ public class Jaxb2XmlDecoder extends AbstractDecoder<Object> {
}
}
catch (JAXBException ex) {
throw new CodecException(ex.getMessage(), ex);
throw new DecodingException(ex.getMessage(), ex);
}
}

View File

@@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Map;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@@ -28,6 +29,8 @@ import reactor.core.publisher.Flux;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.AbstractSingleValueEncoder;
import org.springframework.core.codec.CodecException;
import org.springframework.core.codec.EncodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.util.ClassUtils;
@@ -73,13 +76,16 @@ public class Jaxb2XmlEncoder extends AbstractSingleValueEncoder<Object> {
OutputStream outputStream = buffer.asOutputStream();
Class<?> clazz = ClassUtils.getUserClass(value);
Marshaller marshaller = jaxbContexts.createMarshaller(clazz);
marshaller
.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
marshaller.marshal(value, outputStream);
return Flux.just(buffer);
}
catch (UnmarshalException ex) {
return Flux.error(new EncodingException(
"Could not unmarshal to [" + value.getClass() + "]: " + ex.getMessage(), ex));
}
catch (JAXBException ex) {
return Flux.error(ex);
return Flux.error(new CodecException("Could not instantiate JAXBContext: " + ex.getMessage(), ex));
}
}