Expose ServerCodecConfigurer as a bean
With this commit, ServerCodecConfigurer is now exposed as a bean in order to be provided to DefaultServerWebExchange via WebHttpHandlerBuilder and HttpWebHandlerAdapter. This allows DefaultServerWebExchange to get configured codecs for reading form or multipart requests. Issue: SPR-14546
This commit is contained in:
		
							parent
							
								
									a712c19661
								
							
						
					
					
						commit
						8e272bc5b0
					
				|  | @ -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())); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<HttpMethod> 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<MultiValueMap<String, String>> initFormData(ServerHttpRequest request) { | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	private static Mono<MultiValueMap<String, String>> 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<MultiValueMap<String, String>>)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(); | ||||
|  |  | |||
|  | @ -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}. | ||||
| 	 * <p>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<Void> 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()); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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. | ||||
| 	 *	<li>{@link WebSessionManager} [0..1] -- looked up by the name | ||||
| 	 *	{@link #WEB_SESSION_MANAGER_BEAN_NAME}. | ||||
| 	 *  <li>{@link ServerCodecConfigurer} [0..1] -- looked up by the name | ||||
| 	 *	{@link #SERVER_CODEC_CONFIGURER_BEAN_NAME}. | ||||
| 	 * </ul> | ||||
| 	 * @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; | ||||
| 	} | ||||
|  |  | |||
|  | @ -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())); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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()); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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. | ||||
| 	 * <p>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()); | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -125,13 +125,13 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr | |||
| 					new HandlerStrategies() { | ||||
| 						@Override | ||||
| 						public Supplier<Stream<HttpMessageReader<?>>> messageReaders() { | ||||
| 							return () -> getMessageCodecsConfigurer().getReaders().stream() | ||||
| 							return () -> serverCodecConfigurer().getReaders().stream() | ||||
| 									.map(reader -> (HttpMessageReader<?>) reader); | ||||
| 						} | ||||
| 
 | ||||
| 						@Override | ||||
| 						public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() { | ||||
| 							return () -> getMessageCodecsConfigurer().getWriters().stream() | ||||
| 							return () -> serverCodecConfigurer().getWriters().stream() | ||||
| 									.map(writer -> (HttpMessageWriter<?>) writer); | ||||
| 						} | ||||
| 
 | ||||
|  |  | |||
|  | @ -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); | ||||
| 	} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue