diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java index 9f4e147359e..5db8000d3c4 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/DelegatingWebFluxConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; +import org.springframework.web.reactive.socket.server.WebSocketService; /** * A subclass of {@code WebFluxConfigurationSupport} that detects and delegates @@ -98,6 +99,12 @@ public class DelegatingWebFluxConfiguration extends WebFluxConfigurationSupport return (messageCodesResolver != null ? messageCodesResolver : super.getMessageCodesResolver()); } + @Override + protected WebSocketService getWebSocketService() { + WebSocketService service = this.configurers.getWebSocketService(); + return (service != null ? service : super.getWebSocketService()); + } + @Override protected void configureViewResolvers(ViewResolverRegistry registry) { this.configurers.configureViewResolvers(registry); 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 e61749bb300..c288384d077 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 @@ -65,6 +65,8 @@ import org.springframework.web.reactive.result.method.annotation.ResponseBodyRes import org.springframework.web.reactive.result.method.annotation.ResponseEntityResultHandler; import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; +import org.springframework.web.reactive.socket.server.WebSocketService; +import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebExceptionHandler; import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; @@ -431,6 +433,24 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware { return new SimpleHandlerAdapter(); } + @Bean + public WebSocketHandlerAdapter webFluxWebSocketHandlerAdapter() { + WebSocketService service = getWebSocketService(); + WebSocketHandlerAdapter adapter = (service != null ? + new WebSocketHandlerAdapter(service) : new WebSocketHandlerAdapter()); + + // For backwards compatibility, lower the (default) priority + int defaultOrder = adapter.getOrder(); + adapter.setOrder(defaultOrder + 1); + + return adapter; + } + + @Nullable + protected WebSocketService getWebSocketService() { + return null; + } + @Bean public ResponseEntityResultHandler responseEntityResultHandler( @Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry, diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java index e33b07b801d..43280489f7c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; +import org.springframework.web.reactive.socket.server.WebSocketService; /** * Defines callback methods to customize the configuration for WebFlux @@ -124,6 +125,18 @@ public interface WebFluxConfigurer { return null; } + /** + * Provide the {@link WebSocketService} to create + * {@link org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter} + * with. This can be used to configure server-specific properties through the + * {@link org.springframework.web.reactive.socket.server.RequestUpgradeStrategy}. + * @since 5.3 + */ + @Nullable + default WebSocketService getWebSocketService() { + return null; + } + /** * Configure view resolution for rendering responses with a view and a model, * where the view is typically an HTML template but could also be based on diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java index 2e1eed91cd4..18161d497ed 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/WebFluxConfigurerComposite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; +import org.springframework.web.reactive.socket.server.WebSocketService; /** * A {@link WebFluxConfigurer} that delegates to one or more others. @@ -70,6 +71,12 @@ public class WebFluxConfigurerComposite implements WebFluxConfigurer { this.delegates.forEach(delegate -> delegate.addResourceHandlers(registry)); } + @Nullable + @Override + public WebSocketService getWebSocketService() { + return createSingleBean(WebFluxConfigurer::getWebSocketService, WebSocketService.class); + } + @Override public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { this.delegates.forEach(delegate -> delegate.configureArgumentResolvers(configurer)); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/WebSocketHandlerAdapter.java b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/WebSocketHandlerAdapter.java index 41448844611..a2b01c6afd0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/WebSocketHandlerAdapter.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/socket/server/support/WebSocketHandlerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ package org.springframework.web.reactive.socket.server.support; import reactor.core.publisher.Mono; +import org.springframework.core.Ordered; import org.springframework.util.Assert; -import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.HandlerAdapter; import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.socket.WebSocketHandler; @@ -26,18 +26,30 @@ import org.springframework.web.reactive.socket.server.WebSocketService; import org.springframework.web.server.ServerWebExchange; /** - * {@link HandlerAdapter} that allows using a {@link WebSocketHandler} with the - * generic {@link DispatcherHandler} mapping URLs directly to such handlers. - * Requests are handled by delegating to the configured {@link WebSocketService} - * which by default is {@link HandshakeWebSocketService}. + * {@code HandlerAdapter} that allows + * {@link org.springframework.web.reactive.DispatcherHandler} to support + * handlers of type {@link WebSocketHandler} with such handlers mapped to + * URL patterns via + * {@link org.springframework.web.reactive.handler.SimpleUrlHandlerMapping}. + * + *

Requests are handled by delegating to a + * {@link WebSocketService}, by default {@link HandshakeWebSocketService}, + * which checks the WebSocket handshake request parameters, upgrades to a + * WebSocket interaction, and uses the {@link WebSocketHandler} to handle it. + * + *

As of 5.3 the WebFlux Java configuration, imported via + * {@code @EnableWebFlux}, includes a declaration of this adapter and therefore + * it no longer needs to be present in application configuration. * * @author Rossen Stoyanchev * @since 5.0 */ -public class WebSocketHandlerAdapter implements HandlerAdapter { +public class WebSocketHandlerAdapter implements HandlerAdapter, Ordered { private final WebSocketService webSocketService; + private int order = 2; + /** * Default constructor that creates and uses a @@ -56,6 +68,25 @@ public class WebSocketHandlerAdapter implements HandlerAdapter { } + /** + * Set the order value for this adapter. + *

By default this is set to 2. + * @param order the value to set to + * @since 5.3 + */ + public void setOrder(int order) { + this.order = order; + } + + /** + * Return the {@link #setOrder(int) configured} order for this instance. + * @since 5.3 + */ + @Override + public int getOrder() { + return this.order; + } + /** * Return the configured {@code WebSocketService} to handle requests. */ diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java index d5515616b30..1d592013ed0 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/DelegatingWebFluxConfigurationTests.java @@ -37,11 +37,14 @@ import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; +import org.springframework.web.reactive.socket.server.WebSocketService; +import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** @@ -73,6 +76,7 @@ public class DelegatingWebFluxConfigurationTests { delegatingConfig.setApplicationContext(new StaticApplicationContext()); given(webFluxConfigurer.getValidator()).willReturn(null); given(webFluxConfigurer.getMessageCodesResolver()).willReturn(null); + given(webFluxConfigurer.getWebSocketService()).willReturn(null); } @@ -125,6 +129,17 @@ public class DelegatingWebFluxConfigurationTests { verify(webFluxConfigurer).configurePathMatching(any(PathMatchConfigurer.class)); } + @Test + void webSocketService() { + WebSocketService service = mock(WebSocketService.class); + given(webFluxConfigurer.getWebSocketService()).willReturn(service); + + delegatingConfig.setConfigurers(Collections.singletonList(webFluxConfigurer)); + WebSocketHandlerAdapter adapter = delegatingConfig.webFluxWebSocketHandlerAdapter(); + + assertThat(adapter.getWebSocketService()).isSameAs(service); + } + @Test public void responseBodyResultHandler() { delegatingConfig.setConfigurers(Collections.singletonList(webFluxConfigurer)); diff --git a/src/docs/asciidoc/web/webflux-websocket.adoc b/src/docs/asciidoc/web/webflux-websocket.adoc index 4ad5281f8fa..033751bed2f 100644 --- a/src/docs/asciidoc/web/webflux-websocket.adoc +++ b/src/docs/asciidoc/web/webflux-websocket.adoc @@ -54,7 +54,7 @@ The following example shows how to do so: } ---- -Then you can map it to a URL and add a `WebSocketHandlerAdapter`, as the following example shows: +Then you can map it to a URL: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -70,11 +70,6 @@ Then you can map it to a URL and add a `WebSocketHandlerAdapter`, as the followi return new SimpleUrlHandlerMapping(map, order); } - - @Bean - public WebSocketHandlerAdapter handlerAdapter() { - return new WebSocketHandlerAdapter(); - } } ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] @@ -90,6 +85,34 @@ Then you can map it to a URL and add a `WebSocketHandlerAdapter`, as the followi return SimpleUrlHandlerMapping(map, order) } + } +---- + +If using the <> there is nothing +further to do, or otherwise if not using the WebFlux config you'll need to declare a +`WebSocketHandlerAdapter` as shown below: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Configuration + class WebConfig { + + // ... + + @Bean + public WebSocketHandlerAdapter handlerAdapter() { + return new WebSocketHandlerAdapter(); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + class WebConfig { + + // ... @Bean fun handlerAdapter() = WebSocketHandlerAdapter() @@ -333,9 +356,11 @@ into the attributes of the `WebSocketSession`. === Server Configation [.small]#<># -The `RequestUpgradeStrategy` for each server exposes WebSocket-related configuration -options available for the underlying WebSocket engine. The following example sets -WebSocket options when running on Tomcat: +The `RequestUpgradeStrategy` for each server exposes configuration specific to the +underlying WebSocket server engine. When using the WebFlux Java config you can customize +such properties as shown in the corresponding section of the +<>, or otherwise if +not using the WebFlux config, use the below: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index d1d053130f3..b2aa2af433b 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -4289,6 +4289,53 @@ reliance on it. +[[webflux-config-websocket-service]] +=== WebSocketService + +The WebFlux Java config declares of a `WebSocketHandlerAdapter` bean which provides +support for the invocation of WebSocket handlers. That means all that remains to do in +order to handle a WebSocket handshake request is to map a `WebSocketHandler` to a URL +via `SimpleUrlHandlerMapping`. + +In some cases it may be necessary to create the `WebSocketHandlerAdapter` bean with a +provided `WebSocketService` service which allows configuring WebSocket server properties. +For example: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @Configuration + @EnableWebFlux + public class WebConfig implements WebFluxConfigurer { + + @Override + public WebSocketService getWebSocketService() { + TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy(); + strategy.setMaxSessionIdleTimeout(0L); + return new HandshakeWebSocketService(strategy); + } + } +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @Configuration + @EnableWebFlux + class WebConfig : WebFluxConfigurer { + + @Override + fun webSocketService(): WebSocketService { + val strategy = TomcatRequestUpgradeStrategy().apply { + setMaxSessionIdleTimeout(0L) + } + return HandshakeWebSocketService(strategy) + } + } +---- + + + + [[webflux-config-advanced-java]] === Advanced Configuration Mode [.small]#<>#