4.3.9.RELEASE Spring Framework
635
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
Configuration EnableWebSocket
public class
WebSocketConfig implements
WebSocketConfigurer { Override
public void
registerWebSocketHandlersWebSocketHandlerRegistry registry { registry.addHandlermyHandler,
myHandler .setAllowedOrigins
http:mydomain.com ;
} Bean
public WebSocketHandler myHandler {
return new
MyHandler; }
}
XML configuration equivalent:
beans xmlns
= http:www.springframework.orgschemabeans
xmlns:xsi =
http:www.w3.org2001XMLSchema-instance xmlns:websocket
= http:www.springframework.orgschemawebsocket
xsi:schemaLocation =
http:www.springframework.orgschemabeans http:www.springframework.orgschemabeansspring-beans.xsd
http:www.springframework.orgschemawebsocket http:www.springframework.orgschemawebsocketspring-websocket.xsd
websocket:handlers allowed-origins
= http:mydomain.com
websocket:mapping path
= myHandler
handler =
myHandler websocket:handlers
bean id
= myHandler
class =
org.springframework.samples.MyHandler beans
26.3 SockJS Fallback Options
As explained in the introduction
, 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 as possible based on the SockJS protocol
version 0.3.3.
Overview of SockJS
The goal of SockJS is to let applications use a WebSocket API but fall back to non-WebSocket alternatives when necessary at runtime, i.e. without the need to change application code.
SockJS consists of: • The
SockJS protocol defined in the form of executable
narrated tests .
• The SockJS JavaScript client
- a client library for use in browsers. • SockJS server implementations including one in the Spring Framework
spring-websocket module.
• As of 4.1 spring-websocket
also provides a SockJS Java client. SockJS is designed for use in browsers. It goes to great lengths to support a wide range of browser
versions using a variety of techniques. For the full list of SockJS transport types and browsers see the
4.3.9.RELEASE Spring Framework
636
SockJS client page. Transports fall in 3 general categories: WebSocket, HTTP Streaming, and HTTP
Long Polling. For an overview of these categories see this blog post
. The SockJS client begins by sending
GET info to obtain basic information from the server. After
that it must decide what transport to use. If possible WebSocket is used. If not, in most browsers there is at least one HTTP streaming option and if not then HTTP long polling is used.
All transport requests have the following URL structure:
http:host:portmyAppmyEndpoint{server-id}{session-id}{transport}
• {server-id}
- useful for routing requests in a cluster but not used otherwise. •
{session-id} - correlates HTTP requests belonging to a SockJS session.
• {transport}
- indicates the transport type, e.g. websocket, xhr-streaming, etc. The WebSocket transport needs only a single HTTP request to do the WebSocket handshake. All
messages thereafter are exchanged on that socket. HTTP transports require more requests. AjaxXHR streaming for example relies on one long-
running request for server-to-client messages and additional HTTP POST requests for client-to-server messages. Long polling is similar except it ends the current request after each server-to-client send.
SockJS adds minimal message framing. For example the server sends the letter o open frame initially, messages are sent as a[message1,message2] JSON-encoded array, the letter h
heartbeat frame if no messages flow for 25 seconds by default, and the letter c close frame to close the session.
To learn more, run an example in a browser and watch the HTTP requests. The SockJS client allows fixing the list of transports so it is possible to see each transport one at a time. The SockJS client also
provides a debug flag which enables helpful messages in the browser console. On the server side enable
TRACE logging for
org.springframework.web.socket . For even more detail refer to the
SockJS protocol narrated test
.
Enable SockJS
SockJS is easy to enable through Java configuration:
Configuration EnableWebSocket
public class
WebSocketConfig implements
WebSocketConfigurer { Override
public void
registerWebSocketHandlersWebSocketHandlerRegistry registry { registry.addHandlermyHandler,
myHandler .withSockJS;
} Bean
public WebSocketHandler myHandler {
return new
MyHandler; }
}
and the XML configuration equivalent:
4.3.9.RELEASE Spring Framework
637
beans xmlns
= http:www.springframework.orgschemabeans
xmlns:xsi =
http:www.w3.org2001XMLSchema-instance xmlns:websocket
= http:www.springframework.orgschemawebsocket
xsi:schemaLocation =
http:www.springframework.orgschemabeans http:www.springframework.orgschemabeansspring-beans.xsd
http:www.springframework.orgschemawebsocket http:www.springframework.orgschemawebsocketspring-websocket.xsd
websocket:handlers websocket:mapping
path =
myHandler handler
= myHandler
websocket:sockjs websocket:handlers
bean id
= myHandler
class =
org.springframework.samples.MyHandler beans
The above is for use in Spring MVC applications and should be included in the configuration of a DispatcherServlet
. However, Spring’s WebSocket and SockJS support does not depend on Spring MVC. It is relatively simple to integrate into other HTTP serving environments with the help of
SockJsHttpRequestHandler .
On the browser side, applications can use the sockjs-client
version 1.0.x that emulates the W3C WebSocket API and communicates with the server to select the best transport option depending on
the browser it’s running in. Review the 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.
HTTP Streaming in IE 8, 9: AjaxXHR 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.
The SockJS client supports AjaxXHR streaming in IE 8 and 9 via Microsoft’s 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 matter. If so the SockJS client prefers AjaxXHR 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 xdr-streaming
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
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.
4.3.9.RELEASE Spring Framework
638
See Section 7.1. Default Security Headers
of the Spring Security documentation for details on how to configure the setting of the
X-Frame-Options header. You may also check or watch
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 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. In Java config this can be done as shown below. The XML namespace provides a similar option via
the websocket:sockjs
element:
Configuration EnableWebSocket
public class
WebSocketConfig implements
WebSocketConfigurer { Override
public void
registerStompEndpointsStompEndpointRegistry registry { registry.addEndpoint
portfolio .withSockJS
.setClientLibraryUrl http:localhost:8080myappjssockjs-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 SockJS client
page.
Heartbeat Messages
The SockJS protocol requires servers to send heartbeat messages to preclude proxies from concluding a connection is hung. The Spring SockJS configuration 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
IETF recommendation
for public Internet applications.
Note
When using STOMP over WebSocketSockJS, 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.
Servlet 3 Async Requests
HTTP streaming and HTTP long polling SockJS transports require a connection to remain open longer than usual. For an overview of these techniques see
this blog post .
4.3.9.RELEASE Spring Framework
639
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 that the Servlet API does not provide notifications for a client that has gone away, see SERVLET_SPEC-44
. However, Servlet containers raise an exception on subsequent attempts to write to the response. Since Spring’s SockJS Service supports sever-sent heartbeats every 25 seconds by
default, that means a client disconnect is usually detected within that time period or earlier if messages 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 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.
CORS Headers for SockJS
If you allow cross-origin requests see the section called “Configuring allowed origins”, the SockJS protocol uses CORS for cross-domain support in the XHR streaming and polling transports. Therefore
CORS headers are added automatically unless the presence of CORS headers in the response is detected. So if an application is already configured to provide CORS support, e.g. through a Servlet
Filter, Spring’s SockJsService will skip this part. It is also possible to disable the addition of these CORS headers via the
suppressCors property in
Spring’s SockJsService. The following is the list of headers and values expected by SockJS:
• Access-Control-Allow-Origin
- initialized from the value of the Origin request header. •
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
as well as the TransportType
enum in the source code. Alternatively if the CORS configuration allows it consider excluding URLs with the SockJS endpoint
prefix thus letting Spring’s SockJsService
handle it.
SockJS Client
A SockJS Java client is provided in order to connect to remote SockJS endpoints without using a browser. This can be especially useful when there is a need for bidirectional communication between
2 servers over a public network, i.e. where network proxies may preclude the use of the WebSocket
4.3.9.RELEASE Spring Framework
640
protocol. A SockJS Java client is also very useful for testing purposes, for example to simulate a large number of concurrent users.
The SockJS Java client supports the websocket, xhr-streaming, and xhr-polling transports. The remaining ones only make sense for use in a browser.
The WebSocketTransport
can be configured with: •
StandardWebSocketClient in a JSR-356 runtime
• JettyWebSocketClient
using the Jetty 9+ native WebSocket API • Any implementation of Spring’s
WebSocketClient An
XhrTransport by definition supports both xhr-streaming and xhr-polling since from a client
perspective there is no difference other than in the URL used to connect to the server. At present there are two implementations:
• RestTemplateXhrTransport
uses Spring’s RestTemplate
for HTTP requests. •
JettyXhrTransport uses Jetty’s
HttpClient for HTTP requests.
The example below shows how to create a SockJS client and connect to a SockJS endpoint:
ListTransport transports = new
ArrayList2; transports.add
new WebSocketTransport
new StandardWebSocketClient;
transports.add new
RestTemplateXhrTransport; SockJsClient sockJsClient =
new SockJsClienttransports;
sockJsClient.doHandshake new
MyWebSocketHandler, ws:example.com:8080sockjs
;
Note
SockJS uses JSON formatted arrays for messages. By default Jackson 2 is used and needs to be on the classpath. Alternatively you can configure a custom implementation of
SockJsMessageCodec and configure it on the
SockJsClient .
To use the SockJsClient for simulating a large number of concurrent users you will need to configure the underlying HTTP client for XHR transports to allow a sufficient number of connections and threads.
For example with Jetty:
HttpClient jettyHttpClient = new
HttpClient; jettyHttpClient.setMaxConnectionsPerDestination1000;
jettyHttpClient.setExecutor new
QueuedThreadPool1000;
Consider also customizing these server-side SockJS related properties see Javadoc for details:
Configuration
public class
WebSocketConfig extends
WebSocketMessageBrokerConfigurationSupport { Override
public void
registerStompEndpointsStompEndpointRegistry registry { registry.addEndpoint
sockjs .withSockJS
.setStreamBytesLimit512 1024 .setHttpMessageCacheSize1000
.setDisconnectDelay30 1000; }
... }
4.3.9.RELEASE Spring Framework
641
26.4 STOMP Over WebSocket Messaging Architecture