20. WebSocket Support

This part of the reference documentation covers Spring Framework’s support for WebSocket-style messaging in web applications including use of STOMP as an application level WebSocket sub-protocol.

Section 20.1, “Introduction” establishes a frame of mind in which to think about WebSocket, covering adoption challenges, design considerations, and thoughts on when it is a good fit.

Section 20.2, “WebSocket Server” reviews the Spring WebSocket API on the server-side while Section 20.3, “Fallback Options” explains the SockJS protocol and shows how to configure and use it.

Section 20.4.1, “Overview of STOMP” introduces the STOMP messaging protocol. Section 20.4.2, “Enable STOMP (over WebSocket)” demonstrates how to configure STOMP support in Spring. Section 20.4.3, “Overview of STOMP Message Handling” explains how to use it including writing annotated message handling methods, sending messages, choosing message broker options, as well as working with the special "user" destinations. Finally the section called “Testing Message Handling Controllers” lists three approaches to testing STOMP/WebSocket applications.

20.1 Introduction

The WebSocket protocol RFC 6455 defines an important new capability for web applications: full-duplex, two-way communication between client and server. It is an exciting new capability on the heels of a long history of techniques to make the web more interactive including Java applets, XMLHttpRequest, Adobe Flash, ActiveXObject, various Comet techniques, server-sent events, and others.

A proper introduction of the WebSocket protocol is beyond the scope of this document. At a minimum however it’s important to understand that HTTP is used only for the initial handshake, which relies on a mechanism built into HTTP to request a protocol upgrade (or in this case a protocol switch) to which the server can respond with HTTP status 101 (switching protocols) if it agrees. Assuming the handshake succeeds the TCP socket underlying the HTTP upgrade request remains open and both client and server can use it to send messages to each other.

Spring Framework 4 includes a new spring-websocket module with comprehensive WebSocket support. It is compatible with the Java WebSocket API standard (JSR-356) and also provides additional value-add as explained in the rest of the introduction.

20.1.1 Fallback Options

