|
|
|
@ -984,11 +984,12 @@ public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport |
|
|
|
|
|
|
|
|
|
|
|
[[websocket-stomp]] |
|
|
|
[[websocket-stomp]] |
|
|
|
== STOMP Over WebSocket Messaging Architecture |
|
|
|
== STOMP Over WebSocket Messaging Architecture |
|
|
|
The WebSocket protocol defines two main types of messages -- text and binary -- |
|
|
|
The WebSocket protocol defines two types of messages, text and binary, but their |
|
|
|
but leaves their content undefined. Instead it's expected that the client and |
|
|
|
content is undefined. It's expected that the client and server may agree on using |
|
|
|
server may agree on using a sub-protocol, i.e. a higher-level protocol that defines |
|
|
|
a sub-protocol (i.e. a higher-level protocol) to define message semantics. |
|
|
|
the message content. Using a sub-protocol is optional but either way the client |
|
|
|
While the use of a sub-protocol with WebSocket is completely optional either way |
|
|
|
and server both need to understand how to interpret messages. |
|
|
|
client and server will need to agree on some kind of protocol to help interpret |
|
|
|
|
|
|
|
messages. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -996,12 +997,14 @@ and server both need to understand how to interpret messages. |
|
|
|
=== Overview of STOMP |
|
|
|
=== Overview of STOMP |
|
|
|
http://stomp.github.io/stomp-specification-1.2.html#Abstract[STOMP] is a simple |
|
|
|
http://stomp.github.io/stomp-specification-1.2.html#Abstract[STOMP] is a simple |
|
|
|
text-oriented messaging protocol that was originally created for scripting languages |
|
|
|
text-oriented messaging protocol that was originally created for scripting languages |
|
|
|
(such as Ruby, Python, and Perl) to connect to enterprise message brokers. It is |
|
|
|
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 |
|
|
|
designed to address a subset of commonly used messaging patterns. STOMP can be |
|
|
|
can be used over any reliable 2-way streaming network protocol such as TCP and WebSocket. |
|
|
|
used over any reliable 2-way streaming network protocol such as TCP and WebSocket. |
|
|
|
|
|
|
|
Although STOMP is a text-oriented protocol, the payload of messages can be |
|
|
|
|
|
|
|
either text or binary. |
|
|
|
|
|
|
|
|
|
|
|
STOMP is a frame based protocol with frames modeled on HTTP. This is the |
|
|
|
STOMP is a frame based protocol whose frames are modeled on HTTP. The structure |
|
|
|
structure of a frame: |
|
|
|
of a STOMP frame: |
|
|
|
|
|
|
|
|
|
|
|
---- |
|
|
|
---- |
|
|
|
COMMAND |
|
|
|
COMMAND |
|
|
|
@ -1011,23 +1014,28 @@ header2:value2 |
|
|
|
Body^@ |
|
|
|
Body^@ |
|
|
|
---- |
|
|
|
---- |
|
|
|
|
|
|
|
|
|
|
|
For example, a client can use the +SEND+ command to send a message or the |
|
|
|
Clients can use the +SEND+ or +SUBSCRIBE+ commands to send or subscribe for |
|
|
|
+SUBSCRIBE+ command to express interest in receiving messages. Both of these commands |
|
|
|
messages along with a +"destination"+ header that describes what the |
|
|
|
require a +"destination"+ header that indicates where to send a message, or likewise |
|
|
|
message is about and who should receive it. This enables a simple |
|
|
|
what to subscribe to. |
|
|
|
publish-subscribe mechanism that can be used to send messages through the broker |
|
|
|
|
|
|
|
to other connected clients or to send messages to the server to request that |
|
|
|
|
|
|
|
some work be performed. |
|
|
|
|
|
|
|
|
|
|
|
Here is an example of a client sending a request to buy stock shares: |
|
|
|
When using Spring's STOMP support, the Spring WebSocket application acts |
|
|
|
|
|
|
|
as the STOMP broker to clients. Messages are routed to `@Controller` message-handling |
|
|
|
|
|
|
|
methods or to a simple, in-memory broker that keeps track of subscriptions and |
|
|
|
|
|
|
|
broadcasts messages to subscribed users. You can also configure Spring to work |
|
|
|
|
|
|
|
with a dedicated STOMP broker (e.g. RabbitMQ, ActiveMQ, etc) for the actual |
|
|
|
|
|
|
|
broadcasting of messages. In that case Spring maintains |
|
|
|
|
|
|
|
TCP connections to the broker, relays messages to it, and also passes messages |
|
|
|
|
|
|
|
from it down to connected WebSocket clients. Thus Spring web applications can |
|
|
|
|
|
|
|
rely on unified HTTP-based security, common validation, and a familiar programming |
|
|
|
|
|
|
|
model message-handling work. |
|
|
|
|
|
|
|
|
|
|
|
---- |
|
|
|
Here is an example of a client subscribing to receive stock quotes which |
|
|
|
SEND |
|
|
|
the server may emit periodically e.g. via a scheduled task sending messages |
|
|
|
destination:/queue/trade |
|
|
|
through a `SimpMessagingTemplate` to the broker: |
|
|
|
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 |
|
|
|
SUBSCRIBE |
|
|
|
id:sub-1 |
|
|
|
id:sub-1 |
|
|
|
@ -1036,15 +1044,25 @@ destination:/topic/price.stock.* |
|
|
|
^@ |
|
|
|
^@ |
|
|
|
---- |
|
|
|
---- |
|
|
|
|
|
|
|
|
|
|
|
[NOTE] |
|
|
|
Here is an example of a client sending a trade request, which the server |
|
|
|
==== |
|
|
|
may handle through an `@MessageMapping` method and later on, after the execution, |
|
|
|
|
|
|
|
broadcast a trade confirmation message and details down to the client: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---- |
|
|
|
|
|
|
|
SEND |
|
|
|
|
|
|
|
destination:/queue/trade |
|
|
|
|
|
|
|
content-type:application/json |
|
|
|
|
|
|
|
content-length:44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{"action":"BUY","ticker":"MMM","shares",44}^@ |
|
|
|
|
|
|
|
---- |
|
|
|
|
|
|
|
|
|
|
|
The meaning of a destination is intentionally left opaque in the STOMP spec. It can |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 |
|
|
|
destinations to be path-like strings where `"/topic/.."` implies publish-subscribe |
|
|
|
(__one-to-many__) and `"/queue/"` implies point-to-point (__one-to-one__) message |
|
|
|
(__one-to-many__) and `"/queue/"` implies point-to-point (__one-to-one__) message |
|
|
|
exchanges. |
|
|
|
exchanges. |
|
|
|
==== |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
STOMP servers can use the +MESSAGE+ command to broadcast messages to all subscribers. |
|
|
|
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: |
|
|
|
Here is an example of a server sending a stock quote to a subscribed client: |
|
|
|
@ -1058,26 +1076,21 @@ destination:/topic/price.stock.MMM |
|
|
|
{"ticker":"MMM","price":129.45}^@ |
|
|
|
{"ticker":"MMM","price":129.45}^@ |
|
|
|
---- |
|
|
|
---- |
|
|
|
|
|
|
|
|
|
|
|
[NOTE] |
|
|
|
|
|
|
|
==== |
|
|
|
|
|
|
|
It is important to know that a server cannot send unsolicited messages. All messages |
|
|
|
It is 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 |
|
|
|
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 |
|
|
|
+"subscription-id"+ header of the server message must match the +"id"+ header of the |
|
|
|
client subscription. |
|
|
|
client subscription. |
|
|
|
==== |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The above overview is intended to provide the most basic understanding of the |
|
|
|
The above overview is intended to provide the most basic understanding of the |
|
|
|
STOMP protocol. It is recommended to review the protocol |
|
|
|
STOMP protocol. It is recommended to review the protocol |
|
|
|
http://stomp.github.io/stomp-specification-1.2.html[specification], which is |
|
|
|
http://stomp.github.io/stomp-specification-1.2.html[specification] in full. |
|
|
|
easy to follow and manageable in terms of size. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The following summarizes the benefits for an application of using STOMP over WebSocket: |
|
|
|
The benefits of using STOMP as a WebSocket sub-protocol: |
|
|
|
|
|
|
|
|
|
|
|
* Standard message format |
|
|
|
* No need to invent a custom message format |
|
|
|
* Application-level protocol with support for common messaging patterns |
|
|
|
* Use existing https://github.com/jmesnil/stomp-websocket[stomp.js] client in the browser |
|
|
|
* Client-side support, e.g. https://github.com/jmesnil/stomp-websocket[stomp.js], https://github.com/cujojs/msgs[msgs.js] |
|
|
|
* Ability to route messages to based on destination |
|
|
|
* The ability to interpret, route, and process messages on both the client and server-side |
|
|
|
* Option to use full-fledged message broker such as RabbitMQ, ActiveMQ, etc. for broadcasting |
|
|
|
* The option to plug in 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 |
|
|
|
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 |
|
|
|
to provide a programming model for application-level use in the same way that |
|
|
|
@ -1088,10 +1101,12 @@ Spring MVC provides a programming model based on HTTP. |
|
|
|
[[websocket-stomp-enable]] |
|
|
|
[[websocket-stomp-enable]] |
|
|
|
=== Enable STOMP over WebSocket |
|
|
|
=== Enable STOMP over WebSocket |
|
|
|
The Spring Framework provides support for using STOMP over WebSocket through |
|
|
|
The Spring Framework provides support for using STOMP over WebSocket through |
|
|
|
the +spring-messaging+ and +spring-websocket+ modules. It's easy to enable it. |
|
|
|
the +spring-messaging+ and +spring-websocket+ modules. |
|
|
|
|
|
|
|
Here is an example of exposing a STOMP WebSocket/SockJS endpoint at the URL path |
|
|
|
Here is an example of configuring a STOMP WebSocket endpoint with SockJS fallback |
|
|
|
`/portfolio` where messages whose destination starts with "/app" are routed to |
|
|
|
options. The endpoint is available for clients to connect to a URL path `/portfolio`: |
|
|
|
message-handling methods (i.e. application work) and messages whose destinations |
|
|
|
|
|
|
|
start with "/topic" or "/queue" will be routed to the message broker (i.e. |
|
|
|
|
|
|
|
broadcasting to other connected clients): |
|
|
|
|
|
|
|
|
|
|
|
[source,java,indent=0] |
|
|
|
[source,java,indent=0] |
|
|
|
[subs="verbatim,quotes"] |
|
|
|
[subs="verbatim,quotes"] |
|
|
|
@ -1103,23 +1118,21 @@ options. The endpoint is available for clients to connect to a URL path `/portfo |
|
|
|
@EnableWebSocketMessageBroker |
|
|
|
@EnableWebSocketMessageBroker |
|
|
|
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { |
|
|
|
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void configureMessageBroker(MessageBrokerRegistry config) { |
|
|
|
|
|
|
|
config.setApplicationDestinationPrefixes("/app"); |
|
|
|
|
|
|
|
config.enableSimpleBroker("/queue", "/topic"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void registerStompEndpoints(StompEndpointRegistry registry) { |
|
|
|
public void registerStompEndpoints(StompEndpointRegistry registry) { |
|
|
|
registry.addEndpoint("/portfolio").withSockJS(); |
|
|
|
registry.addEndpoint("/portfolio").withSockJS(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ... |
|
|
|
@Override |
|
|
|
|
|
|
|
public void configureMessageBroker(MessageBrokerRegistry config) { |
|
|
|
|
|
|
|
config.setApplicationDestinationPrefixes("/app"); |
|
|
|
|
|
|
|
config.enableSimpleBroker("/topic", "/queue"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
---- |
|
|
|
---- |
|
|
|
|
|
|
|
|
|
|
|
XML configuration equivalent: |
|
|
|
and in XML: |
|
|
|
|
|
|
|
|
|
|
|
[source,xml,indent=0] |
|
|
|
[source,xml,indent=0] |
|
|
|
[subs="verbatim,quotes,attributes"] |
|
|
|
[subs="verbatim,quotes,attributes"] |
|
|
|
@ -1137,13 +1150,29 @@ XML configuration equivalent: |
|
|
|
<websocket:stomp-endpoint path="/portfolio"> |
|
|
|
<websocket:stomp-endpoint path="/portfolio"> |
|
|
|
<websocket:sockjs/> |
|
|
|
<websocket:sockjs/> |
|
|
|
</websocket:stomp-endpoint> |
|
|
|
</websocket:stomp-endpoint> |
|
|
|
<websocket:simple-broker prefix="/queue, /topic"/> |
|
|
|
<websocket:simple-broker prefix="/topic, /queue"/> |
|
|
|
... |
|
|
|
|
|
|
|
</websocket:message-broker> |
|
|
|
</websocket:message-broker> |
|
|
|
|
|
|
|
|
|
|
|
</beans> |
|
|
|
</beans> |
|
|
|
---- |
|
|
|
---- |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[NOTE] |
|
|
|
|
|
|
|
==== |
|
|
|
|
|
|
|
The "/app" prefix is arbitrary. You can pick any prefix. It's simply meant to differentiate |
|
|
|
|
|
|
|
messages to be routed to message-handling methods to do application work vs messages |
|
|
|
|
|
|
|
to be routed to the broker to broadcast to subscribed clients. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The "/topic" and "/queue" prefixes depend on the broker in use. In the case of the simple, |
|
|
|
|
|
|
|
in-memory broker the prefixes do not have any special meaning; it's merely a convention |
|
|
|
|
|
|
|
that indicates how the destination is used (pub-sub targetting many subscribers or |
|
|
|
|
|
|
|
point-to-point messages typically targeting an individual recipient). |
|
|
|
|
|
|
|
In the case of using a dedicated broker, most brokers use "/topic" as |
|
|
|
|
|
|
|
a prefix for destinations with pub-sub semantics and "/queue" for destinations |
|
|
|
|
|
|
|
with point-to-point semantics. Check the STOMP page of the broker to see the destination |
|
|
|
|
|
|
|
semantics it supports. |
|
|
|
|
|
|
|
==== |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
On the browser side, a client might connect as follows using |
|
|
|
On the browser side, a client might connect as follows using |
|
|
|
https://github.com/jmesnil/stomp-websocket[stomp.js] and the |
|
|
|
https://github.com/jmesnil/stomp-websocket[stomp.js] and the |
|
|
|
https://github.com/sockjs/sockjs-client[sockjs-client]: |
|
|
|
https://github.com/sockjs/sockjs-client[sockjs-client]: |
|
|
|
@ -1180,10 +1209,11 @@ sections <<websocket-stomp-handle-broker-relay-configure>> and |
|
|
|
=== Flow of Messages |
|
|
|
=== Flow of Messages |
|
|
|
|
|
|
|
|
|
|
|
When a STOMP endpoint is configured, the Spring application acts as the STOMP broker |
|
|
|
When a STOMP endpoint is configured, the Spring application acts as the STOMP broker |
|
|
|
to connected clients. It handles incoming messages and sends messages back. |
|
|
|
to connected clients. This section provides a big picture overview of how messages flow |
|
|
|
This section provides a big picture overview of how messages flow inside the application. |
|
|
|
within the application. |
|
|
|
|
|
|
|
|
|
|
|
The `spring-messaging` module contains a number of abstractions that originated in the |
|
|
|
The `spring-messaging` module provides the foundation for asynchronous message processing. |
|
|
|
|
|
|
|
It contains a number of abstractions that originated in the |
|
|
|
https://spring.io/spring-integration[Spring Integration] project and are intended |
|
|
|
https://spring.io/spring-integration[Spring Integration] project and are intended |
|
|
|
for use as building blocks in messaging applications: |
|
|
|
for use as building blocks in messaging applications: |
|
|
|
|
|
|
|
|
|
|
|
@ -1199,16 +1229,22 @@ extends `MessageChannel` and sends messages to registered `MessageHandler` subsc |
|
|
|
a concrete implementation of `SubscribableChannel` that can deliver messages |
|
|
|
a concrete implementation of `SubscribableChannel` that can deliver messages |
|
|
|
asynchronously via a thread pool. |
|
|
|
asynchronously via a thread pool. |
|
|
|
|
|
|
|
|
|
|
|
The provided STOMP over WebSocket config, both Java and XML, uses the above to |
|
|
|
The `@EnableWebSocketMessageBroker` Java config and the `<websocket:message-broker>` XML config |
|
|
|
assemble a concrete message flow including the following 3 channels: |
|
|
|
both assemble a concrete message flow. Below is a diagram of the part of the setup when using |
|
|
|
|
|
|
|
the simple, in-memory broker: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
image::images/message-flow-simple-broker.png[width=640] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The above setup that includes 3 message channels: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* `"clientInboundChannel"` for messages from WebSocket clients. |
|
|
|
|
|
|
|
* `"clientOutboundChannel"` for messages to WebSocket clients. |
|
|
|
|
|
|
|
* `"brokerChannel"` for messages to the broker from within the application. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The same three channels are also used with a dedicated broker except here a |
|
|
|
|
|
|
|
"broker relay" takes the place of the simple broker: |
|
|
|
|
|
|
|
|
|
|
|
* `"clientInboundChannel"` -- for messages from WebSocket clients. Every incoming |
|
|
|
image::images/message-flow-broker-relay.png[width=640] |
|
|
|
WebSocket message carrying a STOMP frame is sent through this channel. |
|
|
|
|
|
|
|
* `"clientOutboundChannel"` -- for messages to WebSocket clients. Every outgoing |
|
|
|
|
|
|
|
STOMP message from the broker is sent through this channel before getting sent |
|
|
|
|
|
|
|
to a client's WebSocket session. |
|
|
|
|
|
|
|
* `"brokerChannel"` -- for messages to the broker from within the application. |
|
|
|
|
|
|
|
Every message sent from the application to the broker passes through this channel. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Messages on the `"clientInboundChannel"` can flow to annotated |
|
|
|
Messages on the `"clientInboundChannel"` can flow to annotated |
|
|
|
methods for application handling (e.g. a stock trade execution request) or can |
|
|
|
methods for application handling (e.g. a stock trade execution request) or can |
|
|
|
|