From d30cb17c91e4e5eeda29e9c1836f734145ecc9b1 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 23 Mar 2014 22:49:33 -0400 Subject: [PATCH 1/4] Polish SockJS documentation --- src/asciidoc/index.adoc | 98 ++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 54 deletions(-) diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index e49c4a9566..923990103c 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -37260,73 +37260,63 @@ configuration options, for example, to specify which transports to include. +[[websocket-fallback-sockjs-servlet3-async]] +==== SockJS and Servlet 3 Async Support + +HTTP streaming and HTTP long polling SockJS transports require a connection to remain +open longer than usual. For an overview of these techniques see +https://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates/[this blog post]. + +In Servlet containers this is done through Servlet 3 async support that +allows exiting the Servlet container thread processing a request and continuing +to write to the response from another thread. + +A specific issue is the Servlet API does not provide notifications for a client +that has gone away, see https://java.net/jira/browse/SERVLET_SPEC-44[SERVLET_SPEC-44]. +However, Servlet containers raise an exception on subseqeunt attempts to write +to the response. Since Spring's SockJS Service support sever-sent heartbeats (every +25 seconds by default), that means a client disconnect is usually detected within that +time period or earlier if a message are sent more frequently. + +[NOTE] +==== +As a result network IO failures may occur simply because a client has disconnected, which +can fill the log with unnecessary stack traces. Spring makes a best effort to identify +such network failures that represent client disconnects (specific to each server) and log +a more minimal message using the dedicated log category `DISCONNECTED_CLIENT_LOG_CATEGORY` +defined in `AbstractSockJsSession`. If you need to see the stack traces, set that +log category to TRACE. +==== + [[websocket-fallback-sockjs-explained]] ==== How SockJS Works -An in-depth description of how SockJS works is beyond the scope of this document. -This section summarizes a few key facts to aid with understanding. -The SockJS protocol itself is defined in a +This is a question beyond the scope of this document. The SockJS protocol +is defined in the form of a Python https://github.com/sockjs/sockjs-protocol/blob/master/sockjs-protocol-0.3.3.py[test suite], -with comments explaining the protocol. There is also an -http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html[HTML version of that test] -showing comments on the right and code on the left. +with narrative in comments. There is an +http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html[HTML formatted version] +of the test showing narrative on the right and client code on the left. -The SockJS client issues HTTP requests with this URL structure: +The SockJS client begins with an initial `"/info"` request to obtain basic +information from the server. Then the client selects a transport and sends +a series of session requests: -.SockJS URL ---- -http://host:port/{path-to-sockjs-endpoint}/{server-id}/{session-id}/{transport} +http://host:port/{sockjs-endpoint}/{server-id}/{session-id}/{transport} ---- -The WebSocket transport type uses a single HTTP connection to perform a -WebSocket handshake and establish an actual WebSocket session. HTTP-based -transports on the other hand must simulate the WebSocket API and at any time -may use two HTTP connections -- one for server-to-client messages, via -https://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates[HTTP streaming or long polling], -and another for sending client messages to the server via HTTP POST. - -The session id is useful with HTTP transports to associate individual HTTP -requests that belong to the same SockJS session. The server id is not used in the -protocol but is added to help in clustered environments. +The WebSocket transport type only needs a single HTTP connection for the handshake. +HTTP-based transports use one connection for sending messages from server to client +and separate requests for sending messages from client to server. +The session id is used to correlate HTTP requests belonging to the same SockJS +session. The server id is not used in the protocol but is added to help in +clustered environments. -SockJS adds a minimal amount of message framing. For example, the server can send +The SockJS protocol requires minimal message framing. The server for examples sends an "open frame" (the letter +o+), a "heartbeat frame" (the letter +h+), or a "close frame" (the letter +c+); while the client sends messages as a JSON-encoded array prepended with the letter `a` (e.g. +a["message1","message2"]+). -By default the server sends a heartbeat frame every 25 seconds to keep proxies -and loadbalancers from timing out the connection. - - - -[[websocket-fallback-sockjs-spring]] -==== Spring's SockJS Support -In the Spring Framework, server-side support for the SockJS protocol is provided through a -hierarchy of classes that implement the `SockJsService` interface, while -`SockJsHttpRequestHandler` integrates the service into HTTP request processing. - -To implement HTTP streaming and long polling in Servlet containers (both of which require -an HTTP connection to remain open longer than usual), Spring's SockJS support -relies on Servlet 3 async support. - -[WARNING] -==== -The Servlet API does not provide notifications when a client disconnects, -see https://java.net/jira/browse/SERVLET_SPEC-44[SERVLET_SPEC-44]. However, -Serlvet containers typically raise an IOException on the next attempt to write -to the response at which point the SockJS session is closed. Since the -SockJsService sends a heartbeat every 25 seconds, typically a disconnected -client should be detected within that time period. - -This also means that network IO failures may occur simply because a client has gone -away and that can fill the logs with unnecessary stack traces. -We make a best effort to identify such network failures, on a per-server basis, and log -them under a separate log category, see `AbstractSockJsSession#DISCONNECTED_CLIENT_LOG_CATEGORY`. -A simple one-line message is logged at DEBUG level using this category while a full -stack trace is shown at TRACE level. -==== - - - [[websocket-stomp]] === STOMP Messaging From 15188a8eee31c0d42468bc11dd8d4e84901ff988 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 23 Mar 2014 19:31:45 -0400 Subject: [PATCH 2/4] Document IE 8 and 9 related SockJS transport details Issue: SPR-11496, SPR-11525 --- .../annotation/SockJsServiceRegistration.java | 7 +- .../sockjs/support/AbstractSockJsService.java | 9 +- src/asciidoc/index.adoc | 98 ++++++++++++++++++- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java index 20507b4e76..28a29d8c6b 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java @@ -78,8 +78,9 @@ public class SockJsServiceRegistration { * "foreign" domain) from an invisible iframe. Code run from this iframe * doesn't need to worry about cross-domain issues since it is running from * a domain local to the SockJS server. The iframe does need to load the - * SockJS javascript client library and this option allows configuring its - * url. + * SockJS javascript client library and this option allows configuring its url. + * See the reference documentation for more details on this. + * *