An important challenge to adoption is the lack of support for WebSocket in some browsers. Notably the first Internet Explorer version to support WebSocket is version 10 (see http://caniuse.com/websockets for support by browser versions). Furthermore, some restrictive proxies may be configured in ways that either preclude the attempt to do HTTP upgrade or otherwise break connection after some time because it has remained opened for too long. A good overview on this topic from Peter Lubbers is available in the InfoQ article "How HTML5 Web Sockets Interact With Proxy Servers".

Therefore to build a WebSocket application today, fallback options are required to simulate the WebSocket API where necessary. Spring Framework provides such transparent fallback options based on the SockJS protocol. These options can be enabled through configuration and do not require modifying the application otherwise.

20.1.2 Messaging Architecture

Aside from short-to-midterm adoption challenges, using WebSocket brings up important design considerations that are important to recognize early on, especially in contrast to what we know about building web applications today.

Today REST is a widely accepted, understood, and supported architecture for building web applications. It is an architecture that relies on having many URLs (nouns), a handful of HTTP methods (verbs), and other principles such as using hypermedia (links), remaining stateless, etc.

By contrast a WebSocket application may use a single URL only for the initial HTTP handshake. All messages thereafter share and flow on the same TCP connection. This points to an entirely different, asynchronous, event-driven, messaging architecture. One that is much closer to traditional messaging applications (e.g. JMS, AMQP).

Spring Framework 4 includes a new spring-messaging module with key abstractions from the Spring Integration project such as Message, MessageChannel, MessageHandler and others that can serve as a foundation for such a messaging architecture. The module also includes a set of annotations for mapping messages to methods, similar to the Spring MVC annotation based programming model.

20.1.3 Sub-Protocol Support

WebSocket does imply a messaging architecture but does not mandate the use of any specific messaging protocol. It is a very thin layer over TCP that transforms a stream of bytes into a stream of messages (either text or binary) and not much more. It is up to applications to interpret the meaning of a message.

Unlike HTTP, which is an application-level protocol, in the WebSocket protocol there is simply not enough information in an incoming message for a framework or container to know how to route it or process it. Therefore WebSocket is arguably too low level for anything but a very trivial application. It can be done, but it will likely lead to creating a framework on top. This is comparable to how most web applications today are written using a web framework rather than the Servlet API alone.

For this reason the WebSocket RFC defines the use of sub-protocols. During the handshake, client and server can use the header Sec-WebSocket-Protocol to agree on a sub-protocol, i.e. a higher, application-level protocol to use. The use of a sub-protocol is not required, but even if not used, applications will still need to choose a message format that both client and server can understand. That format can be custom, framework-specific, or a standard messaging protocol.

Spring Framework provides support for using STOMP — a simple, messaging protocol originally created for use in scripting languages with frames inspired by HTTP. STOMP is widely support and well suited for use over WebSocket and over the web.

20.1.4 When To Use WebSocket?

With all the design considerations surrounding the use of WebSocket, it is reasonable to ask when is it appropriate to use?

The best fit for WebSocket is in web applications where client and server need to exchange events at high frequency and at low latency. Prime candidates include but are not limited to applications in finance, games, collaboration, and others. Such applications are both very sensitive to time delays and also need to exchange a wide variety of messages at high frequency.

For other application types, however, this may not be the case. For example, a news or social feed that shows breaking news as they become available may be perfectly okay with simple polling once every few minutes. Here latency is important, but it is acceptable if the news takes a few minutes to appear.

Even in cases where latency is crucial, if the volume of messages is relatively low (e.g. monitoring network failures) the use of long polling should be considered as a relatively simple alternative that works reliably and is comparable by efficiency (again assuming the volume of messages is relatively low).

It is the combination of both low latency and high frequency of messages that can make the use of the WebSocket protocol critical. Even in such applications, the choice remains whether all client-server communication should be done through WebSocket messages as opposed to using HTTP and REST? The answer is going to vary by application, however, it is likely that some functionality may be exposed over both WebSocket and as a REST API in order to provide clients with alternatives. Furthermore, a REST API call may need to broadcast a message to interested clients connected via WebSocket.

Spring Framework allows @Controller classes to have both HTTP request handling and WebSocket message handling methods. Furthermore, a Spring MVC request handling method, or any application method for that matter, can easily broadcast a message to all interested WebSocket clients or to a specific user.

20.2 WebSocket Server

The Spring Framework provides a WebSocket API designed to adapt to various WebSocket engines. For example, it runs on JSR-356 runtimes such as Tomcat (7.0.47+) and GlassFish (4.0+) but can also adapt to other WebSocket runtimes such as the Jetty (9.0+) native WebSocket support.

[Note]Note

As explained in the introduction, direct use of a WebSocket API is too low level for applications — until assumptions are made about the format of a message there is little a framework can do to interpret messages or route them via annotations. This is why applications should consider using a sub-protocol and Spring’s STOMP over WebSocket support.

When using a higher level protocol, the details of the WebSocket API become less relevant, much like the details of TCP communication are not exposed to applications when using HTTP. Nevertheless this section covers the details of using WebSocket directly.

20.2.1 Create and Configure a WebSocketHandler

Creating a WebSocket server is as simple as implementing WebSocketHandler or more likely extending either TextWebSocketHandler or BinaryWebSocketHandler:

import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;

public class MyHandler extends TextWebSocketHandler {

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // ...
    }

}

There is dedicated WebSocket Java-config and XML namespace support for mapping the above WebSocket handler at a specific URL:

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 registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

XML configuration equivalent:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
    </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 support does not depend on Spring MVC. It is relatively simple to integrate a WebSocketHandler into other HTTP serving environments with the help of WebSocketHttpRequestHandler.

20.2.2 Customizing the WebSocket Handshake

The easiest way to customize the initial HTTP WebSocket handshake request is through a HandshakeInterceptor, which exposes "before" and "after" the handshake methods. Such an interceptor can be used to preclude the handshake or to make any attributes available to the WebSocketSession. For example, there is a built-in interceptor for passing HTTP session attributes to the WebSocket session:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/myHandler")
            .addInterceptors(new HttpSessionHandshakeInterceptor());
    }

}

And the XML configuration equivalent:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
        <websocket:handshake-interceptors>
            <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

