parent
c53c8bfc5a
commit
513461d4f1
|
@ -850,23 +850,23 @@ Consider also customizing these server-side SockJS related properties (see Javad
|
||||||
== STOMP
|
== STOMP
|
||||||
|
|
||||||
The WebSocket protocol defines two types of messages, text and binary, but their
|
The WebSocket protocol defines two types of messages, text and binary, but their
|
||||||
content is undefined. It's expected that the client and server may agree on using
|
content is undefined. The defines a mechanism for client and server to negotiate a
|
||||||
a sub-protocol (i.e. a higher-level protocol) to define message semantics.
|
sub-protocol -- i.e. a higher level messaging protocol, to use on top of WebSocket to
|
||||||
While the use of a sub-protocol with WebSocket is completely optional either way
|
define what kind of messages each can send, what is the format and content for each
|
||||||
client and server will need to agree on some kind of protocol to help interpret
|
message, and so on. The use of a sub-protocol is optional but either way client and
|
||||||
messages.
|
server will need to agree on some protocol that defines message content.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[websocket-stomp-overview]]
|
[[websocket-stomp-overview]]
|
||||||
=== Overview
|
=== Overview
|
||||||
|
|
||||||
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 messaging patterns. STOMP can be
|
designed to address a minimal subset of commonly used messaging patterns. STOMP 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
|
Although STOMP is a text-oriented protocol, message payloads can be
|
||||||
either text or binary.
|
either text or binary.
|
||||||
|
|
||||||
STOMP is a frame based protocol whose frames are modeled on HTTP. The structure
|
STOMP is a frame based protocol whose frames are modeled on HTTP. The structure
|
||||||
|
@ -951,29 +951,34 @@ 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] in full.
|
http://stomp.github.io/stomp-specification-1.2.html[specification] in full.
|
||||||
|
|
||||||
The benefits of using STOMP as a WebSocket sub-protocol:
|
|
||||||
|
|
||||||
* No need to invent a custom message format
|
|
||||||
* Use existing https://github.com/jmesnil/stomp-websocket[stomp.js] client in the browser
|
|
||||||
* Ability to route messages to based on destination
|
|
||||||
* Option to use full-fledged message broker such as RabbitMQ, ActiveMQ, etc. for broadcasting
|
|
||||||
|
|
||||||
Most importantly the use of STOMP (vs plain WebSocket) enables the Spring Framework
|
[[websocket-stomp-benefits]]
|
||||||
to provide a programming model for application-level use in the same way that
|
=== Benefits
|
||||||
Spring MVC provides a programming model based on HTTP.
|
|
||||||
|
Use of STOMP as a sub-protocol enables the Spring Framework and Spring Security to
|
||||||
|
provide a richer programming model vs using raw WebSockets. The same point can be
|
||||||
|
made about how HTTP vs raw TCP and how it enables Spring MVC and other web frameworks
|
||||||
|
to provide rich functionality. The following is a list of benefits:
|
||||||
|
|
||||||
|
* No need to invent a custom messaging protocol and message format.
|
||||||
|
* STOMP clients are available including a <<websocket-stomp-client,Java client>>
|
||||||
|
in the Spring Framework.
|
||||||
|
* Message brokers such as RabbitMQ, ActiveMQ, and others can be used (optionally) to
|
||||||
|
manage subscriptions and broadcast messages.
|
||||||
|
* Application logic can be organized in any number of ``@Controller``'s and messages
|
||||||
|
routed to them based on the STOMP destination header vs handling raw WebSocket messages
|
||||||
|
with a single `WebSocketHandler` for a given connection.
|
||||||
|
* Use Spring Security to secure messages based on STOMP destinations and message types.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[websocket-stomp-enable]]
|
[[websocket-stomp-enable]]
|
||||||
=== Enable STOMP
|
=== Enable STOMP
|
||||||
|
|
||||||
The Spring Framework provides support for using STOMP over WebSocket through
|
STOMP over WebSocket support is available in the `spring-messaging` and the
|
||||||
the +spring-messaging+ and +spring-websocket+ modules.
|
`spring-websocket` modules. Once you have those dependencies, you can expose a STOMP
|
||||||
Here is an example of exposing a STOMP WebSocket/SockJS endpoint at the URL path
|
endpoints, over WebSocket with <<websocket-fallback>>, as shown below:
|
||||||
`/portfolio` where messages whose destination starts with "/app" are routed to
|
|
||||||
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"]
|
||||||
|
@ -987,18 +992,25 @@ broadcasting to other connected clients):
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||||
registry.addEndpoint("/portfolio").withSockJS();
|
registry.addEndpoint("/portfolio").withSockJS(); // <1>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||||
config.setApplicationDestinationPrefixes("/app");
|
config.setApplicationDestinationPrefixes("/app"); // <2>
|
||||||
config.enableSimpleBroker("/topic", "/queue");
|
config.enableSimpleBroker("/topic", "/queue"); // <3>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
and in XML:
|
<1> `"/portfolio"` is the HTTP URL for the endpoint to which a WebSocket (or SockJS)
|
||||||
|
client will need to connect to for the WebSocket handshake.
|
||||||
|
<2> STOMP messages whose destination header begins with `"/app"` are routed to
|
||||||
|
`@MessageMapping` methods in `@Controller` classes.
|
||||||
|
<3> Use the built-in, message broker for subscriptions and broadcasting;
|
||||||
|
Route messages whose destination header begins with "/topic" or "/queue" to the broker.
|
||||||
|
|
||||||
|
The same configuration in XML:
|
||||||
|
|
||||||
[source,xml,indent=0]
|
[source,xml,indent=0]
|
||||||
[subs="verbatim,quotes,attributes"]
|
[subs="verbatim,quotes,attributes"]
|
||||||
|
@ -1024,18 +1036,11 @@ and in XML:
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
The "/app" prefix is arbitrary. You can pick any prefix. It's simply meant to differentiate
|
For the built-in, simple broker the "/topic" and "/queue" prefixes do not have any special
|
||||||
messages to be routed to message-handling methods to do application work vs messages
|
meaning. They're merely a convention to differentiate between pub-sub vs point-to-point
|
||||||
to be routed to the broker to broadcast to subscribed clients.
|
messaging (i.e. many subscribers vs one consumer). When using an external broker, please
|
||||||
|
check the STOMP page of the broker to understand what kind of STOMP destinations and
|
||||||
The "/topic" and "/queue" prefixes depend on the broker in use. In the case of the simple,
|
prefixes it supports.
|
||||||
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.
|
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|
||||||
|
@ -1075,60 +1080,64 @@ sections <<websocket-stomp-handle-broker-relay-configure>> and
|
||||||
[[websocket-stomp-message-flow]]
|
[[websocket-stomp-message-flow]]
|
||||||
=== Flow of Messages
|
=== Flow of Messages
|
||||||
|
|
||||||
When a STOMP endpoint is configured, the Spring application acts as the STOMP broker
|
Once a STOMP endpoint is exposed, the Spring application becomes a STOMP broker for
|
||||||
to connected clients. This section provides a big picture overview of how messages flow
|
connected clients. This section describes the flow of messages on the server side.
|
||||||
within the application.
|
|
||||||
|
|
||||||
The `spring-messaging` module provides the foundation for asynchronous message processing.
|
The `spring-messaging` module contains foundational support for messaging applications
|
||||||
It contains a number of abstractions that originated in the
|
that originated in https://spring.io/spring-integration[Spring Integration] and was
|
||||||
https://spring.io/spring-integration[Spring Integration] project and are intended
|
later extracted and incorporated into the Spring Framework for broader use across many
|
||||||
for use as building blocks in messaging applications:
|
https://spring.io/projects[Spring projects] and application scenarios.
|
||||||
|
Below is a list of a few of the available messaging abstractions:
|
||||||
|
|
||||||
* {api-spring-framework}/messaging/Message.html[Message] --
|
* {api-spring-framework}/messaging/Message.html[Message] --
|
||||||
a message with headers and a payload.
|
simple representation for a message including headers and payload.
|
||||||
* {api-spring-framework}/messaging/MessageHandler.html[MessageHandler] --
|
* {api-spring-framework}/messaging/MessageHandler.html[MessageHandler] --
|
||||||
a contract for handling a message.
|
contract for handling a message.
|
||||||
* {api-spring-framework}/messaging/MessageChannel.html[MessageChannel] --
|
* {api-spring-framework}/messaging/MessageChannel.html[MessageChannel] --
|
||||||
a contract for sending a message enabling loose coupling between senders and receivers.
|
contract for sending a message that enables loose coupling between producers and consumers.
|
||||||
* {api-spring-framework}/messaging/SubscribableChannel.html[SubscribableChannel] --
|
* {api-spring-framework}/messaging/SubscribableChannel.html[SubscribableChannel] --
|
||||||
extends `MessageChannel` and sends messages to registered `MessageHandler` subscribers.
|
`MessageChannel` with `MessageHandler` subscribers.
|
||||||
* {api-spring-framework}/messaging/support/ExecutorSubscribableChannel.html[ExecutorSubscribableChannel] --
|
* {api-spring-framework}/messaging/support/ExecutorSubscribableChannel.html[ExecutorSubscribableChannel] --
|
||||||
a concrete implementation of `SubscribableChannel` that can deliver messages
|
`SubscribableChannel` that uses an `Executor` for delivering messages.
|
||||||
asynchronously via a thread pool.
|
|
||||||
|
|
||||||
The `@EnableWebSocketMessageBroker` Java config and the `<websocket:message-broker>` XML config
|
Both the Java config (i.e. `@EnableWebSocketMessageBroker`) and the XML namespace config
|
||||||
both assemble a concrete message flow. Below is a diagram of the part of the setup when using
|
(i.e. `<websocket:message-broker>`) use the above components to assemble a message
|
||||||
the simple, in-memory broker:
|
workflow. The diagram below shows the components used when the simple, built-in message
|
||||||
|
broker is enabled:
|
||||||
|
|
||||||
image::images/message-flow-simple-broker.png[]
|
image::images/message-flow-simple-broker.png[]
|
||||||
|
|
||||||
The above setup that includes 3 message channels:
|
There are 3 message channels in the above diagram:
|
||||||
|
|
||||||
* `"clientInboundChannel"` for messages from WebSocket clients.
|
* `"clientInboundChannel"` -- for passing messages received from WebSocket clients.
|
||||||
* `"clientOutboundChannel"` for messages to WebSocket clients.
|
* `"clientOutboundChannel"` -- for sending server messages to WebSocket clients.
|
||||||
* `"brokerChannel"` for messages to the broker from within the application.
|
* `"brokerChannel"` -- for sending messages to the message broker from within
|
||||||
|
server-side, application code.
|
||||||
|
|
||||||
The same three channels are also used with a dedicated broker except here a
|
The next diagram shows the components used when an external broker (e.g. RabbitMQ)
|
||||||
"broker relay" takes the place of the simple broker:
|
is configured for managing subscriptions and broadcasting messages:
|
||||||
|
|
||||||
image::images/message-flow-broker-relay.png[]
|
image::images/message-flow-broker-relay.png[]
|
||||||
|
|
||||||
Messages on the `"clientInboundChannel"` can flow to annotated
|
The main difference in the above diagram is the use of the "broker relay" for passing
|
||||||
methods for application handling (e.g. a stock trade execution request) or can
|
messages up to the external STOMP broker over TCP, and for passing messages down from the
|
||||||
be forwarded to the broker (e.g. client subscribing for stock quotes).
|
broker to subscribed clients.
|
||||||
The STOMP destination is used for simple prefix-based routing. For example
|
|
||||||
the "/app" prefix could route messages to annotated methods while the "/topic"
|
|
||||||
and "/queue" prefixes could route messages to the broker.
|
|
||||||
|
|
||||||
When a message-handling annotated method has a return type, its return
|
When messages are received from a WebSocket connectin, they're decoded to STOMP frames,
|
||||||
value is sent as the payload of a Spring `Message` to the `"brokerChannel"`.
|
then turned into a Spring `Message` representation, and sent to the
|
||||||
The broker in turn broadcasts the message to clients. Sending a message
|
`"clientInboundChannel"` for further processing. For example STOMP messages whose
|
||||||
to a destination can also be done from anywhere in the application with
|
destination header starts with `"/app"` may be routed to `@MessageMapping` methods in
|
||||||
the help of a messaging template. For example, an HTTP POST handling method
|
annotated controllers, while `"/topic"` and `"/queue"` messages may be routed directly
|
||||||
can broadcast a message to connected clients, or a service component may
|
to the message broker.
|
||||||
periodically broadcast stock quotes.
|
|
||||||
|
|
||||||
Below is a simple example to illustrate the flow of messages:
|
An annotated `@Controller` handling a STOMP message from a client may send a message to
|
||||||
|
the message broker through the `"brokerChannel"`, and the broker will broadcast the
|
||||||
|
message to matching subscribers through the `"clientOutboundChannel"`. The same
|
||||||
|
controller can also do the same in response to HTTP requests, so a client may perform an
|
||||||
|
HTTP POST and then an `@PostMapping` method can send a message to the message broker
|
||||||
|
to broadcast to subscribed clients.
|
||||||
|
|
||||||
|
Let's trace the flow through a simple example. Given the following server setup:
|
||||||
|
|
||||||
[source,java,indent=0]
|
[source,java,indent=0]
|
||||||
[subs="verbatim,quotes"]
|
[subs="verbatim,quotes"]
|
||||||
|
@ -1162,18 +1171,22 @@ Below is a simple example to illustrate the flow of messages:
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
The following explains the message flow for the above example:
|
. Client connects to `"http://localhost:8080/portfolio"` and once a WebSocket connection
|
||||||
|
is established, STOMP frames begin to flow on it.
|
||||||
* WebSocket clients connect to the WebSocket endpoint at "/portfolio".
|
. Client sends SUBSCRIBE frame with destination header `"/topic/greeting"`. Once received
|
||||||
* Subscriptions to "/topic/greeting" pass through the "clientInboundChannel"
|
and decoded, the message is sent to the `"clientInboundChannel"`, then routed to the
|
||||||
and are forwarded to the broker.
|
message broker which stores the client subscription.
|
||||||
* Greetings sent to "/app/greeting" pass through the "clientInboundChannel"
|
. Client sends SEND frame to `"/app/greeting"`. The `"/app"` prefix helps to route it to
|
||||||
and are forwarded to the `GreetingController`. The controller adds the current
|
annotated controllers. After the `"/app"` prefix is stripped, the remaining `"/greeting"`
|
||||||
time, and the return value is passed through the "brokerChannel" as a message
|
part of the destination is mapped to the `@MessageMapping` method in `GreetingController`.
|
||||||
to "/topic/greeting" (destination is selected based on a convention but can be
|
. The value returned from `GreetingController` is turned into a Spring `Message` with
|
||||||
overridden via `@SendTo`).
|
a payload based on the return value and a default destination header of
|
||||||
* The broker in turn broadcasts messages to subscribers, and they pass through
|
`"/topic/greeting"` (derived from the input destination with `"/app"` replaced by
|
||||||
the `"clientOutboundChannel"`.
|
`"/topic"`). The resulting message is sent to the "brokerChannel" and handled
|
||||||
|
by the message broker.
|
||||||
|
. The message broker finds all matching subscribers, and sends a MESSAGE frame to each
|
||||||
|
through the `"clientOutboundChannel"` from where messages are encoded as STOMP frames
|
||||||
|
and sent on the WebSocket connection.
|
||||||
|
|
||||||
The next section provides more details on annotated methods including the
|
The next section provides more details on annotated methods including the
|
||||||
kinds of arguments and return values supported.
|
kinds of arguments and return values supported.
|
||||||
|
|
Loading…
Reference in New Issue