Use ContentTypeResolver for content negotiation

This commit is contained in:
Rossen Stoyanchev 2016-05-26 10:52:19 -04:00
parent 2292e46b04
commit 37404d081e
3 changed files with 84 additions and 22 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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")