A more advanced option is to extend the DefaultHandshakeHandler that performs the steps of the WebSocket handshake, including validating the client origin, negotiating a sub-protocol, and others. An application may also need to use this option if it needs to configure a custom RequestUpgradeStrategy in order to adapt to a WebSocket server engine and version that is not yet supported (also see Section 20.2.4, “Deployment Considerations” for more on this subject). Both the Java-config and XML namespace make it possible to configure a custom HandshakeHandler.

20.2.3 WebSocketHandler Decoration

Spring provides a WebSocketHandlerDecorator base class that can be used to decorate a WebSocketHandler with additional behavior. Logging and exception handling implementations are provided and added by default when using the WebSocket Java-config or XML namespace. The ExceptionWebSocketHandlerDecorator catches all uncaught exceptions arising from any WebSocketHandler method and closes the WebSocket session with status 1011 that indicates a server error.

20.2.4 Deployment Considerations

The Spring WebSocket API is easy to integrate into a Spring MVC application where the DispatcherServlet serves both HTTP WebSocket handshake as well as other HTTP requests. It is also easy to integrate into other HTTP processing scenarios by invoking WebSocketHttpRequestHandler. This is convenient and easy to understand. However, special considerations apply with regards to JSR-356 runtimes.

The Java WebSocket API (JSR-356) provides two deployment mechanisms. The first involves a Servlet container classpath scan (Servlet 3 feature) at startup; and the other is a registration API to use at Servlet container initialization. Neither of these mechanism make it possible to use a single "front controller" for all HTTP processing — including WebSocket handshake and all other HTTP requests — such as Spring MVC’s DispatcherServlet.

This is a significant limitation of JSR-356 that Spring’s WebSocket support addresses by providing a server-specific RequestUpgradeStrategy even when running in a JSR-356 runtime. At present such support is available on Tomcat 7.0.47+, Jetty 9.0+, and GlassFish 4.0+. Additional support will be added as more WebSocket runtimes become available.

[Note]Note

A request to overcome the above limitation in the Java WebSocket API has been created and can be followed at WEBSOCKET_SPEC-211. Also note that Tomcat and Jetty already provide native API alternatives that makes it easy to overcome the limitation. We are hopeful that more servers will follow their example regardless of when it is addressed in the Java WebSocket API.

A secondary consideration is that Servlet containers with JSR-356 support are expected to perform an SCI scan that can slow down application startup, in some cases dramatically. If a significant impact is observed after an upgrade to a Servlet container version with JSR-356 support, it should be possible to selectively enable or disable web fragments (and SCI scanning) through the use of an <absolute-ordering /> element in web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <absolute-ordering/>

</web-app>

You can then selectively enable web fragments by name, such as Spring’s own SpringServletContainerInitializer that provides support for the Servlet 3 Java initialization API, if required:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <absolute-ordering>
        <name>spring_web</name>
    </absolute-ordering>

</web-app>

20.2.5 Configuring the WebSocket Engine

Each underlying WebSocket engine exposes configuration properties that control runtime characteristics such as the size of message buffer sizes, idle timeout, and others.

For Tomcat, WildFly, and Glassfish add a WebSocketContainerFactoryBean to your WebSocket Java config:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Bean
    public WebSocketContainerFactoryBean createWebSocketContainer() {
        WebSocketContainerFactoryBean container = new WebSocketContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }

}

or WebSocket XML namespace:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <bean class="org.springframework...WebSocketContainerFactoryBean">
        <property name="maxTextMessageBufferSize" value="8192"/>
        <property name="maxBinaryMessageBufferSize" value="8192"/>
    </bean>

</beans>

For Jetty, you’ll need to supply a pre-configured Jetty WebSocketServerFactory and plug that into Spring’s DefaultHandshakeHandler through your WebSocket Java config:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(echoWebSocketHandler(),
            "/echo").setHandshakeHandler(handshakeHandler());
    }

    @Bean
    public DefaultHandshakeHandler handshakeHandler() {

        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(600000);

        return new DefaultHandshakeHandler(
                new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
    }

}

