SPRING FRAMEWORK 4.0 M2: WEBSOCKET MESSAGING ARCHITECTURES |
Overview
As I wrote previously, a WebSocket API is only the starting point for WebSocket-style messaging applications. Many practical challenges remain. As one Tomcat mailing list user mused recently:
it does seem to me that websockets is still not really "production-ready", (I am not talking about the Tomcat implementation per se, but more generally) … native websockets capability in IE is only available since IE-10 and that solutions which allow this to work in lower IE versions are a bit "iffy" (relying on a diversion through Adobe's FlashPlayer e.g.). (Most of our customers are largish corporations, which are not going to update their browsers, nor open special ports in their firewalls, just to please us).
The first milestone of Spring Framework 4.0 provided server-side support for SockJS, the best and the most comprehensive WebSocket browser fallback options. You will need fallback options in browsers that don't support WebSocket and in situations where network proxies prevent its use. Simply put SockJS enables you to build WebSocket applications today and rely on transparent fallback options when necessary.
Even with fallback options, bigger challenges remain. A socket is a very low-level abstraction and the vast majority of web applications today do not program to sockets. This is why the WebSocket protocol defines a sub-protocol mechanism that essentially enables, and encourages, the use of higher-level protocols over WebSocket, much like we use HTTP over TCP.
The second milestone of Spring Framework 4.0 enables the use of higher-level messaging protocols over WebSocket. To demonstrate this we've put together a sample application.
Stock Portfolio Sample
The Stock Portfolio sample application, available on Github, loads a user's portfolio positions, allows buying and selling shares, consumes price quotes, and displays position updates. It is a reasonably simple application. Yet it handles a number of common tasks that are likely to come up in browser-based messaging applications.
So how do we put together an application like that? From HTTP and REST we are used to relying on URLs along with HTTP verbs to express what needs to be done. Here we have a socket and lots of messages. How do you tell who a message is for and what the message means?
Browser and server must agree on a common message format before such semantics can be expressed. Several protocols exist that can help. We chose STOMP for this milestone due to its simplicity and wide support.
Simple/Streaming Text-Oriented Messaging Protocol (STOMP)
STOMP is a messaging protocol created with simplicity in mind. It is based on frames modelled on HTTP. A frame consists of a command, optional headers, and optional body.
For example the Stock Portfolio application needs to receive stock quotes, so the client sends a SUBSCRIBE
frame where the destination
header indicates what the client wants to subscribe to:
As stock quotes become available, the server sends a MESSAGE
frame with a matching destination and subscription id as well as a content-type header and a body:
To do all that in the browser we use stomp.js and the SockJS client:
This is a huge gain already!! We have a standard message format and client-side support.
Now we can move one to the server side.
Message-Broker Solution
One server-side option is a pure message-broker solution where messages are sent directly to a traditional message broker like RabbitMQ, ActiveMQ, etc. Most, if not all brokers, support STOMP over TCP but increasingly they support it over WebSocket too while RabbitMQ goes further and also supports SockJS. Our architecture would look like this:
This is a robust and scalable solution but arguably not the best fit for the problem at hand. Message brokers have typically been used within the enterprise. Exposing them directly over the web isn't ideal.
If we've learned anything from REST it is that we don't want to expose details about the internals of our system like the database or the domain model.
Furthermore, as a Java developer you want to apply security, validation, and add application logic. In a message-broker solution the application server sits behind the message broker, which is a significant departure from what most web application developer are used to.
This is why a library such as socket.io is popular. It is simple and it targets the needs of web applications. On other hand we must not ignore the capabilities of message brokers to handle messages, they are really good at it and messaging is a hard problem. We need the best of both.
Application and Message-Broker Solution
Another approach is to make the application handle incoming messages and serve as intermediary between web clients and the message broker. Messages from clients can flow to the broker through the application and reversely messages from the broker can flow back to clients through the application. This gives the application a chance to examine the incomingmessage type and "destination" header and decide whether to handle the message or pass it on to the broker.
This is the approach we've chosen. To illustrate better here are some scenarios.
Load portfolio positions
- Client requests portfolio positions
- The application handles the request by loading and returning the data to the subscription
- The message broker is not involved in this interaction
Subscribe for stock quotes
- Client sends subscription request for stock quotes
- The application passes the message to the message broker
- The message broker propagates the message to all subscribed clients
Receive stock quotes
- QuoteService sends stock quote message to the message broker
- The message broker propagates the message to all subscribed clients
Execute a trade
- Client sends trade request
- The application handles it, submits the trade for execution through the TradeService
- The message broker is not involved in this interaction
Receive position update
- Trade service sends a position update message to a queue on the message broker
- The message broker sends the position update to the client
- Sending messages to a specific user is covered in more detail further below
Strictly speaking the use of a message broker is optional. We provide an out-of-the-box "simple" alternative for getting-started. However the use of a message broker is recommended for scalability and for deployments with multiple application servers.
Code Snippets
Let's see some examples of client and server-side code.
This is portfolio.js requesting portfolio positions:
On the server side PortfolioController detects the request and returns portfolio positions demonstrating a request-reply interaction that is very common in web applications. Since we use Spring Security to protect HTTP requests, including the one leading to the WebSocket handshake, the principal
method argument below is taken from the user principal Spring Security set on the HttpServletRequest.
This is portfolio.js
sending a trade request:
On the server side PortfolioController
sends the trade for execution:
PortfolioController
can also handle unexpected exceptions by sending a message to the user.
What about sending messages from within the application to subscribed clients? This is how the QuoteService
sends quotes:
And this is how the TradeService
sends position updates after a trade is executed:
And just in case you're wondering… yes PortfolioController
can also contain Spring MVC methods (e.g. @RequestMapping
) as suggested in this ticket by a developer who previously built an online game application:
Yes, having [message] mappings and spring mvc mappings consolidated would be nice. There is no reason why they can't be unified.
And just like the QuoteService and TradeService, Spring MVC controller methods can publish messages too.
Messaging Support For Spring Applications
For a long time Spring Integration has provided first-class abstractions for the well-knownEnterprise Integration patterns as well as lightweight messagings. While working on this milestone we realized the latter was exactly what we needed to build on.
As a result I'm pleased to announce we've moved a selection of Spring Integration types to the Spring Framework into a new module predictably called spring-messaging. Besides core abstractions such as Message
, MessageChannel
, MessageHandler
, and others, the new module contains all the annotations and classes to support the new features described in this post.
With that in mind we can now look at a diagram of the internal architecture of the Stock Portfolio application:
StompWebSocketHandler
puts incoming client messages on the "dispatch" message channel. There are 3 subscribers to this channel. The first one delegates to annotated methods, the second relays messages to a STOMP message broker, while the third one handles messages to individual users by transforming the destination into a unique queue name to which the client is subscribed (more detail to come).
By default the application runs with a "simple" message broker provided as a getting-started option. As explained in the sample README, you can alternate between the "simple" and a full-featured message broker by activating and de-activating profiles.
Another possible configuration change is to switch from Executor to Reactor-based implementations of MessageChannel for message passing. The Reactor project that recently released a first milestone is also used to manage TCP connections between the application and the message broker.
You can see the full application configuration that also includes the new Spring Security Java configuration. You might also be interested in the improved STS support for Java configuration.
Sending Messages To a Single User
It is easy to see how messages can be broadcast to multiple subscribed clients, just publish a message to a topic. It is more difficult to see how to send a message to a specific user. For example you may catch an exception and would like to send an error message. Or you may have received a trade confirmation and would like to send it to the user.
In traditional messaging applications it is common to create a temporary queue and set a "reply-to" header on any message to which a reply is expected. This works but feels rather cumbersome in web applications. The client must remember to set the necessary header on all applicable messages and the server application may need to keep track and pass this around. Sometimes such information may simply not be readily available, e.g. while handling an HTTP POST as an alternative to passing messages.
To support this requirement, we send a unique queue suffix to every connected client. The suffix can then be appended to create unique queue names.
Then on the server-side an @MessageExceptionHandler
method (or any message-handling method) can add an @ReplyToUser
annotation to send the return value as a message.
All other classes, like the TradeService, can use a messaging template to achieve the same.
In both cases internally we locate the user queue suffix (through the configuredUserQueueSuffixResolver) in order to reconstruct the correct queue name. At the moment there is only one simple resolver implementation. However, it would be easy to add a Redisimplementation that would support the same feature regardless of whether the user is connected to this or another application server.
Conclusion
Hopefully this has been a useful introduction of the new functionality. Rather than making the post longer, I encourage you to check the sample and consider what it means for applications you write or intend to write. It is a perfect time for feedback as we work towards a release candidate in early September.
To use Spring Framework 4.0.0.M2 add the http://repo.springsource.org/libs-milestone or thehttp://repo.springsource.org/milestone repositories to your configuration. The former includes transient dependencies as explained in our Repository FAQ.
SpringOne 2GX 2013 is around the corner
Book your place at SpringOne in Santa Clara soon. It's simply the best opportunity to find out first hand all that's going on and to provide direct feedback. Expect a number of significant new announcements this year. Check recent blog posts to see what I mean and there is more to come!
SIMILAR POSTS
- Understanding AMQP, the protocol used by RabbitMQ
- Spring MVC 3.2 Preview: Techniques for Real-time Updates
- RabbitMQ Plugin for Grails – Early Access
- Mixing RabbitMQ with Spring Python
- Spring MVC 3.2 Preview: Adding Long Polling to an Existing Web Application
출처 - http://assets.spring.io/wp/WebSocketBlogPost.html
https://github.com/rstoyanchev/spring-websocket-portfolio
Using Spring 4 WebSocket, sockJS and Stomp support to implement two way server client communication
JANUARY 19, 2014 1 COMMENT
One exciting new feature of Spring 4 is the support for WebSocket, SockJS and STOMP messaging. This allows two way communication between the server and its clients in a Spring MVC web application using the standard point-to-point and publish-subscribe messaging protocols. In this post, I will demonstrate how to set up a basic boilerplate project to start using this new feature. It is in part based on this article.
Maven Setup
First we need to add the Spring messaging modules in the POM file:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>4.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.0.0.RELEASE</version> </dependency>
Spring MVC Configuration
Next, we need to add the message broker config to the Spring MVC config XML file.
<beans ... xmlns:websocket="http://www.springframework.org/schema/websocket" xsi:schemaLocation=" ... 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="/hello"> <websocket:sockjs/> </websocket:stomp-endpoint> <websocket:simple-broker prefix="/topic"/> </websocket:message-broker> <!-- Other MVC config omitted here-->
The main thing here is the set up of the message broker for handling the message exchange between the server and its clients. This is done via the <message-broker> and its child tags. The tag <websocket:simple-broker>indicates we are using in-memory message broker.
It is easy to understand together with the server and client codes so I will include them below first before attempting to give a bit more explanations by cross-referencing the client and server codes.
Spring MVC Controller
Below is my Spring MVC Controller
@Controller public class MessageController { @MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { return new Greeting("Hello, " + message.getName() + "!"); } }
The method argument HelloMessage and output Greeting are just POJOs representing the body of the messages being sent and returned.
public class Greeting { private String content; public Greeting(String content) { this.content = content; } public String getContent() { return content; } } public class HelloMessage { private String name; public String getName() { return name; } }
Client sockJS and STOMP codes
On the client side, I use the sockJS protocol fallback option as outlined in the Spring documentation. The javascript codes are included below
// Create stomp client over sockJS protocol (see Note 1) var socket = new SockJS("/hello"); var stompClient = Stomp.over(socket); // callback function to be called when stomp client is connected to server (see Note 2) var connectCallback = function() { alert("connected!"); stompClient.subscribe('/topic/greetings', function(greeting){ alert(JSON.parse(greeting.body).content); }); }; // callback function to be called when stomp client could not connect to server (see Note 3) var errorCallback = function(error) { // display the error's message header: alert(error.headers.message); }; // Connect as guest (Note 4) stompClient.connect("guest", "guest", connectCallback, errorCallback);
Note
- The client starts by create a sockJS by specifying the endpoint (ie. /hello) to connect to and then a stomp client is created over the socket. The endpoint here should match that defined in the Spring MVC configuration in the lines. Note also the 2nd line referring to sockJS.
<websocket:stomp-endpoint path=”/hello”>
<websocket:sockjs/>
</websocket:stomp-endpoint> - Then a callback function is created and assigned to a variable connectCallback. This is called when a successful connection is made by the stomp client. This allows us to start making subscriptions to messages (as in codes, repeated below) and sending messages. Note the subscription is for the topic “/topic/greetings”
stompClient.subscribe(‘/topic/greetings’, function(greeting){
alert(JSON.parse(greeting.body).content);
}); - A error callback function is defined if stomp client fails to connect to server.
- This line makes the connection registering the callback functions.
Now we are ready to send messages from the client, e.g. using the following javascript function
// function to send message function fnSayHi() { stompClient.send("/app/hello", {}, JSON.stringify({ 'name': 'Joe' })); }
The message will be sent to the Spring MVC message handler method greeting() as defined via the annotation @MessageMapping(“/hello”).
<websocket:message-broker application-destination-prefix=”/app”>
Note the prefix “/app” is defined in the Spring config as application-destination-prefix attribute of the message broker: Note also, the use of @SendTo annotation to direct the message to a given destination. I repeat the controller method below
@MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { return new Greeting("Hello, " + message.getName() + "!"); }
That’s it for now.
출처 - http://raymondhlee.wordpress.com/2014/01/19/using-spring-4-websocket-sockjs-and-stomp-support-to-implement-two-way-server-client-communication/
'Framework & Platform > Spring' 카테고리의 다른 글
spring - STOMP interceptor (0) | 2014.03.31 |
---|---|
spring - springx2013-websocket (0) | 2014.03.29 |
spring - WebSocket (0) | 2014.03.27 |
spring - @Scheduled (0) | 2014.02.11 |
spring data - mongoDB Date type (0) | 2014.01.15 |