Use ContentTypeResolver for content negotiation
This commit is contained in:
parent
2292e46b04
commit
37404d081e
|
@ -42,6 +42,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.reactive.HandlerResultHandler;
|
||||
import org.springframework.web.reactive.accept.ContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
|
@ -64,23 +66,44 @@ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered
|
|||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private final ContentTypeResolver contentTypeResolver;
|
||||
|
||||
private final List<MediaType> supportedMediaTypes;
|
||||
|
||||
private int order = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor with message converters and conversion service.
|
||||
* Constructor with message converters and a {@code ConversionService} only
|
||||
* and creating a {@link HeaderContentTypeResolver}, i.e. using Accept header
|
||||
* to determine the requested content type.
|
||||
*
|
||||
* @param converters converters for writing the response body with
|
||||
* @param conversionService for converting to Flux and Mono from other reactive types
|
||||
*/
|
||||
public ResponseBodyResultHandler(List<HttpMessageConverter<?>> converters,
|
||||
ConversionService conversionService) {
|
||||
|
||||
this(converters, conversionService, new HeaderContentTypeResolver());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with message converters, a {@code ConversionService}, and a
|
||||
* {@code ContentTypeResolver}.
|
||||
*
|
||||
* @param messageConverters converters for writing the response body with
|
||||
* @param conversionService for converting to Flux and Mono from other reactive types
|
||||
*/
|
||||
public ResponseBodyResultHandler(List<HttpMessageConverter<?>> messageConverters,
|
||||
ConversionService conversionService) {
|
||||
ConversionService conversionService, ContentTypeResolver contentTypeResolver) {
|
||||
|
||||
Assert.notEmpty(messageConverters, "At least one message converter is required.");
|
||||
Assert.notNull(conversionService, "'conversionService' is required.");
|
||||
Assert.notNull(contentTypeResolver, "'contentTypeResolver' is required.");
|
||||
|
||||
this.messageConverters = messageConverters;
|
||||
this.conversionService = conversionService;
|
||||
this.contentTypeResolver = contentTypeResolver;
|
||||
this.supportedMediaTypes = initSupportedMediaTypes(messageConverters);
|
||||
}
|
||||
|
||||
|
@ -149,10 +172,13 @@ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered
|
|||
}
|
||||
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
List<MediaType> compatibleMediaTypes = getCompatibleMediaTypes(request, elementType);
|
||||
List<MediaType> compatibleMediaTypes = getCompatibleMediaTypes(exchange, elementType);
|
||||
if (compatibleMediaTypes.isEmpty()) {
|
||||
List<MediaType> supported = getProducibleMediaTypes(elementType);
|
||||
return Mono.error(new NotAcceptableStatusException(supported));
|
||||
if (result.getReturnValue().isPresent()) {
|
||||
List<MediaType> mediaTypes = getProducibleMediaTypes(elementType);
|
||||
return Mono.error(new NotAcceptableStatusException(mediaTypes));
|
||||
}
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
MediaType bestMediaType = selectBestMediaType(compatibleMediaTypes);
|
||||
|
@ -167,10 +193,10 @@ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered
|
|||
return Mono.error(new NotAcceptableStatusException(this.supportedMediaTypes));
|
||||
}
|
||||
|
||||
private List<MediaType> getCompatibleMediaTypes(ServerHttpRequest request,
|
||||
private List<MediaType> getCompatibleMediaTypes(ServerWebExchange exchange,
|
||||
ResolvableType elementType) {
|
||||
|
||||
List<MediaType> acceptableMediaTypes = getAcceptableMediaTypes(request);
|
||||
List<MediaType> acceptableMediaTypes = getAcceptableMediaTypes(exchange);
|
||||
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(elementType);
|
||||
|
||||
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<>();
|
||||
|
@ -187,8 +213,8 @@ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered
|
|||
return result;
|
||||
}
|
||||
|
||||
private List<MediaType> getAcceptableMediaTypes(ServerHttpRequest request) {
|
||||
List<MediaType> mediaTypes = request.getHeaders().getAccept();
|
||||
private List<MediaType> getAcceptableMediaTypes(ServerWebExchange exchange) {
|
||||
List<MediaType> mediaTypes = this.contentTypeResolver.resolveMediaTypes(exchange);
|
||||
return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
|
||||
}
|
||||
|
||||
|
|
|
@ -406,14 +406,11 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
|
|||
|
||||
@Bean
|
||||
public ResponseBodyResultHandler responseBodyResultHandler() {
|
||||
List<HttpMessageConverter<?>> converters =
|
||||
Arrays.asList(new ResourceHttpMessageConverter(),
|
||||
new CodecHttpMessageConverter<ByteBuffer>(
|
||||
new ByteBufferEncoder(), new ByteBufferDecoder()),
|
||||
new CodecHttpMessageConverter<String>(new StringEncoder(),
|
||||
new StringDecoder()),
|
||||
new CodecHttpMessageConverter<Object>(
|
||||
new JacksonJsonEncoder(), new JacksonJsonDecoder()));
|
||||
List<HttpMessageConverter<?>> converters = Arrays.asList(
|
||||
new ResourceHttpMessageConverter(),
|
||||
new CodecHttpMessageConverter<>(new ByteBufferEncoder(), new ByteBufferDecoder()),
|
||||
new CodecHttpMessageConverter<>(new StringEncoder(), new StringDecoder()),
|
||||
new CodecHttpMessageConverter<>(new JacksonJsonEncoder(), new JacksonJsonDecoder()));
|
||||
ResponseBodyResultHandler resultHandler =
|
||||
new ResponseBodyResultHandler(converters, conversionService());
|
||||
resultHandler.setOrder(1);
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.web.reactive.result.method.annotation;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -25,23 +27,36 @@ import org.reactivestreams.Publisher;
|
|||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.support.JacksonJsonEncoder;
|
||||
import org.springframework.core.codec.support.StringEncoder;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.reactive.CodecHttpMessageConverter;
|
||||
import org.springframework.http.converter.reactive.HttpMessageConverter;
|
||||
import org.springframework.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.MockServerHttpResponse;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.ui.ExtendedModelMap;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.reactive.HandlerResult;
|
||||
import org.springframework.web.reactive.HandlerResultHandler;
|
||||
import org.springframework.web.reactive.accept.ContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.FixedContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.HeaderContentTypeResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ResponseBodyResultHandler}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
|
@ -50,7 +65,7 @@ public class ResponseBodyResultHandlerTests {
|
|||
|
||||
@Test
|
||||
public void supports() throws NoSuchMethodException {
|
||||
ResponseBodyResultHandler handler = createResultHandler(new StringEncoder());
|
||||
ResponseBodyResultHandler handler = createHandler(new StringEncoder());
|
||||
TestController controller = new TestController();
|
||||
|
||||
HandlerMethod hm = new HandlerMethod(controller, TestController.class.getMethod("notAnnotated"));
|
||||
|
@ -68,18 +83,42 @@ public class ResponseBodyResultHandlerTests {
|
|||
|
||||
@Test
|
||||
public void defaultOrder() throws Exception {
|
||||
ResponseBodyResultHandler handler = createResultHandler(new StringEncoder());
|
||||
ResponseBodyResultHandler handler = createHandler(new StringEncoder());
|
||||
assertEquals(0, handler.getOrder());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentTypeResolver() throws Exception {
|
||||
MediaType contentType = MediaType.APPLICATION_JSON_UTF8;
|
||||
ContentTypeResolver resolver = new FixedContentTypeResolver(contentType);
|
||||
HandlerResultHandler handler = createHandler(resolver, new StringEncoder(), new JacksonJsonEncoder());
|
||||
|
||||
ServerWebExchange exchange = createExchange("/foo");
|
||||
HandlerResult result = new HandlerResult(new Object(), "fooValue", ResolvableType.forClass(String.class));
|
||||
handler.handleResult(exchange, result).get();
|
||||
|
||||
assertEquals(contentType, exchange.getResponse().getHeaders().getContentType());
|
||||
}
|
||||
|
||||
|
||||
private ResponseBodyResultHandler createHandler(Encoder<?>... encoders) {
|
||||
return createHandler(new HeaderContentTypeResolver(), encoders);
|
||||
}
|
||||
|
||||
private ResponseBodyResultHandler createHandler(ContentTypeResolver resolver,
|
||||
Encoder<?>... encoders) {
|
||||
|
||||
private ResponseBodyResultHandler createResultHandler(Encoder<?>... encoders) {
|
||||
List<HttpMessageConverter<?>> converters = Arrays.stream(encoders)
|
||||
.map(encoder -> new CodecHttpMessageConverter<>(encoder, null))
|
||||
.collect(Collectors.toList());
|
||||
return new ResponseBodyResultHandler(converters, new DefaultConversionService());
|
||||
return new ResponseBodyResultHandler(converters, new DefaultConversionService(), resolver);
|
||||
}
|
||||
|
||||
private ServerWebExchange createExchange(String path) throws URISyntaxException {
|
||||
ServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, new URI(path));
|
||||
WebSessionManager sessionManager = mock(WebSessionManager.class);
|
||||
return new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
|
Loading…
Reference in New Issue