or WebSocket XML namespace:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/echo" handler="echoHandler"/>
        <websocket:handshake-handler ref="handshakeHandler"/>
    </websocket:handlers>

    <bean id="handshakeHandler" class="org.springframework...DefaultHandshakeHandler">
        <constructor-arg ref="upgradeStrategy"/>
    </bean>

    <bean id="upgradeStrategy" class="org.springframework...JettyRequestUpgradeStrategy">
        <constructor-arg ref="serverFactory"/>
    </bean>

    <bean id="serverFactory" class="org.eclipse.jetty...WebSocketServerFactory">
        <constructor-arg>
            <bean class="org.eclipse.jetty...WebSocketPolicy">
                <constructor-arg value="SERVER"/>
                <property name="inputBufferSize" value="8092"/>
                <property name="idleTimeout" value="600000"/>
            </bean>
        </constructor-arg>
    </bean>

</beans>

20.3 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.

20.3.1 Enable SockJS

SockJS is easy to enable through a configuration:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler").withSockJS();
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

and the XML configuration equivalent:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket-4.0.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 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.

20.3.2 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 test suite, with comments explaining the protocol. There is also an HTML version of that test showing comments on the right and code on the left.

The SockJS client issues HTTP requests with this URL structure:

SockJS URL. 

http://host:port/{path-to-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 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.

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"]).

By default the server sends a heartbeat frame every 25 seconds to keep proxies and loadbalancers from timing out the connection.

20.3.3 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]Warning

Servlet 3 async support does not expose a notification when a client disconnects, e.g. a browser tab is closed, page is refreshed, etc (see SERVLET_SPEC-44). However, the Serlvet container will usually raise an IOException on the next attempt to write to the response; at which point the SockJS session will be closed. Since the SockJsService sends a heartbeat every 25 seconds, typically a disconnected client will be detected within that time period.

20.4 STOMP Messaging

The WebSocket protocol defines two main types of messages — text and binary — but leaves their content undefined. Instead it’s expected that client and server may agree on using a sub-protocol, i.e. a higher-level protocol that defines the message content. Using a sub-protocol is optional but either way client and server both need to understand how to interpret messages.

20.4.1 Overview of STOMP

STOMP is a simple messaging protocol originally created for scripting languages (such as Ruby, Python and Perl) to connect to enterprise message brokers. It is designed to address a subset of commonly used patterns in messaging protocols. STOMP can be used over any reliable 2-way streaming network protocol such as TCP and WebSocket.

STOMP is a frame based protocol with frames modelled on HTTP. This is the structure of a frame:

COMMAND
header1:value1
header2:value2

Body^@

For example, a client can use the SEND command to send a message or the SUBSCRIBE command to express interest in receiving messages. Both of these commands require a "destination" header that indicates where to send a message to, or likewise what to subscribe to.

Here is an example of a client sending a request to buy stock shares:

SEND
destination:/queue/trade
content-type:application/json
content-length:44

{"action":"BUY","ticker":"MMM","shares",44}^@

Here is an example of a client subscribing to receive stock quotes:

SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*

^@
[Note]Note

The meaning of a destination is intentionally left opaque in the STOMP spec. It can be any string and it’s entirely up to STOMP servers to define the semantics and the syntax of the destinations that they support. It is very common however, for destinations to be path-like strings where "/topic/.." implies publish-subscribe (one-to-many) and "/queue/" to implies point-to-point (one-to-one) message exchanges.

STOMP servers can use the MESSAGE command to broadcast messages to all subscribers. Here is an example of a server sending a stock quote to a subscribed client:

MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM

{"ticker":"MMM","price":129.45}^@
[Note]Note

It’s important to know that a server cannot send unsolicited messages. All messages from a server must be in response to a specific client subscription and the "subscription-id" header of the server message must match the "id" header of the client subscription.

The above overview is intended to provide the most basic understanding of the STOMP protocol. It is recommended to review the protocol specification, which is easy to follow and manageable in terms of size.

The following summarizes the benefits for an application from using STOMP over WebSocket:

  • Standard message format
  • Application-level protocol with support for common messaging patterns
  • Client-side support, e.g. stomp.js, msgs.js
  • The ability to interpret, route, and process messages on both client and server-side
  • The option to plug a message broker — RabbitMQ, ActiveMQ, many others — to broadcast messages (explained later)

