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 e49c4a9566..6297cfb65e 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,74 +37258,200 @@ 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.
-[[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
-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.
+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.
-The SockJS client issues HTTP requests with this URL structure:
+In Java config this can be done as shown below. The XML namespace provides a
+similar option on the `` element:
-.SockJS URL
+[source,java,indent=0]
+[subs="verbatim,quotes"]
----
-http://host:port/{path-to-sockjs-endpoint}/{server-id}/{session-id}/{transport}
+ @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");
+ }
+
+ // ...
+
+ }
----
-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.
+[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.
+====
-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.
+[[websocket-fallback-sockjs-heartbeat]]
+==== Heartbeat Support in SockJS
-SockJS adds a minimal amount of message framing. For example, the server can send
-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"]+).
+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.
-By default the server sends a heartbeat frame every 25 seconds to keep proxies
-and loadbalancers from timing out the connection.
+[NOTE]
+====
+When using STOMP over WebSocket/SockJS, if the STOMP client and server negotiate
+heartbeats to be exchanged, the SockJS heartbeats are disabled.
+====
+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
-[[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.
+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].
-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.
+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.
-[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.
+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.
-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.
+[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-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
+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 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 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:
+
+----
+http://host:port/{sockjs-endpoint}/{server-id}/{session-id}/{transport}
+----
+
+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.
+
+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"]+).
[[websocket-stomp]]