parent
c53c8bfc5a
commit
513461d4f1
|
@ -850,23 +850,23 @@ Consider also customizing these server-side SockJS related properties (see Javad
|
|||
== STOMP
|
||||
|
||||
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
|
||||
a sub-protocol (i.e. a higher-level protocol) to define message semantics.
|
||||
While the use of a sub-protocol with WebSocket is completely optional either way
|
||||
client and server will need to agree on some kind of protocol to help interpret
|
||||
messages.
|
||||
content is undefined. The defines a mechanism for client and server to negotiate a
|
||||
sub-protocol -- i.e. a higher level messaging protocol, to use on top of WebSocket to
|
||||
define what kind of messages each can send, what is the format and content for each
|
||||
message, and so on. The use of a sub-protocol is optional but either way client and
|
||||
server will need to agree on some protocol that defines message content.
|
||||
|
||||
|
||||
|
||||
[[websocket-stomp-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
|
||||
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
|
||||
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
|
||||
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.
|
||||
Although STOMP is a text-oriented protocol, message payloads can be
|
||||
either text or binary.
|
||||
|
||||
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
|
||||
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
|
||||
to provide a programming model for application-level use in the same way that
|
||||
Spring MVC provides a programming model based on HTTP.
|
||||
[[websocket-stomp-benefits]]
|
||||
=== Benefits
|
||||
|
||||
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]]
|
||||
=== Enable STOMP
|
||||
|
||||
The Spring Framework provides support for using STOMP over WebSocket through
|
||||
the +spring-messaging+ and +spring-websocket+ modules.
|
||||
Here is an example of exposing a STOMP WebSocket/SockJS endpoint at the URL path
|
||||
`/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):
|
||||
STOMP over WebSocket support is available in the `spring-messaging` and the
|
||||
`spring-websocket` modules. Once you have those dependencies, you can expose a STOMP
|
||||
endpoints, over WebSocket with <<websocket-fallback>>, as shown below:
|
||||
|
||||
[source,java,indent=0]
|
||||
[subs="verbatim,quotes"]
|
||||
|
@ -987,18 +992,25 @@ broadcasting to other connected clients):
|
|||
|
||||
@Override
|
||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||
registry.addEndpoint("/portfolio").withSockJS();
|
||||
registry.addEndpoint("/portfolio").withSockJS(); // <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureMessageBroker(MessageBrokerRegistry config) {
|
||||
config.setApplicationDestinationPrefixes("/app");
|
||||
config.enableSimpleBroker("/topic", "/queue");
|
||||
config.setApplicationDestinationPrefixes("/app"); // <2>
|
||||
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]
|
||||
[subs="verbatim,quotes,attributes"]
|
||||
|
@ -1024,18 +1036,11 @@ and in XML:
|
|||
|
||||
[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.
|
||||
For the built-in, simple broker the "/topic" and "/queue" prefixes do not have any special
|
||||
meaning. They're merely a convention to differentiate between pub-sub vs point-to-point
|
||||
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
|
||||
prefixes it supports.
|
||||
====
|
||||
|
||||
|
||||
|
@ -1075,60 +1080,64 @@ sections <<websocket-stomp-handle-broker-relay-configure>> and
|
|||
[[websocket-stomp-message-flow]]
|
||||
=== Flow of Messages
|
||||
|
||||
When a STOMP endpoint is configured, the Spring application acts as the STOMP broker
|
||||
to connected clients. This section provides a big picture overview of how messages flow
|
||||
within the application.
|
||||
Once a STOMP endpoint is exposed, the Spring application becomes a STOMP broker for
|
||||
connected clients. This section describes the flow of messages on the server side.
|
||||
|
||||
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
|
||||
for use as building blocks in messaging applications:
|
||||
The `spring-messaging` module contains foundational support for messaging applications
|
||||
that originated in https://spring.io/spring-integration[Spring Integration] and was
|
||||
later extracted and incorporated into the Spring Framework for broader use across many
|
||||
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] --
|
||||
a message with headers and a payload.
|
||||
simple representation for a message including headers and payload.
|
||||
* {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] --
|
||||
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] --
|
||||
extends `MessageChannel` and sends messages to registered `MessageHandler` subscribers.
|
||||
`MessageChannel` with `MessageHandler` subscribers.
|
||||
* {api-spring-framework}/messaging/support/ExecutorSubscribableChannel.html[ExecutorSubscribableChannel] --
|
||||
a concrete implementation of `SubscribableChannel` that can deliver messages
|
||||
asynchronously via a thread pool.
|
||||
`SubscribableChannel` that uses an `Executor` for delivering messages.
|
||||
|
||||
The `@EnableWebSocketMessageBroker` Java config and the `<websocket:message-broker>` XML config
|
||||
both assemble a concrete message flow. Below is a diagram of the part of the setup when using
|
||||
the simple, in-memory broker:
|
||||
Both the Java config (i.e. `@EnableWebSocketMessageBroker`) and the XML namespace config
|
||||
(i.e. `<websocket:message-broker>`) use the above components to assemble a message
|
||||
workflow. The diagram below shows the components used when the simple, built-in message
|
||||
broker is enabled:
|
||||
|
||||
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.
|
||||
* `"clientOutboundChannel"` for messages to WebSocket clients.
|
||||
* `"brokerChannel"` for messages to the broker from within the application.
|
||||
* `"clientInboundChannel"` -- for passing messages received from WebSocket clients.
|
||||
* `"clientOutboundChannel"` -- for sending server messages to WebSocket clients.
|
||||
* `"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
|
||||
"broker relay" takes the place of the simple broker:
|
||||
The next diagram shows the components used when an external broker (e.g. RabbitMQ)
|
||||
is configured for managing subscriptions and broadcasting messages:
|
||||
|
||||
image::images/message-flow-broker-relay.png[]
|
||||
|
||||
Messages on the `"clientInboundChannel"` can flow to annotated
|
||||
methods for application handling (e.g. a stock trade execution request) or can
|
||||
be forwarded to the broker (e.g. client subscribing for stock quotes).
|
||||
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.
|
||||
The main difference in the above diagram is the use of the "broker relay" for passing
|
||||
messages up to the external STOMP broker over TCP, and for passing messages down from the
|
||||
broker to subscribed clients.
|
||||
|
||||
When a message-handling annotated method has a return type, its return
|
||||
value is sent as the payload of a Spring `Message` to the `"brokerChannel"`.
|
||||
The broker in turn broadcasts the message to clients. Sending a message
|
||||
to a destination can also be done from anywhere in the application with
|
||||
the help of a messaging template. For example, an HTTP POST handling method
|
||||
can broadcast a message to connected clients, or a service component may
|
||||
periodically broadcast stock quotes.
|
||||
When messages are received from a WebSocket connectin, they're decoded to STOMP frames,
|
||||
then turned into a Spring `Message` representation, and sent to the
|
||||
`"clientInboundChannel"` for further processing. For example STOMP messages whose
|
||||
destination header starts with `"/app"` may be routed to `@MessageMapping` methods in
|
||||
annotated controllers, while `"/topic"` and `"/queue"` messages may be routed directly
|
||||
to the message broker.
|
||||
|
||||
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]
|
||||
[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:
|
||||
|
||||
* WebSocket clients connect to the WebSocket endpoint at "/portfolio".
|
||||
* Subscriptions to "/topic/greeting" pass through the "clientInboundChannel"
|
||||
and are forwarded to the broker.
|
||||
* Greetings sent to "/app/greeting" pass through the "clientInboundChannel"
|
||||
and are forwarded to the `GreetingController`. The controller adds the current
|
||||
time, and the return value is passed through the "brokerChannel" as a message
|
||||
to "/topic/greeting" (destination is selected based on a convention but can be
|
||||
overridden via `@SendTo`).
|
||||
* The broker in turn broadcasts messages to subscribers, and they pass through
|
||||
the `"clientOutboundChannel"`.
|
||||
. Client connects to `"http://localhost:8080/portfolio"` and once a WebSocket connection
|
||||
is established, STOMP frames begin to flow on it.
|
||||
. Client sends SUBSCRIBE frame with destination header `"/topic/greeting"`. Once received
|
||||
and decoded, the message is sent to the `"clientInboundChannel"`, then routed to the
|
||||
message broker which stores the client subscription.
|
||||
. Client sends SEND frame to `"/app/greeting"`. The `"/app"` prefix helps to route it to
|
||||
annotated controllers. After the `"/app"` prefix is stripped, the remaining `"/greeting"`
|
||||
part of the destination is mapped to the `@MessageMapping` method in `GreetingController`.
|
||||
. The value returned from `GreetingController` is turned into a Spring `Message` with
|
||||
a payload based on the return value and a default destination header of
|
||||
`"/topic/greeting"` (derived from the input destination with `"/app"` replaced by
|
||||
`"/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
|
||||
kinds of arguments and return values supported.
|
||||
|
|
Loading…
Reference in New Issue