Most importantly the use of STOMP (vs plain WebSocket) enables the Spring Framework to provide a programming model for application-level use in the same way that Spring MVC provides a programming model based on HTTP.

20.4.2 Enable STOMP (over WebSocket)

The Spring Framework provides support for using STOMP over WebSocket through the spring-messaging and spring-websocket modules. It’s easy to enable it.

Here is an example of configuring a STOMP WebSocket endpoint with SockJS fallback options. The endpoint is available for clients to connect to at URL path /portfolio:

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS();
    }

    // ...

}

XML configuration equivalent:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">

    <websocket:message-broker>
        <websocket:stomp-endpoint path="/portfolio">
            <websocket:sockjs/>
        </websocket:stomp-endpoint>
        ...
    </websocket:message-broker>

</beans>

On the browser side, a client might connect as follows using stomp.js and the sockjs-client:

var socket = new SockJS("/spring-websocket-portfolio/portfolio");
var stompClient = Stomp.over(socket);

Or if connecting via WebSocket (without SockJS):

var socket = new WebSocket("/spring-websocket-portfolio/portfolio");
var stompClient = Stomp.over(socket);

20.4.3 Overview of STOMP Message Handling

When a STOMP endpoint is configured, the Spring application effectively becomes the broker to connected clients, handling incoming messages and broadcasting messages back to them. This part of the documentation describes how STOMP messages are handled within the application.

As mentioned in the introduction the spring-messaging module contains key abstractions from the Spring Integration project, including Message, MessageChannel, MessageHandler and a few others.

[Note]Note

Spring Integration 4 will be the first version to start using the abstractions from the package structure of the spring-messaging module as opposed to its own present packages. Spring Integration also provides many additional abstractions and implementations in support of the well-known EAI patterns (enterprise integration patterns).

MessageChannel is a simple contract for passing messages between components without creating tight coupling among them. SubscribableChannel extends it, with the ability to register subscribers; and ExecutorSubscribableChannel is an implementation that passes messages to subscribers in the same thread or a different thread depending on whether it has been provided with a java.util.concurrent.Executor. This enables assembling message handling flows from various components and modifying them through configuration.

The provided Java-config @EnableWebSocketMessageBroker and XML namespace <websocket:message-broker> each put together a default message handling flow for applications to use, as explained next. This flow can be modified, customized, or extended. For example, an application can add a ChannelInterceptor to any message channel in order to intercept messages passing through it; it can register additional message handling components, alternate between synchronous and asynchronous message passing, and so on.

Incoming client STOMP messages are passed to a message channel with the name "clientInboundChannel". By default the messages are routed to annotated methods as well as to a "simple" message broker. This simple message broker automatically records subscriptions, in-memory, and broadcasts messages as necessary. As explained later, you can also use a full-featured message broker (e.g. RabbitMQ, ActiveMQ, and any other broker that supports STOMP) to manage subscriptions and broadcast messages.

Below is example configuration:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic/");
        registry.setApplicationDestinationPrefixes("/app");
    }

}

XML configuration equivalent:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">

    <websocket:message-broker application-destination-prefix="/app">
        <websocket:stomp-endpoint path="/portfolio" />
            <websocket:sockjs/>
        </websocket:stomp-endpoint>
        <websocket:simple-broker prefix="/topic"/>
    </websocket:message-broker>

</beans>

The configuration example assigns destination prefixes — /app for filtering messages to annotated methods and /topic for messages to the broker. The examples below demonstrate how this can be used.

The destination prefix should not be included in annotation mappings. For example, this method handles messages to destination /app/greetings:

@Controller
public class GreetingController {

    @MessageMapping("/greetings") {
    public void handle(String greeting) {
        // ...
    }

}

The method accepts a String extracted from the payload of the message, possibly converted based on its content type. The method can also return a value, which is wrapped as the payload of a new message and sent to a message channel named "brokerChannel" (which is used for sending messages to the broker from within the application). The new message is sent to the same destination as that of the client message, but with the default prefix /topic (you can also use @SendTo for any other target destination):

@Controller
public class GreetingController {

