diff --git a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerWebExchange.java b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerWebExchange.java index 59400c4bedb..5276c71b7f0 100644 --- a/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerWebExchange.java +++ b/spring-test/src/main/java/org/springframework/mock/http/server/reactive/MockServerWebExchange.java @@ -15,6 +15,7 @@ */ package org.springframework.mock.http.server.reactive; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.server.ServerWebExchangeDecorator; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.session.DefaultWebSessionManager; @@ -35,7 +36,8 @@ public class MockServerWebExchange extends ServerWebExchangeDecorator { public MockServerWebExchange(MockServerHttpRequest request) { super(new DefaultServerWebExchange( - request, new MockServerHttpResponse(), new DefaultWebSessionManager())); + request, new MockServerHttpResponse(), new DefaultWebSessionManager(), + ServerCodecConfigurer.create())); } diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java index 9d517543044..d96c248f013 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/DefaultServerWebExchange.java @@ -34,7 +34,8 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; -import org.springframework.http.codec.FormHttpMessageReader; +import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; @@ -46,6 +47,8 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebSession; import org.springframework.web.server.session.WebSessionManager; +import static org.springframework.http.MediaType.*; + /** * Default implementation of {@link ServerWebExchange}. * @@ -56,8 +59,6 @@ public class DefaultServerWebExchange implements ServerWebExchange { private static final List SAFE_METHODS = Arrays.asList(HttpMethod.GET, HttpMethod.HEAD); - private static final FormHttpMessageReader FORM_READER = new FormHttpMessageReader(); - private static final ResolvableType FORM_DATA_VALUE_TYPE = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class); @@ -85,26 +86,35 @@ public class DefaultServerWebExchange implements ServerWebExchange { * Alternate constructor with a WebSessionManager parameter. */ public DefaultServerWebExchange(ServerHttpRequest request, ServerHttpResponse response, - WebSessionManager sessionManager) { + WebSessionManager sessionManager, ServerCodecConfigurer codecConfigurer) { Assert.notNull(request, "'request' is required"); Assert.notNull(response, "'response' is required"); - Assert.notNull(response, "'sessionManager' is required"); - Assert.notNull(response, "'formReader' is required"); + Assert.notNull(sessionManager, "'sessionManager' is required"); + Assert.notNull(codecConfigurer, "'codecConfigurer' is required"); this.request = request; this.response = response; this.sessionMono = sessionManager.getSession(this).cache(); - this.formDataMono = initFormData(request); + this.formDataMono = initFormData(request, codecConfigurer); this.requestParamsMono = initRequestParams(request, this.formDataMono); + } - private static Mono> initFormData(ServerHttpRequest request) { + @SuppressWarnings("unchecked") + private static Mono> initFormData( + ServerHttpRequest request, ServerCodecConfigurer codecConfigurer) { + MediaType contentType; try { contentType = request.getHeaders().getContentType(); - if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) { - return FORM_READER + if (APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) { + return ((HttpMessageReader>)codecConfigurer + .getReaders() + .stream() + .filter(messageReader -> messageReader.canRead(FORM_DATA_VALUE_TYPE, APPLICATION_FORM_URLENCODED)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Could not find HttpMessageReader that supports " + APPLICATION_FORM_URLENCODED))) .readMono(FORM_DATA_VALUE_TYPE, request, Collections.emptyMap()) .switchIfEmpty(EMPTY_FORM_DATA) .cache(); diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java b/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java index a9ebc6e9e3d..95814850ba6 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/HttpWebHandlerAdapter.java @@ -21,6 +21,7 @@ import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Mono; import org.springframework.http.HttpStatus; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; @@ -38,6 +39,7 @@ import org.springframework.web.server.session.WebSessionManager; * then invokes the target {@code WebHandler}. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze * @since 5.0 */ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHandler { @@ -46,6 +48,8 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa private WebSessionManager sessionManager = new DefaultWebSessionManager(); + private ServerCodecConfigurer codecConfigurer; + public HttpWebHandlerAdapter(WebHandler delegate) { super(delegate); @@ -71,6 +75,24 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa return this.sessionManager; } + /** + * Configure a custom {@link ServerCodecConfigurer}. The provided instance is set on + * each created {@link DefaultServerWebExchange}. + *

By default this is set to {@link ServerCodecConfigurer#create()}. + * @param codecConfigurer the codec configurer to use + */ + public void setCodecConfigurer(ServerCodecConfigurer codecConfigurer) { + Assert.notNull(codecConfigurer, "ServerCodecConfigurer must not be null"); + this.codecConfigurer = codecConfigurer; + } + + /** + * Return the configured {@link ServerCodecConfigurer}. + */ + public ServerCodecConfigurer getCodecConfigurer() { + return this.codecConfigurer != null ? this.codecConfigurer : ServerCodecConfigurer.create(); + } + @Override public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { @@ -87,7 +109,7 @@ public class HttpWebHandlerAdapter extends WebHandlerDecorator implements HttpHa } protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttpResponse response) { - return new DefaultServerWebExchange(request, response, this.sessionManager); + return new DefaultServerWebExchange(request, response, this.sessionManager, getCodecConfigurer()); } } diff --git a/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java b/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java index 62b233e0928..b687b0fb34f 100644 --- a/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/server/adapter/WebHttpHandlerBuilder.java @@ -22,6 +22,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -51,6 +52,7 @@ import org.springframework.web.server.session.WebSessionManager; * {@link #applicationContext(ApplicationContext)}, or a mix of both. * * @author Rossen Stoyanchev + * @author Sebastien Deleuze * @since 5.0 * @see HttpWebHandlerAdapter */ @@ -62,6 +64,9 @@ public class WebHttpHandlerBuilder { /** Well-known name for the WebSessionManager in the bean factory. */ public static final String WEB_SESSION_MANAGER_BEAN_NAME = "webSessionManager"; + /** Well-known name for the ServerCodecConfigurer in the bean factory. */ + public static final String SERVER_CODEC_CONFIGURER_BEAN_NAME = "serverCodecConfigurer"; + private final WebHandler webHandler; @@ -71,6 +76,8 @@ public class WebHttpHandlerBuilder { private WebSessionManager sessionManager; + private ServerCodecConfigurer codecConfigurer; + /** * Private constructor. @@ -102,6 +109,8 @@ public class WebHttpHandlerBuilder { * ordered. *

  • {@link WebSessionManager} [0..1] -- looked up by the name * {@link #WEB_SESSION_MANAGER_BEAN_NAME}. + *
  • {@link ServerCodecConfigurer} [0..1] -- looked up by the name + * {@link #SERVER_CODEC_CONFIGURER_BEAN_NAME}. * * @param context the application context to use for the lookup * @return the prepared builder @@ -126,6 +135,14 @@ public class WebHttpHandlerBuilder { // Fall back on default } + try { + builder.codecConfigurer( + context.getBean(SERVER_CODEC_CONFIGURER_BEAN_NAME, ServerCodecConfigurer.class)); + } + catch (NoSuchBeanDefinitionException ex) { + // Fall back on default + } + return builder; } @@ -184,6 +201,16 @@ public class WebHttpHandlerBuilder { return this; } + /** + * Configure the {@link ServerCodecConfigurer} to set on the + * {@link ServerWebExchange WebServerExchange}. + * @param codecConfigurer the codec configurer + */ + public WebHttpHandlerBuilder codecConfigurer(ServerCodecConfigurer codecConfigurer) { + this.codecConfigurer = codecConfigurer; + return this; + } + /** * Build the {@link HttpHandler}. @@ -199,6 +226,9 @@ public class WebHttpHandlerBuilder { if (this.sessionManager != null) { adapted.setSessionManager(this.sessionManager); } + if (this.codecConfigurer != null) { + adapted.setCodecConfigurer(this.codecConfigurer); + } return adapted; } diff --git a/spring-web/src/test/java/org/springframework/mock/http/server/reactive/test/MockServerWebExchange.java b/spring-web/src/test/java/org/springframework/mock/http/server/reactive/test/MockServerWebExchange.java index 32ec52273a7..73c77ab4db0 100644 --- a/spring-web/src/test/java/org/springframework/mock/http/server/reactive/test/MockServerWebExchange.java +++ b/spring-web/src/test/java/org/springframework/mock/http/server/reactive/test/MockServerWebExchange.java @@ -15,6 +15,7 @@ */ package org.springframework.mock.http.server.reactive.test; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.web.server.ServerWebExchangeDecorator; import org.springframework.web.server.adapter.DefaultServerWebExchange; import org.springframework.web.server.session.DefaultWebSessionManager; @@ -35,7 +36,7 @@ public class MockServerWebExchange extends ServerWebExchangeDecorator { public MockServerWebExchange(MockServerHttpRequest request) { super(new DefaultServerWebExchange( - request, new MockServerHttpResponse(), new DefaultWebSessionManager())); + request, new MockServerHttpResponse(), new DefaultWebSessionManager(), ServerCodecConfigurer.create())); } diff --git a/spring-web/src/test/java/org/springframework/web/server/session/DefaultWebSessionManagerTests.java b/spring-web/src/test/java/org/springframework/web/server/session/DefaultWebSessionManagerTests.java index 548a0bae8bf..b6be20397d7 100644 --- a/spring-web/src/test/java/org/springframework/web/server/session/DefaultWebSessionManagerTests.java +++ b/spring-web/src/test/java/org/springframework/web/server/session/DefaultWebSessionManagerTests.java @@ -26,6 +26,7 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; import org.springframework.web.server.ServerWebExchange; @@ -59,7 +60,7 @@ public class DefaultWebSessionManagerTests { MockServerHttpRequest request = MockServerHttpRequest.get("/path").build(); MockServerHttpResponse response = new MockServerHttpResponse(); - this.exchange = new DefaultServerWebExchange(request, response, this.manager); + this.exchange = new DefaultServerWebExchange(request, response, this.manager, ServerCodecConfigurer.create()); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java index 4204f08077d..76861084e20 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurationSupport.java @@ -81,8 +81,6 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { private PathMatchConfigurer pathMatchConfigurer; - private ServerCodecConfigurer messageCodecsConfigurer; - private ApplicationContext applicationContext; @@ -242,7 +240,7 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); - adapter.setMessageCodecConfigurer(getMessageCodecsConfigurer()); + adapter.setMessageCodecConfigurer(serverCodecConfigurer()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); adapter.setReactiveAdapterRegistry(webFluxAdapterRegistry()); @@ -267,16 +265,15 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { } /** - * Main method to access the configurer for HTTP message readers and writers. + * Return the configurer for HTTP message readers and writers. *

    Use {@link #configureHttpMessageCodecs(ServerCodecConfigurer)} to * configure the readers and writers. */ - protected final ServerCodecConfigurer getMessageCodecsConfigurer() { - if (this.messageCodecsConfigurer == null) { - this.messageCodecsConfigurer = ServerCodecConfigurer.create(); - configureHttpMessageCodecs(this.getMessageCodecsConfigurer()); - } - return this.messageCodecsConfigurer; + @Bean + public ServerCodecConfigurer serverCodecConfigurer() { + ServerCodecConfigurer serverCodecConfigurer = ServerCodecConfigurer.create(); + configureHttpMessageCodecs(serverCodecConfigurer); + return serverCodecConfigurer; } /** @@ -372,13 +369,13 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { @Bean public ResponseEntityResultHandler responseEntityResultHandler() { - return new ResponseEntityResultHandler(getMessageCodecsConfigurer().getWriters(), + return new ResponseEntityResultHandler(serverCodecConfigurer().getWriters(), webFluxContentTypeResolver(), webFluxAdapterRegistry()); } @Bean public ResponseBodyResultHandler responseBodyResultHandler() { - return new ResponseBodyResultHandler(getMessageCodecsConfigurer().getWriters(), + return new ResponseBodyResultHandler(serverCodecConfigurer().getWriters(), webFluxContentTypeResolver(), webFluxAdapterRegistry()); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java index 4948887f7a2..3e8154853ae 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/DispatcherHandlerIntegrationTests.java @@ -125,13 +125,13 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr new HandlerStrategies() { @Override public Supplier>> messageReaders() { - return () -> getMessageCodecsConfigurer().getReaders().stream() + return () -> serverCodecConfigurer().getReaders().stream() .map(reader -> (HttpMessageReader) reader); } @Override public Supplier>> messageWriters() { - return () -> getMessageCodecsConfigurer().getWriters().stream() + return () -> serverCodecConfigurer().getWriters().stream() .map(writer -> (HttpMessageWriter) writer); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolverTests.java index f817a55b703..c37d8004326 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolverTests.java @@ -31,6 +31,7 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; @@ -81,7 +82,7 @@ public class SessionAttributeMethodArgumentResolverTests { WebSessionManager sessionManager = new MockWebSessionManager(this.session); ServerHttpRequest request = MockServerHttpRequest.get("/").build(); - this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager); + this.exchange = new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager, ServerCodecConfigurer.create()); this.handleMethod = ReflectionUtils.findMethod(getClass(), "handleWithSessionAttribute", (Class[]) null); }