From 536e72c8df7aa206bd02268e5bef939b73d5b653 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 13 Nov 2017 18:06:10 -0500 Subject: [PATCH] Add WebSocket chapter to WebFlux section Issue: SPR-15700 --- .../AbstractWebSocketIntegrationTests.java | 4 +- src/docs/asciidoc/web-reactive.adoc | 1 + src/docs/asciidoc/web/webflux-websocket.adoc | 167 ++++++++++++++++++ src/docs/asciidoc/web/websocket.adoc | 38 ++-- 4 files changed, 187 insertions(+), 23 deletions(-) create mode 100644 src/docs/asciidoc/web/webflux-websocket.adoc diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractWebSocketIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractWebSocketIntegrationTests.java index a97f048e021..2766c1f926f 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractWebSocketIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/socket/AbstractWebSocketIntegrationTests.java @@ -171,7 +171,9 @@ public abstract class AbstractWebSocketIntegrationTests { @Bean public WebSocketService webSocketService() { - return new HandshakeWebSocketService(getUpgradeStrategy()); + TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy(); + strategy.setMaxSessionIdleTimeout(0L); + return new HandshakeWebSocketService(strategy); } protected abstract RequestUpgradeStrategy getUpgradeStrategy(); diff --git a/src/docs/asciidoc/web-reactive.adoc b/src/docs/asciidoc/web-reactive.adoc index 258a0b0c1e1..b80b8bdf154 100644 --- a/src/docs/asciidoc/web-reactive.adoc +++ b/src/docs/asciidoc/web-reactive.adoc @@ -18,6 +18,7 @@ include::web/webflux.adoc[leveloffset=+1] include::web/webflux-webclient.adoc[leveloffset=+1] +include::web/webflux-websocket.adoc[leveloffset=+1] diff --git a/src/docs/asciidoc/web/webflux-websocket.adoc b/src/docs/asciidoc/web/webflux-websocket.adoc new file mode 100644 index 00000000000..21bce882494 --- /dev/null +++ b/src/docs/asciidoc/web/webflux-websocket.adoc @@ -0,0 +1,167 @@ +[[webflux-websocket]] += WebSockets +[.small]#<># + +This part of the reference documentation covers support for Reactive stack, WebSocket +messaging. + + +include::websocket-intro.adoc[leveloffset=+1] + + + +[[webflux-websocket-server]] +== WebSocket API +[.small]#<># + +The Spring Framework provides a WebSocket API that can be used to write client and +server side applications that handle WebSocket messages. + + + +[[webflux-websocket-server-handler]] +=== WebSocketHandler +[.small]#<># + +Creating a WebSocket server is as simple as implementing `WebSocketHandler`: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +import org.springframework.web.reactive.socket.WebSocketHandler; +import org.springframework.web.reactive.socket.WebSocketSession; + +public class MyWebSocketHandler implements WebSocketHandler { + + @Override + public Mono handle(WebSocketSession session) { + // ... + } + +} +---- + +Spring WebFlux provides a `WebSocketHandlerAdapter` that can adapt WebSocket +requests and use the above handler to handle the resulting WebSocket session. After the +adapter is registered as a bean, you can map requests to your handler, for example using +`SimpleUrlHandlerMapping`. This is shown below: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +@Configuration +static class WebConfig { + + @Bean + public HandlerMapping handlerMapping() { + Map map = new HashMap<>(); + map.put("/path", new MyWebSocketHandler()); + + SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); + mapping.setUrlMap(map); + mapping.setOrder(-1); // before annotated controllers + return mapping; + } + + @Bean + public WebSocketHandlerAdapter handlerAdapter() { + return new WebSocketHandlerAdapter(); + } + +} +---- + + + +[[webflux-websocket-server-handshake]] +=== WebSocket Handshake +[.small]#<># + +`WebSocketHandlerAdapter` does not perform WebSocket handshakes itself. Instead it +delegates to an instance of `WebSocketService`. The default `WebSocketService` +implementation is `HandshakeWebSocketService`. + +The `HandshakeWebSocketService` performs basic checks on the WebSocket request and +delegates to a server-specific `RequestUpgradeStrategy`. At present upgrade strategies +exist for Reactor Netty, Tomcat, Jetty, and Undertow. + + + +[[webflux-websocket-server-config]] +=== Server config +[.small]#<># + +The `RequestUpgradeStrategy` for each server exposes the WebSocket-related configuration +options available for the underlying WebSocket engine. Below is an example of setting +WebSocket options when running on Tomcat: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +@Configuration +static class WebConfig { + + @Bean + public WebSocketHandlerAdapter handlerAdapter() { + return new WebSocketHandlerAdapter(webSocketService()); + } + + @Bean + public WebSocketService webSocketService() { + TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy(); + strategy.setMaxSessionIdleTimeout(0L); + return new HandshakeWebSocketService(strategy); + } + +} +---- + +Check the upgrade strategy for your server to see what options are available. Currently +only Tomcat and Jetty expose such options. + + + +[[webflux-websocket-server-cors]] +=== CORS +[.small]#<># + +The easiest way to configure CORS and restrict access to a WebSocket endpoint is to +have your `WebSocketHandler` implement `CorsConfigurationSource` and return a +`CorsConfiguraiton` with allowed origins, headers, etc. If for any reason you can't do +that, you can also set the `corsConfigurations` property on the `SimpleUrlHandler` to +specify CORS settings by URL pattern. If both are specified they're combined via the +`combine` method on `CorsConfiguration`. + + + +[[webflux-websocket-client]] +== WebSocketClient + +Spring WebFlux provides a `WebSocketClient` abstraction with implementations for +Reactor Netty, Tomcat, Jetty, Undertow, and standard Java (i.e. JSR-356). + +[NOTE] +==== +The Tomcat client is effectively an extension of the standard Java one with some extra +functionality in the `WebSocketSession` handling taking advantage of Tomcat specific +API to suspend receiving messages for back pressure. +==== + +To start a WebSocket session, create an instance of the client and use its `execute` +methods: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +WebSocketClient client = new ReactorNettyWebSocketClient(); + +URI url = new URI("ws://localhost:8080/path"); +client.execute(url, session -> + session.receive() + .doOnNext(System.out::println) + .then()); +---- + +Some clients, e.g. Jetty, implement `Lifecycle` and need to be started in stopped +before you can use them. All clients have constructor options related to configuration +of the underlying WebSocket client. diff --git a/src/docs/asciidoc/web/websocket.adoc b/src/docs/asciidoc/web/websocket.adoc index da679e1921f..7d05ad825db 100644 --- a/src/docs/asciidoc/web/websocket.adoc +++ b/src/docs/asciidoc/web/websocket.adoc @@ -1,6 +1,7 @@ [[websocket]] = WebSockets :doc-spring-security: {doc-root}/spring-security/site/docs/current/reference +[.small]#<># This part of the reference documentation covers support for Servlet stack, WebSocket messaging that includes raw WebSocket interactions, WebSocket emulation via SockJS, and @@ -10,32 +11,19 @@ pub-sub messaging via STOMP as a sub-protocol over WebSocket. include::websocket-intro.adoc[leveloffset=+1] + [[websocket-server]] == WebSocket API +[.small]#<># -The Spring Framework provides a WebSocket API designed to adapt to various WebSocket engines. -Currently the list includes WebSocket runtimes such as Tomcat 7.0.47+, Jetty 9.1+, -GlassFish 4.1+, WebLogic 12.1.3+, and Undertow 1.0+ (and WildFly 8.0+). Additional support -may be added as more WebSocket runtimes become available. - -[NOTE] -==== -As explained in the <>, direct use of a -WebSocket API is too low level for applications -- until assumptions are made about the -format of a message there is little a framework can do to interpret messages or route -them via annotations. This is why applications should consider using a sub-protocol -and Spring's <> support. - -When using a higher level protocol, the details of the WebSocket API become less -relevant, much like the details of TCP communication are not exposed to applications -when using HTTP. Nevertheless this section covers the details of using WebSocket -directly. -==== +The Spring Framework provides a WebSocket API that can be used to write client and +server side applications that handle WebSocket messages. [[websocket-server-handler]] === WebSocketHandler +[.small]#<># Creating a WebSocket server is as simple as implementing `WebSocketHandler` or more likely extending either `TextWebSocketHandler` or `BinaryWebSocketHandler`: @@ -117,6 +105,7 @@ into other HTTP serving environments with the help of [[websocket-server-handshake]] === WebSocket Handshake +[.small]#<># The easiest way to customize the initial HTTP WebSocket handshake request is through a `HandshakeInterceptor`, which exposes "before" and "after" the handshake methods. @@ -204,16 +193,19 @@ Neither of these mechanism makes it possible to use a single "front controller" for all HTTP processing -- including WebSocket handshake and all other HTTP requests -- such as Spring MVC's `DispatcherServlet`. -This is a significant limitation of JSR-356 that Spring's WebSocket support -addresses by providing a server-specific `RequestUpgradeStrategy` even when -running in a JSR-356 runtime. +This is a significant limitation of JSR-356 that Spring's WebSocket support addresses +server-specific ``RequestUpgradeStrategy``'s even when running in a JSR-356 runtime. +Such strategies currently exist for Tomcat, Jetty, GlassFish, WebLogic, WebSphere, and +Undertow (and WildFly). + + [NOTE] ==== A request to overcome the above limitation in the Java WebSocket API has been created and can be followed at https://java.net/jira/browse/WEBSOCKET_SPEC-211[WEBSOCKET_SPEC-211]. -Also note that Tomcat and Jetty already provide native API alternatives that +Tomcat, Jetty, and WebSphere provide their own API alternatives that makes it easy to overcome the limitation. We are hopeful that more servers will follow their example regardless of when it is addressed in the Java WebSocket API. @@ -266,6 +258,7 @@ Java initialization API, if required: [[websocket-server-runtime-configuration]] === Server config +[.small]#<># Each underlying WebSocket engine exposes configuration properties that control runtime characteristics such as the size of message buffer sizes, idle timeout, @@ -394,6 +387,7 @@ or WebSocket XML namespace: [[websocket-server-allowed-origins]] === Allowed origins +[.small]#<># As of Spring Framework 4.1.5, the default behavior for WebSocket and SockJS is to accept only _same origin_ requests. It is also possible to allow _all_ or a specified list of origins.