    @MessageMapping("/greetings") {
    public String handle(String greeting) {
        return "[" + getTimestamp() + ": " + greeting;
    }

}

As a result, to put it all together, a client sends a greeting message to destination /app/greetings. The message is routed to GreetingController, which enriches the greeting with a timestamp and sends a new message to the broker with destination /topic/greetings. The broker then broadcasts the message to all subscribed, connected clients.

Annotation-based Message Handling

The @MessageMapping annotation is supported on methods of @Controller-annotated classes. It can be used for mapping methods to path-like message destinations. It is also possible to combine with a type-level @MessageMapping for expressing shared mappings across all annotated methods within a controller.

Destination mappings can contain Ant-style patterns (e.g. "/foo*", "/foo/**") and template variables (e.g. "/foo/{id}"), which can then be accessed via @DestinationVariable method arguments. This should be familiar to Spring MVC users, in fact the same AntPathMatcher is used for matching destinations based on patterns and for extracting template variables.

The following method arguments are supported for @MessageMapping methods:

  • Message method argument to get access to the complete message being processed.
  • @Payload-annotated argument for access to the payload of a message, converted with a org.springframework.messaging.converter.MessageConverter. The presence of the annotation is not required since it is assumed by default. Payload method arguments annotated with Validation annotations (like @Validated) will be subject to JSR-303 validation.
  • @Header-annotated arguments for access to a specific header value along with type conversion using an org.springframework.core.convert.converter.Converter if necessary.
  • @Headers-annotated method argument that must also be assignable to java.util.Map for access to all headers in the message.
  • MessageHeaders method argument for getting access to a map of all headers.
  • MessageHeaderAccessor, SimpMessageHeaderAccessor, or StompHeaderAccessor for access to headers via typed accessor methods.
  • @DestinationVariable-annotated arguments for access to template variables extracted from the message destination. Values will be converted to the declared method argument type as necessary.
  • java.security.Principal method arguments reflecting the user logged in at the time of the WebSocket HTTP handshake.

The return value from an @MessageMapping method is converted with a org.springframework.messaging.converter.MessageConverter and used as the body of a new message that is then sent, by default, to the "brokerChannel" with the same destination as the client message but using the prefix "/topic" by default. An @SendTo message level annotation can be used to specify any other destination instead.

An @SubscribeMapping annotation can also be used to map subscription requests to @Controller methods. It is supported on the method level, but can also be combined with a type level @MessageMapping annotation that expresses shared mappings across all message handling methods within the same controller.

By default the return value from an @SubscribeMapping method is sent as a message directly back to the connected client and does not pass through the broker. This is useful for implementing request-reply message interactions; for example, to fetch application data when the application UI is being initialized. Or alternatively an @SubscribeMapping method can be annotated with @SendTo in which case the resulting message is sent to the "brokerChannel" using the specified target destination.

Sending Messages From Anywhere

What if you wanted to send messages to connected clients from any part of the application? Any application component can send messages to the "brokerChannel". The easist way to do that is to have a SimpMessagingTemplate injected, and use it to send messages. Typically it should be easy to have it injected by type, for example:

@Controller
public class GreetingController {

    private SimpMessagingTemplate template;

    @Autowired
    public GreetingController(SimpMessagingTemplate template) {
        this.template = template;
    }

    @RequestMapping(value="/greetings", method=POST)
    public void greet(String greeting) {
        String text = "[" + getTimestamp() + "]:" + greeting;
        this.template.convertAndSend("/topic/greetings", text);
    }

}

But it can also be qualified by its name "brokerMessagingTemplate" if another bean of the same type exists.

Simple Message Broker

The built-in, simple, message broker handles subscription requests from clients, stores them in memory, and broadcasts messages to connected clients with matching destinations. The broker supports path-like destinations, including subscriptions to Ant-style destination patterns.

Using a Full-Featured Message Broker

The simple broker is great for getting started but supports only a subset of STOMP commands (e.g. no acks, receipts, etc), relies on a simple message sending loop, and is not suitable for clustering.

Instead, applications can use a full-featured message broker and use it for managing client subscriptions and broadcasting messages; while annotated methods can still be used for application processing. In other words, all else remains the same, except a full-featured broker replaces the simple broker.

Check your message broker STOMP documentation (e.g. RabbitMQ, ActiveMQ), install and run the broker with STOMP support enabled. Then enable the STOMP broker relay in the Spring configuration as an alternative to the simple broker.

Below is example configuration that enables use of a full-featured broker:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableStompBrokerRelay("/topic/", "/queue/");
        registry.setApplicationDestinationPrefixes("/app");
    }

}

