Merge pull request #494 from rstoyanchev/doc-sockjs

Expand SockJS documentation
master
Rossen Stoyanchev 11 years ago
commit 2f24bd24a8
  1. 7
      spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/SockJsServiceRegistration.java
  2. 9
      spring-websocket/src/main/java/org/springframework/web/socket/sockjs/support/AbstractSockJsService.java
  3. 226
      src/asciidoc/index.adoc

@ -78,8 +78,9 @@ public class SockJsServiceRegistration {
* "foreign" domain) from an invisible iframe. Code run from this iframe * "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 * 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 * a domain local to the SockJS server. The iframe does need to load the
* SockJS javascript client library and this option allows configuring its * SockJS javascript client library and this option allows configuring its url.
* url. * See the reference documentation for more details on this.
*
* <p>By default this is set to point to * <p>By default this is set to point to
* "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js". * "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 * 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. * 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. * for load balancing or in Java Servlet containers for the use of an HTTP session.
*
* <p>This is especially important for IE 8,9 that support XDomainRequest -- a modified * <p>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 * 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 * those cases, the SockJS client prefers the "iframe-htmlfile" transport over
* "xdr-streaming" in order to be able to send cookies. * "xdr-streaming" in order to be able to send cookies.
*
* <p>The default value is "true" to maximize the chance for applications to work * <p>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 * 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 * particular). However, an application can choose to set this to "false" if the use

@ -44,6 +44,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.sockjs.SockJsException; import org.springframework.web.socket.sockjs.SockJsException;
import org.springframework.web.socket.sockjs.SockJsService; import org.springframework.web.socket.sockjs.SockJsService;
import org.springframework.web.util.UriComponentsBuilder;
/** /**
* An abstract base class for {@link SockJsService} implementations that provides SockJS * 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 * "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 * 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 * a domain local to the SockJS server. The iframe does need to load the
* SockJS javascript client library and this option allows configuring its * SockJS javascript client library and this option allows configuring that url.
* url. * For more details see the reference documentation.
*
* <p>By default this is set to point to * <p>By default this is set to point to
* "https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js". * "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 * 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 * 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. * load balancing or in Java Servlet containers for the use of an HTTP session.
*
* <p>This is especially important for IE 8,9 that support XDomainRequest -- a modified * <p>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 * 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 * those cases, the SockJS client prefers the "iframe-htmlfile" transport over
* "xdr-streaming" in order to be able to send cookies. * "xdr-streaming" in order to be able to send cookies.
*
* <p>The SockJS protocol also expects a SockJS service to echo back the JSESSIONID * <p>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 * 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. * container this is not necessary since the container takes care of it.
*
* <p>The default value is "true" to maximize the chance for applications to work * <p>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 * 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 * particular). However, an application can choose to set this to "false" if

@ -37188,7 +37188,7 @@ or WebSocket XML namespace:
[[websocket-fallback]] [[websocket-fallback]]
=== Fallback Options === SockJS Fallback Options
As explained in the <<websocket-into-fallback-options,introduction>>, WebSocket is not As explained in the <<websocket-into-fallback-options,introduction>>, WebSocket is not
supported in all browsers yet and may be precluded by restrictive network proxies. 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 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 transport types supported by browser. The client also provides several
configuration options, for example, to specify which transports to include. 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 <origin>`. 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]] See http://docs.spring.io/spring-security/site/docs/3.2.2.RELEASE/reference/htmlsingle/#headers[Section 7.1. "Default Security Headers"]
==== How SockJS Works of the Spring Security documentation for details no how to configure the
An in-depth description of how SockJS works is beyond the scope of this document. setting of the `X-Frame-Options` header. You may also check or watch
This section summarizes a few key facts to aid with understanding. https://jira.spring.io/browse/SEC-2501[SEC-2501] for additional background.
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 If your application adds the `X-Frame-Options` response header (as it should!)
http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html[HTML version of that test] and relies on an iframe-based transport, you will need to set the header value to
showing comments on the right and code on the left. `SAMEORIGIN` or `ALLOW-FROM <origin>`. 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 `<websocket:sockjs>` 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 [NOTE]
WebSocket handshake and establish an actual WebSocket session. HTTP-based ====
transports on the other hand must simulate the WebSocket API and at any time During initial development, do enable the SockJS client `devel` mode that prevents
may use two HTTP connections -- one for server-to-client messages, via the browser from caching SockJS requests (like the iframe) that would otherwise
https://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates[HTTP streaming or long polling], be cached. For details on how to enable it see the
and another for sending client messages to the server via HTTP POST. https://github.com/sockjs/sockjs-client[SockJS client] page.
====
The session id is useful with HTTP transports to associate individual HTTP [[websocket-fallback-sockjs-heartbeat]]
requests that belong to the same SockJS session. The server id is not used in the ==== Heartbeat Support in SockJS
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 servers to send heartbeat messages to preclude proxies
an "open frame" (the letter +o+), a "heartbeat frame" (the letter +h+), or a from concluding a connection is hung. The Spring SockJS configuiration has a property
"close frame" (the letter +c+); while the client sends messages as a JSON-encoded called `heartbeatTime` that can be used to customize the frequency. By default a
array prepended with the letter `a` (e.g. +a["message1","message2"]+). 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 [NOTE]
and loadbalancers from timing out the connection. ====
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]] HTTP streaming and HTTP long polling SockJS transports require a connection to remain
==== Spring's SockJS Support open longer than usual. For an overview of these techniques see
In the Spring Framework, server-side support for the SockJS protocol is provided through a https://spring.io/blog/2012/05/08/spring-mvc-3-2-preview-techniques-for-real-time-updates/[this blog post].
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 In Servlet containers this is done through Servlet 3 async support that
an HTTP connection to remain open longer than usual), Spring's SockJS support allows exiting the Servlet container thread processing a request and continuing
relies on Servlet 3 async support. to write to the response from another thread.
[WARNING] 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].
The Servlet API does not provide notifications when a client disconnects, However, Servlet containers raise an exception on subseqeunt attempts to write
see https://java.net/jira/browse/SERVLET_SPEC-44[SERVLET_SPEC-44]. However, to the response. Since Spring's SockJS Service support sever-sent heartbeats (every
Serlvet containers typically raise an IOException on the next attempt to write 25 seconds by default), that means a client disconnect is usually detected within that
to the response at which point the SockJS session is closed. Since the time period or earlier if a message are sent more frequently.
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 [NOTE]
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 As a result network IO failures may occur simply because a client has disconnected, which
them under a separate log category, see `AbstractSockJsSession#DISCONNECTED_CLIENT_LOG_CATEGORY`. can fill the log with unnecessary stack traces. Spring makes a best effort to identify
A simple one-line message is logged at DEBUG level using this category while a full such network failures that represent client disconnects (specific to each server) and log
stack trace is shown at TRACE level. 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]] [[websocket-stomp]]

Loading…
Cancel
Save