STOMP over WebSocket and the fallback option in Spring 4

In the previous section, we saw that in a WebSocket application that does not use subprotocols, the client and server should be aware of the message format (JSON in this case) in order to handle it. In this section, we use STOMP as a subprotocol in a WebSocket application (this is known as STOMP over WebSocket) and show how this application layer protocol helps us handle messages.

The messaging architecture in the previous application was an asynchronous client/server-based communication.

The spring-messaging module brings features of asynchronous messaging systems to Spring Framework. It is based on some concepts inherited from Spring Integration, such as messages, message handlers (classes that handle messages), and message channels (data channels between senders and receivers that provide loose coupling during communication).

At the end of this section, we will explain how our Spring WebSocket application integrates with the Spring messaging system and works in a similar way to legacy messaging systems such as JMS.

In the first application, we saw that in certain types of browsers, WebSocket communication failed because of browser incompatibility. In this section, we will explain how Spring's fallback option addresses this problem.

Suppose you are asked to develop a browser-based chat room application in which anonymous users can join a chat room and any text sent by a user should be sent to all active users. This means that we need a topic that all users should be subscribed to and messages sent by any user should be broadcasted to all. Spring WebSocket features meet these requirements. In Spring, using STOMP over WebSocket, users can exchange messages in a similar way to JMS. In this section, we will develop a chat room application and explain some of Spring WebSocket's features.

The first task is to configure Spring to handle STOMP messages over WebSocket. Using Spring 4, you can instantly configure a very simple, lightweight (memory-based) message broker, set up subscription, and let controller methods serve client messages. The code for the ChatroomWebSocketMessageBrokerConfigurer class is:

package com.springessentialsbook.chapter4;
…..
@Configuration
@EnableWebSocketMessageBroker
public class ChatroomWebSocketMessageBrokerConfigurer extends AbstractWebSocketMessageBrokerConfigurer {
   @Override
   public void configureMessageBroker(MessageBrokerRegistry config) {
      config.enableSimpleBroker("/chatroomTopic");
      config.setApplicationDestinationPrefixes("/myApp");
   }
   @Override
   public void registerStompEndpoints(StompEndpointRegistry registry) {
      registry.addEndpoint("/broadcastMyMessage").withSockJS();
   }
}

@Configuration tags a ChatroomWebSocketMessageBrokerConfigurer class as a Spring configuration class. @EnableWebSocketMessageBroker provides WebSocket messaging features backed by a message broker.

The overridden method configureMessageBroker, as its name suggests, overrides the parent method for message broker configuration and sets:

  • setApplicationDestinationPrefixes: Specify /myApp as the prefix, and any client message whose destination starts with /myApp will be routed to the controller's message-handling methods.
  • enableSimpleBroker: Set the broker topic to /chatroomTopic. Any messages whose destinations start with /chatroomTopic will be routed to the message broker (that is, broadcasted to other connected clients). Since we are using an in-memory broker, we can specify any topic. If we use a dedicated broker, the destination's name would be /topic or /queue, based on the subscription model (pub/sub or point-to-point).

The overridden method registerStompEndpoints is used to set the endpoint and fallback options. Let's look at it closely:

  • The client-side WebSocket can connect to the server's endpoint at /broadcastMyMessage. Since STOMP has been selected as the subprotocol, we do not need to know about the underlying message format and let STOMP handle it.
  • The .withSockJS() method enables Spring's fallback option. This guarantees successful WebSocket communication in any type or version of browser.

As Spring MVC forwards HTTP requests to methods in controllers, the MVC extension can receive STOMP messages over WebSocket and forward them to controller methods. A Spring Controller class can receive client STOMP messages whose destinations start with /myApp. The handler method can reply to subscribed clients by sending the returned message to the broker channel, and the broker replies to the client by sending the message to the response channel. At the end of this section, we will look at some more information about the messaging architecture. As an example, let's look at the ChatroomController class:

    package com.springessentialsbook.chapter4;
      ...
@Controller
public class ChatroomController {

    @MessageMapping("/broadcastMyMessage")
    @SendTo("/chatroomTopic/broadcastClientsMessages")
    public ReturnedDataModelBean broadCastClientMessage(ClientInfoBean message) throws Exception {
        String returnedMessage=message.getClientName() + ":"+message.getClientMessage();
        return new ReturnedDataModelBean(returnedMessage );
    }
}

Here, @Controller tags ChatroomController as an MVC workflow controller. @MessageMapping is used to tell the controller to map the client message to the handler method (broadCastClientMessage). This will be done by matching a message endpoint to the destination (/broadcastMyMessage). The method's returned object (ReturnedDataModelBean) will be sent back through the broker to the subscriber's topic (/chatroomTopic/broadcastClientsMessages) by the @SendTo annotation. Any message in the topic will be broadcast to all subscribers (clients). Note that clients do not wait for the response, since they send and listen to messages to and from the topic and not the service directly.

Our domain POJOs (ClientInfoBean and ReturnedDataModelBean), detailed as follows, will provide the communication message payloads (actual message content) between the client and server:

