|
|
@ -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]] |
|
|
|