By default this is set to point to * "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js". */ @@ -108,10 +109,12 @@ public class SockJsServiceRegistration { * from clients with a "cookie_needed" boolean property that indicates whether the use * of a JSESSIONID cookie is required for the application to function correctly, e.g. * for load balancing or in Java Servlet containers for the use of an HTTP session. + * *

This is especially important for IE 8,9 that support XDomainRequest -- a modified * AJAX/XHR -- that can do requests across domains but does not send any cookies. In * those cases, the SockJS client prefers the "iframe-htmlfile" transport over * "xdr-streaming" in order to be able to send cookies. + * *

The default value is "true" to maximize the chance for applications to work * correctly in IE 8,9 with support for cookies (and the JSESSIONID cookie in * particular). However, an application can choose to set this to "false" if the use diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java index 1baf7e88f4..ae9639ed43 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java @@ -44,6 +44,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsService; +import org.springframework.web.util.UriComponentsBuilder; /** * An abstract base class for {@link SockJsService} implementations that provides SockJS @@ -106,8 +107,9 @@ public abstract class AbstractSockJsService implements SockJsService { * "foreign" domain) from an invisible iframe. Code run from this iframe * doesn't need to worry about cross-domain issues since it is running from * a domain local to the SockJS server. The iframe does need to load the - * SockJS javascript client library and this option allows configuring its - * url. + * SockJS javascript client library and this option allows configuring that url. + * For more details see the reference documentation. + * *

By default this is set to point to * "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js". */ @@ -146,13 +148,16 @@ public abstract class AbstractSockJsService implements SockJsService { * clients with a "cookie_needed" boolean property that indicates whether the use of a * JSESSIONID cookie is required for the application to function correctly, e.g. for * load balancing or in Java Servlet containers for the use of an HTTP session. + * *

This is especially important for IE 8,9 that support XDomainRequest -- a modified * AJAX/XHR -- that can do requests across domains but does not send any cookies. In * those cases, the SockJS client prefers the "iframe-htmlfile" transport over * "xdr-streaming" in order to be able to send cookies. + * *

The SockJS protocol also expects a SockJS service to echo back the JSESSIONID * cookie when this property is set to true. However, when running in a Servlet * container this is not necessary since the container takes care of it. + * *

The default value is "true" to maximize the chance for applications to work * correctly in IE 8,9 with support for cookies (and the JSESSIONID cookie in * particular). However, an application can choose to set this to "false" if diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 923990103c..cacfd86538 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -37188,7 +37188,7 @@ or WebSocket XML namespace: [[websocket-fallback]] -=== Fallback Options +=== SockJS Fallback Options As explained in the <>, WebSocket is not supported in all browsers yet and may be precluded by restrictive network proxies. This is why Spring provides fallback options that emulate the WebSocket API as close @@ -37258,6 +37258,102 @@ https://github.com/sockjs/sockjs-client[sockjs-client] page and the list of transport types supported by browser. The client also provides several configuration options, for example, to specify which transports to include. +[[websocket-fallback-sockjs-transport]] +==== SockJS Transports + +The SockJS client simulates the WebSocket API in a wide range of browsers. +For the full list of transports by browser see the +https://github.com/sockjs/sockjs-client[SockJS client] page. The transport types +fall in 3 categories: WebSocket, HTTP Streaming, and HTTP Long Polling. For more +background on those techniques see +https://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates/[this blog post]. +An important goal of SockJS is to support at least one streaming transport +per browser, for efficiency reasons, but when necessary fall back on +long polling. + +The next few sections cover various aspects of confugring and using SockJS +in Spring applications. + +[[websocket-fallback-xhr-vs-iframe]] +==== HTTP Streaming in IE 8, 9: Ajax/XHR vs IFrame + +Internet Explorer 8 and 9 are and will remain common for some time. They are +a key reason for having SockJS. This section covers important +considerations about running in those browsers. + +SockJS client supports Ajax/XHR streaming in IE 8, 9 via Microsoft's +http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx[XDomainRequest]. +That works across domains but does not support sending cookies. +Cookies are very often essential for Java applications. +However since the SockJS client can be used with many server +types (not just Java ones), it needs to know whether cookies do matter. +If so the SockJS client prefers Ajax/XHR for streaming or otherwise it +relies on a iframe-based technique. + +The very first `"/info"` request from the SockJS client is a request for +information that can influence the client's choice of transports. +One of those details is whether the server application relies on cookies, +e.g. for authentication purposes or clustering with sticky sessions. +Spring's SockJS support includes a property called `sessionCookieNeeded`. +It is enabled by default since most Java applications rely on the `JSESSIONID` +cookie. If your application does not need it, you can turn off this option +and the SockJS client should choose the `xhr-streaming` transport in IE 8 and 9. + +If you do use an iframe-based transport, and in any case, it is good to know +that browsers can be instructed to block the use of iframes on a given page by +setting the HTTP response header `X-Frame-Options` to `DENY`, +`SAMEORIGIN`, or `ALLOW-FROM `. This is used to prevent +https://www.owasp.org/index.php/Clickjacking[clickjacking]. + +[NOTE] +==== +Spring Security 3.2+ provides support for setting `X-Frame-Options` on every +response. By default the Spring Security Java config sets it to `DENY`. +In 3.2 the Spring Security XML namespace does not set that header by default +but may be configured to do so, and in the future it may set it by default. + +See http://docs.spring.io/spring-security/site/docs/3.2.2.RELEASE/reference/htmlsingle/#headers[Section 7.1. "Default Security Headers"] +of the Spring Security documentation for details no how to configure the +setting of the `X-Frame-Options` header. You may also check or watch +https://jira.spring.io/browse/SEC-2501[SEC-2501] for additional background. +==== + +If your application adds the `X-Frame-Options` response header (as it should!) +and relies on an iframe-based transport, you will need to set the header value to +`SAMEORIGIN` or `ALLOW-FROM `. Along with that the Spring SockJS +support also needs to know the location of the SockJS client because it is loaded +from the iframe. By default the iframe is set to download the SockJS client +from a CDN location. It is a good idea to configure this option to +a URL from the same origin as the application. + +In Java config this can be done as shown below. The XML namespace provides a +similar option on the `` element: + +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + @Configuration + @EnableWebSocket + public class WebSocketConfig implements WebSocketConfigurer { + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/portfolio").withSockJS() + .setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js"); + } + + // ... + + } +---- + +[NOTE] +==== +During initial development, do enable the SockJS client `devel` mode that prevents +the browser from caching SockJS requests (like the iframe) that would otherwise +be cached. For details on how to enable it see the +https://github.com/sockjs/sockjs-client[SockJS client] page. +==== [[websocket-fallback-sockjs-servlet3-async]] From b1a0b38d8faf661e5eb105f7ade713bab0104e3d Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 23 Mar 2014 21:39:27 -0400 Subject: [PATCH 3/4] Document use of CORS headers in SockJS Service Issue: SPR-11437 --- src/asciidoc/index.adoc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index cacfd86538..39fef6db76 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -37355,6 +37355,25 @@ be cached. For details on how to enable it see the https://github.com/sockjs/sockjs-client[SockJS client] page. ==== +[[websocket-fallback-cors]] +==== SockJS and CORS + +The SockJS protocol uses CORS for cross-domain support in the XHR streaming and +XHR polling transports. CORS headers are automatically added to SockJS requests +for transports that require it as well as for the initial `"/info"` request. + +Spring's `SockJsServce` implementation checks for the presence of the CORS +`"Access-Control-Allow-Origin"` header in the response. If present, no new CORS +headers are added, essentially assuming that CORS support is configured +centrally, e.g. through a Servlet Filter. Otherwise the following are added: + +* `"Access-Control-Allow-Origin"` - intitialized from the value of the "origin" request header or "*". +* `"Access-Control-Allow-Credentials"` - always set to `true`. +* `"Access-Control-Request-Headers"` - initialized from values from the equivalent request header. +* `"Access-Control-Allow-Methods"` - the HTTP methods a transport supports (see `TransportType` enum). +* `"Access-Control-Max-Age"` - set to 31536000 (1 year). + +For the exact implementation, see `addCorsHeaders` in `AbstractSockJsService`. [[websocket-fallback-sockjs-servlet3-async]] ==== SockJS and Servlet 3 Async Support From f25b7df5529e4aefe0f4af9830cfe587e0a8e51b Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Sun, 23 Mar 2014 23:13:13 -0400 Subject: [PATCH 4/4] Document heartbeat support in SockJS --- src/asciidoc/index.adoc | 53 ++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 39fef6db76..6297cfb65e 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -37355,25 +37355,26 @@ be cached. For details on how to enable it see the https://github.com/sockjs/sockjs-client[SockJS client] page. ==== -[[websocket-fallback-cors]] -==== SockJS and CORS - -The SockJS protocol uses CORS for cross-domain support in the XHR streaming and -XHR polling transports. CORS headers are automatically added to SockJS requests -for transports that require it as well as for the initial `"/info"` request. +[[websocket-fallback-sockjs-heartbeat]] +==== Heartbeat Support in SockJS -Spring's `SockJsServce` implementation checks for the presence of the CORS -`"Access-Control-Allow-Origin"` header in the response. If present, no new CORS -headers are added, essentially assuming that CORS support is configured -centrally, e.g. through a Servlet Filter. Otherwise the following are added: +The SockJS protocol requires servers to send heartbeat messages to preclude proxies +from concluding a connection is hung. The Spring SockJS configuiration has a property +called `heartbeatTime` that can be used to customize the frequency. By default a +heartbeat is sent after 25 seconds assuming no other messages were sent on that +connection. This 25 seconds value is in line with the following +http://tools.ietf.org/html/rfc6202[IETF recommendation] for public Internet applications. -* `"Access-Control-Allow-Origin"` - intitialized from the value of the "origin" request header or "*". -* `"Access-Control-Allow-Credentials"` - always set to `true`. -* `"Access-Control-Request-Headers"` - initialized from values from the equivalent request header. -* `"Access-Control-Allow-Methods"` - the HTTP methods a transport supports (see `TransportType` enum). -* `"Access-Control-Max-Age"` - set to 31536000 (1 year). +[NOTE] +==== +When using STOMP over WebSocket/SockJS, if the STOMP client and server negotiate +heartbeats to be exchanged, the SockJS heartbeats are disabled. +==== -For the exact implementation, see `addCorsHeaders` in `AbstractSockJsService`. +The Spring SockJS support also allows configuring the `TaskScheduler` to use +for scheduling heartbeats tasks. The task scheduler is backed by a thread pool +with default settings based on the number of available processors. Applications +should consider customizing the settings according to their specific needs. [[websocket-fallback-sockjs-servlet3-async]] ==== SockJS and Servlet 3 Async Support @@ -37403,6 +37404,26 @@ defined in `AbstractSockJsSession`. If you need to see the stack traces, set tha log category to TRACE. ==== +[[websocket-fallback-cors]] +==== SockJS and CORS + +The SockJS protocol uses CORS for cross-domain support in the XHR streaming and +XHR polling transports. CORS headers are automatically added to SockJS requests +for transports that require it as well as for the initial `"/info"` request. + +Spring's `SockJsServce` implementation checks for the presence of the CORS +`"Access-Control-Allow-Origin"` header in the response. If present, no new CORS +headers are added, essentially assuming that CORS support is configured +centrally, e.g. through a Servlet Filter. Otherwise the following are added: + +* `"Access-Control-Allow-Origin"` - intitialized from the value of the "origin" request header or "*". +* `"Access-Control-Allow-Credentials"` - always set to `true`. +* `"Access-Control-Request-Headers"` - initialized from values from the equivalent request header. +* `"Access-Control-Allow-Methods"` - the HTTP methods a transport supports (see `TransportType` enum). +* `"Access-Control-Max-Age"` - set to 31536000 (1 year). + +For the exact implementation, see `addCorsHeaders` in `AbstractSockJsService`. + [[websocket-fallback-sockjs-explained]] ==== How SockJS Works This is a question beyond the scope of this document. The SockJS protocol