XML configuration equivalent:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">

    <websocket:message-broker application-destination-prefix="/app">
        <websocket:stomp-endpoint path="/portfolio" />
            <websocket:sockjs/>
        </websocket:stomp-endpoint>
        <websocket:stomp-broker-relay prefix="/topic,/queue" />
    </websocket:message-broker>

</beans>

The STOMP "broker relay" from the above configuration manages TCP connections to the external broker, and forwards messages with matching destination prefixes to it. Likewise, any messages received from the external broker are matched and routed to connected clients.

In effect, messages are now broadcast through a full-featured, robust and scalable message broker.

Handling Messages to User Destinations

An application can also send messages targeting a specific user.

To do so a user must first be authenticated. Although the STOMP CONNECT frame has authentication headers when used over WebSocket, it makes more sense to use the same HTTP-based authentication already used to secure the application.

For example, an application can use Spring Security as usual to protect HTTP URLs, including paths to STOMP WebSocket endpoint(s). The authenticanted user, based on the value returned from HttpServletRequest.getUserPrincipal(), will be saved in the WebSocket session and subsequently added as a header named user to all incoming messages for that STOMP/WebSocket session.

Spring’s STOMP support recognizes destinations prefixed with /user/. For example, a client can subscribe to destination /user/position-updates. This destination will be handled by the UserDestinationMessageHandler and transformed into a destination unique to the user’s session, e.g. /user/position-updates-123. This provides the convenience of subscribing to a generically named destination, while also ensuring that it doesn’t "collide" with any other user that also subscribes to /user/position-updates in order to receive stock position updates unique to them.

On the sending side, messages can be sent to a destination such as /user/{username}/position-updates, which in turn will be translated by the UserDestinationMessageHandler into the same unique destination belonging to the specified user name.

This allows any component within the application to send messages to a specific user without necessarily knowing anything more than their name and a generic destination.

When this is used with an external message broker, check the broker documentation on how to manage inactive queues, so that when the user session is over, all unique user queues are removed. For example, RabbitMQ creates auto-delete queues when destinations like /exchange/amq.direct/position-updates are used. So in that case the client could subscribe to /user/exchange/amq.direct/position-updates. ActiveMQ has configuration options for purging inactive destinations.

Testing Message Handling Controllers

There are two main approaches to testing applications using Spring’s STOMP over WebSocket support. The first is to write server-side tests verifying the functionality of controllers and their annotated message handling methods. The second is to write full end-to-end tests that involve running a client and a server.

The two approaches are not mutually exclusive. On the contrary each has a place in an overall test strategy. Server-side tests are more focused and easier to write and maintain. End-to-end integration tests on the other hand are more complete and test much more but they’re also more involved to write and maintain.

The simplest form of server-side tests is to write controller unit tests. However this is not useful enough since much of what a controller does depends on its annotations. Pure unit tests simply can’t test that.

Ideally controllers under test should be invoked as they are at runtime, much like the approach to testing controllers handling HTTP requests using the Spring MVC Test framework. i.e. without running a Servlet container but relying on the Spring Framework to invoke the annotated controllers. Just like with Spring MVC Test here there are two two possible alternatives, either using a "context-based" or "standalone" setup:

  1. Load the actual Spring configuration with the help of the Spring TestContext framework, inject "clientInboundChannel" as a test field, and use it to send messages to be handled by controller methods.
  2. Manually set up the minimum Spring framework infrastructure required to invoke controllers (namely the SimpAnnotationMethodMessageHandler) and pass messages for controllers directly to it.

Both of these setup scenarios are demonstrated in the tests for the stock portfolio sample application.

The second approach is to create end-to-end integration tests. For that you will need to run a WebSocket server in embedded mode and connect to it as a WebSocket client sending WebSocket messages containing STOMP frames. The tests for the stock portfolio sample application also demonstrate this approach using Tomcat as the embedded WebSocket server and a simple STOMP client for test purposes.