package com.springessentialsbook.chapter4;
public class ClientInfoBean {
    private String clientName;
    private String clientMessage;
    public String getClientMessage() {
    return clientMessage;
  }
    public String getClientName() {
        return clientName;
    }
}


package com.springessentialsbook.chapter4;
public class ReturnedDataModelBean {

    private String returnedMessage;
    public ReturnedDataModelBean(String returnedMessage) {
        this.returnedMessage = returnedMessage; }
    public String getReturnedMessage() {
        return returnedMessage;
    }
}

To add some sort of security, we can add basic HTTP authentication, as follows (we are not going to explain Spring security in this chapter, but it will be detailed in the next chapter):

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();
        http.authorizeRequests().anyRequest().authenticated();
    }
    @Autowired
    void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
        .withUser("user").password("password").roles("USER");
    }
}

The @Configuration tags this class as a configuration class and @EnableGlobalMethodSecurity and @EnableWebSecurity set security methods and web security in the class. In the configure method, we set basic authentication, and in configureGlobal, we set the recognized username and password as well as the role that the user belongs to.

To add Spring Security features, we should add the following Maven dependencies:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-messaging</artifactId>
    <version>4.0.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
</dependency>

As we explained in the previous section, the @SpringBootApplication tag sets up a Spring application within a web application without us having to write a single line of XML configuration (applicationContext.xml or web.xml):

package com.springessentialsbook.chapter4;
...
@SpringBootApplication
public class ChatroomBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(ChatroomBootApplication.class, args);
    }
}

Finally, you can run the application by running this command:

mvn spring-boot:run -Dserver.port=8090

This runs and deploys the web application on an embedded web server on port 8090 (8080 is not used as it may conflict with your running Apache service). So, the index page of the application will be accessible at http://localhost:8090/ (follow read-me.txt to run the application):

    <script src="sockjs-0.3.4.js"></script>
    <script src="stomp.js"></script>
    <script type="text/javascript">
...
function joinChatroom() {
    var topic='/chatroomTopic/broadcastClientsMessages';
    var servicePath='/broadcastMyMessage';
    var socket = new SockJS(servicePath);
    stompClient = Stomp.over(socket);
    stompClient.connect('user','password', function(frame) {
        setIsJoined(true);
        console.log('Joined Chatroom: ' + frame);
        stompClient.subscribe(topic, function(serverReturnedData){
            renderServerReturnedData(JSON.parse(serverReturnedData.body).returnedMessage);
        });
    });
}
...
function sendMyClientMessage() {
    var serviceFullPath='/myApp/broadcastMyMessage';
    var myText = document.getElementById('myText').value;
    stompClient.send(serviceFullPath, {}, JSON.stringify({ 'clientName': 'Client-'+randomnumber, 'clientMessage':myText}));
    document.getElementById('myText').value='';
}

On the client side, notice how the browser connects (with joinChatRoom) and sends data (in the sendMyClientMessage method). These methods use the JavaScript libraries SockJS and Stomp.js.

As you can see, when a client subscribes to a topic, it registers a listener method (stompClient.subscribe(topic, function(serverReturnedData){.…}). The listener method will be called when any message (from any client) arrives in the topic.

As discussed earlier, some versions of browsers do not support WebSocket. SockJS was introduced to handle all versions of browsers. On the client side, when you try to connect to the server, the SockJS client sends the GET/info message to get some information from the server. Then it chooses the transport protocol, which could be one of WebSocket, HTTP streaming, or HTTP long-polling. WebSocket is the preferred transport protocol; however, in case of browser incompatibility, it chooses HTTP streaming, and in the worse case, HTTP long-polling.

In the beginning of this section, we described how our WebSocket application integrates with the Spring messaging system and works in a way similar to legacy messaging systems.

The overridden method settings of @EnableWebSocketMessageBroker and ChatroomWebSocketMessageBrokerConfigurer create a concrete message flow (refer to the following diagram). In our messaging architecture, channels decouple receivers and senders. The messaging architecture contains three channels:

  • The client inbound channel (Request channel) for request messages sent from the client side
  • The client outbound channel (Response channel) for messages sent to the client side
  • The Broker channel for internal server messages to the broker

Our system uses STOMP destinations for simple routing by prefix. Any client message whose destination starts with /myApp will be routed to controller message-handling methods. Any message whose destination starts with /chatroomTopic will be routed to the message broker.

STOMP over WebSocket and the fallback option in Spring 4

The simple broker (in-memory) messaging architecture

Here is the messaging flow of our application:

  1. The client connects to the WebSocket endpoint (/broadcastMyMessage).
  2. Client messages to /myApp/broadcastMyMessage will be forwarded to the ChatroomController class (through the Request channel). The mapping controller's method passes the returned value to the Broker channel for the topic /chatroomTopic/broadcastClientsMessages.
  3. The broker passes the message to the Response channel, which is the topic /chatroomTopic/broadcastClientsMessages, and clients subscribed to this topic receive the message.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset