Using WebSockets with Spring, AngularJS and SockJS

A while ago I wrote a tutorial about writing a web application using Spring, AngularJS and WebSockets. However, that tutorial only used a fraction of what WebSockets could do, so in this tutorial I will explain how you can write a small chat app using the same frameworks; Spring, AngularJS, Stomp.js and SockJS. The entire application will be written using JavaConfig, even the web.xml (what I still kept in my previous tutorial) will be replaced by a WebAppInitializer.

The application we’re going to write will look like this:

app-example

Why WebSockets

Once upon a time, someone decided to write a mail list application. At first, he made a client that would check if there was a new mail every minute. However, most of the time there was no new mail, yet the client was always sending new request, causing a huge load on the server. This technique was quite popular, and was called polling.
Then after a while, they used a new technique, where the client would check if there was new mail, and the server would respond as soon as there was mail available. This technique was a bit better than polling, but you still had to send a request, causing a lot of unnecessary (blocking) traffic, we called this technique long polling.

When you start thinking, the only conclusion you can make is that the server should send a message to the client as soon as there is mail available. The client should not initiate the request, but the server should do that. This was impossible for a long time, but since WebSockets where introduced, it finally became possible.

WebSockets is a protocol and a JavaScript API, the protocol is a very low level, full-duplex protocol, which means that messages can be sent in both directions simultaneous. It made it possible for the server to send data to the client, in stead of doing the opposite. Polling and long-polling were no longer necessary, and they lived happily ever after.

Because WebSockets provide a way to communicate in both ways, they’re often used for realtime applications. If for example, someone opened your application and modifies some data, you can directly update the visualized data for all users by using WebSockets.

Project setup

You will need several libraries here, mainly the Spring Web MVC framework for setting up our web application and Spring messaging + WebSockets for the WebSocket part of the application. We also need a JSON serializer like Jackson, because Stomp needs JSON serialization/deserialization, so I’m going to add those to our application as well.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.1.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>4.1.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>4.1.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.3.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.3.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.3.3</version>
</dependency>

In the front-end I’m going to need some libraries as well, which I will setup using Bower. If you’re not into Bower, you can always download the libraries by yourself.

{
  "name": "spring-ng-chat",
  "version": "0.0.1-SNAPSHOT",
  "dependencies": {
    "sockjs": "0.3.4",
    "stomp-websocket": "2.3.4",
    "angular": "1.3.8",
    "lodash": "2.4.1"
  }
}

The libraries I’m going to use are SockJS + Stomp.js for communication through WebSockets, AngularJS will be used for setting up the client-part of the application and Lo-Dash is a utility library that I will use (a fork of Underscore.js).

What is STOMP? Like I said before, the WebSocket protocol is a pretty low-level protocol, however, there are a few high(er) level protocols that can be used on top of WebSockets, for example MQTT and STOMP. STOMP for example adds extra possibilities to WebSockets, like publishing and subscribing to topics.

Java config

In stead of configuring our application using XML’s, I’m going to show you how you could write the same application, without the need of any XML file. The first class we need is the replacement of our web.xml, to bootstrap our web application. In this class we can define our application context(s), our web application context and some other servlet related configuration.

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected void customizeRegistration(ServletRegistration.Dynamic registration) {
    registration.setInitParameter("dispatchOptionsRequest", "true");
    registration.setAsyncSupported(true);
  }

  @Override
  protected Class< ?>[] getRootConfigClasses() {
    return new Class< ?>[] { AppConfig.class, WebSocketConfig.class };
  }

  @Override
  protected Class< ?>[] getServletConfigClasses() {
    return new Class< ?>[] { WebConfig.class };
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

  @Override
  protected Filter[] getServletFilters() {
    CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
    characterEncodingFilter.setEncoding(StandardCharsets.UTF_8.name());
    return new Filter[] { characterEncodingFilter };
  }
}

Most of this class is quite clear. First of all we have our getRootConfigClasses() and getServletConfigClasses() which we use to define our bean configuration classes. The getServletMappings() and getServletFilters() are related to servlet configuration. In this case I’m mapping the application to the context root and I’m adding a filter to make sure all content is in UTF-8.

Then the final method here is the customizeRegistrion. This can be quite important if you’re running the application on a Tomcat container. It says that asynchronous communication is possible, so that connections do not have to be closed directly.

As you might notice, you will get three compilation errors of classes that are not found. I’m going to define those now, so let’s start with AppConfig:

@Configuration
@ComponentScan(basePackages = "be.g00glen00b", excludeFilters = {
    @ComponentScan.Filter(value = Controller.class, type = FilterType.ANNOTATION),
    @ComponentScan.Filter(value = Configuration.class, type = FilterType.ANNOTATION)
})
public class AppConfig {

}

Quite empty and useless here, it tells which packages to scan, but excludes all configuration and controller classes (configuration classes are bootstrapped by our WebAppInitializer while Controller classes are bound to our WebConfig). Since we will only need a controller, this class will do nothing special, but if you have special services, then they will become spring beans if annoted correctly.

The next class is the WebConfig:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "be.g00glen00b.controller")
public class WebConfig extends WebMvcConfigurerAdapter {

  @Bean
  public InternalResourceViewResolver getInternalResourceViewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
  }

  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
  }

  @Bean
  public WebContentInterceptor webContentInterceptor() {
    WebContentInterceptor interceptor = new WebContentInterceptor();
    interceptor.setCacheSeconds(0);
    interceptor.setUseExpiresHeader(true);
    interceptor.setUseCacheControlHeader(true);
    interceptor.setUseCacheControlNoStore(true);

    return interceptor;
  }

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/libs/**").addResourceLocations("/libs/");
    registry.addResourceHandler("/app/**").addResourceLocations("/app/");
    registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
  }

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(webContentInterceptor());
  }
}

This configuration class bootstraps our web context. It tells us which static resources can be served (with addResourceHandlers. It adds a no cache interceptor (webContentInterceptor() and addInterceptors()) and also tells us the location of our dynamic resources (JSP files) by using the getInternalResourceViewResolver() bean.

Then finally we also have the WebSocket configuration:

@Configuration
@EnableWebSocketMessageBroker
@ComponentScan(basePackages = "be.g00glen00b.controller")
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic");
    config.setApplicationDestinationPrefixes("/app");
  }

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/chat").withSockJS();
  }
}

Just like the WebConfig it has to scan components in the controller package as well, because we will map our WebSocket traffic onto our controllers. Then we have to configure the message broker (where communication enters and leaves) using configureMessageBroker and we also have to configure our endpoints using registerStompEndpoints.

WebSockets are not yet working in all browsers. Many WebSockets libraries (for example SockJS and Socket.io) provide fallback-options using long polling, polling, … . Spring also allows these fallbacks, and is compatible with SockJS. This is why choosing SockJS as the client is a good idea.

Data transfer object

Our main communication will happen through WebSockets. To communicate, we will send a certain payload and respond to a specific Stomp.js topic. We need two classes for it, Message and OutputMessage.

First of all, Message will contain the chat message itself, and a generated ID, for example:

public class Message {

  private String message;
  private int id;
  
  public Message() {
    
  }
  
  public Message(int id, String message) {
    this.id = id;
    this.message = message;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }
}

The OutputMessage will extend Message, but will also add a timestamp (the current date) to it:

public class OutputMessage extends Message {

    private Date time;
    
    public OutputMessage(Message original, Date time) {
        super(original.getId(), original.getMessage());
        this.time = time;
    }
    
    public Date getTime() {
        return time;
    }
    
    public void setTime(Date time) {
        this.time = time;
    }
}

Spring controller

The final step in the Java-part of our application is the controller itself, with two mappings; one for the HTML/JSP page that contains our application, and the other for the WebSocket traffic:

@Controller
@RequestMapping("/")
public class ChatController {

  @RequestMapping(method = RequestMethod.GET)
  public String viewApplication() {
    return "index";
  }
    
  @MessageMapping("/chat")
  @SendTo("/topic/message")
  public OutputMessage sendMessage(Message message) {
    return new OutputMessage(message, new Date());
  }
}

What happens here is quite easy, when we go to the context root, we will see that viewApplication() is mapped onto that, so that the index.jsp page is used as the view. The other method, sendMessage() allows us to broadcast a message to /topic/message when a message entes the messagebroker /app/chat (don’t forget that we defined the prefix /app in WebSocketConfig).

The view

Now our entire Java code is already written, let’s start by defining the JSP page. This page will contain two main components; the form to add a new message, and the message list itself.

<!DOCTYPE HTML>
<html lang="en">
  <head>
    <link href="http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700" rel="stylesheet" type="text/css" />
    <link href="assets/style.css" rel="stylesheet" type="text/css" />
  </head>
  <body ng-app="chatApp">
    <div ng-controller="ChatCtrl" class="container">
      <form ng-submit="addMessage()" name="messageForm">
        <input type="text" placeholder="Compose a new message..." ng-model="message" />
        <div class="info">
          <span class="count" ng-bind="max - message.length" ng-class="{danger: message.length > max}">140</span>
          <button ng-disabled="message.length > max || message.length === 0">Send</button>
        </div>
      </form>
      <hr />
      <p ng-repeat="message in messages | orderBy:'time':true" class="message">
        <time>{{message.time | date:'HH:mm'}}</time>
        <span ng-class="{self: message.self}">{{message.message}}</span>
      </p>
    </div>
    
    <script src="libs/sockjs/sockjs.min.js" type="text/javascript"></script>
    <script src="libs/stomp-websocket/lib/stomp.min.js" type="text/javascript"></script>
    <script src="libs/angular/angular.min.js"></script>
    <script src="libs/lodash/dist/lodash.min.js"></script>
    <script src="app/app.js" type="text/javascript"></script>
    <script src="app/controllers.js" type="text/javascript"></script>
    <script src="app/services.js" type="text/javascript"></script>
  </body>
</html>

First of all we’re adding the Open Sans font and our own stylesheet (which we will define later in this tutorial). Then we start the body and bootstrap our AngularJS application which we will call chatApp. In this application we will have one AngularJS controller, the ChatCtrl. Don’t confuse this one with our Spring controller!

The first thing we have to do is create form that has a text field. We’re binding this text-field to the model called message. When the form is submit, the addMessage() function on our controller will be called, which we will use to send the message using websockets.

To make the form a bit fancier, we also added a counter similar to how Twitter works. The moment you enter too many characters (max), it will turn red and you can no longer submit the form thanks to the ng-disabled directive.

Below the form we loop through the messages and for each message we print the time and the message. If the message originated from the user self, it will have a specific self class, thanks to the ng-class directive. The messages are sorted by their date, with the most recent one at the top of the list.

At the end of our page we load all the libraries we need, and our application JavaScript files.

Bootstrapping the AngularJS application

Our first JavaScript file is app.js. This file will define all module packages, in this case:

angular.module("chatApp", [
  "chatApp.controllers",
  "chatApp.services"
]);

angular.module("chatApp.controllers", []);
angular.module("chatApp.services", []);

AngularJS controller

The AngularJS controller will be quite easy as well, as it will forward everything to a seperate service we will write later in this tutorial. The controller contains three model related fields, the message which will contain the currently typed message in the textbox, the messages array which contains all received messages and also max the maximum allowed characters in a message, used for the Twitter-look-a-like counter.

angular.module("chatApp.controllers").controller("ChatCtrl", function($scope, ChatService) {
  $scope.messages = [];
  $scope.message = "";
  $scope.max = 140;

  $scope.addMessage = function() {
    ChatService.send($scope.message);
    $scope.message = "";
  };

  ChatService.receive().then(null, null, function(message) {
    $scope.messages.push(message);
  });
});

We already explained that when the form is submit, the addMessage is called, which will forward the message to the service, and which will then empty the field by resetting the message model to an empty string.
We also call the service for receiving messages. This part of the service will return a deferred, that each time a message is received, updates the progress part of the directive. The controller will react on that message by adding it to the messages array.

AngularJS service

The last part of our AngularJS based client application is the service. The service is a bit more complex, since it will contain all WebSocket traffic handling code. The code of this service is as follows:

angular.module("chatApp.services").service("ChatService", function($q, $timeout) {
    
    var service = {}, listener = $q.defer(), socket = {
      client: null,
      stomp: null
    }, messageIds = [];
    
    service.RECONNECT_TIMEOUT = 30000;
    service.SOCKET_URL = "/spring-ng-chat/chat";
    service.CHAT_TOPIC = "/topic/message";
    service.CHAT_BROKER = "/app/chat";
    
    service.receive = function() {
      return listener.promise;
    };
    
    service.send = function(message) {
      var id = Math.floor(Math.random() * 1000000);
      socket.stomp.send(service.CHAT_BROKER, {
        priority: 9
      }, JSON.stringify({
        message: message,
        id: id
      }));
      messageIds.push(id);
    };
    
    var reconnect = function() {
      $timeout(function() {
        initialize();
      }, this.RECONNECT_TIMEOUT);
    };
    
    var getMessage = function(data) {
      var message = JSON.parse(data), out = {};
      out.message = message.message;
      out.time = new Date(message.time);
      if (_.contains(messageIds, message.id)) {
        out.self = true;
        messageIds = _.remove(messageIds, message.id);
      }
      return out;
    };
    
    var startListener = function() {
      socket.stomp.subscribe(service.CHAT_TOPIC, function(data) {
        listener.notify(getMessage(data.body));
      });
    };
    
    var initialize = function() {
      socket.client = new SockJS(service.SOCKET_URL);
      socket.stomp = Stomp.over(socket.client);
      socket.stomp.connect({}, startListener);
      socket.stomp.onclose = reconnect;
    };
    
    initialize();
    return service;
  });

So, let’s first start with the bottom. At the bottom of the code you can see that we execute the initialize() function for setting up the service. This will happen exactly once, since AngularJS services are singletons, meaning that each time the same instance is returned.

The initialize() function will set up the SockJS Websocket client and use it for the Stomp.js websocket client. Stomp.js is an addition to the Websocket protocol which allows subscribing and publishing to topics and also allows JSON payloads.

When the client is connected to the WebSocket server, then the startListener() function is called, which will listen to the /topic/message topic on which all messages will be received. It will then send the data to the deferred which will be used by the controllers.

The startListener() function calls the getMessage() function which will translate the Websocket data body (= payload) to the model required by the controller. In this case it will parse the JSON string to an object, and it will set the time as a Date object.
If the message ID is listed in the messageIds array, then it means the message originated from this client, so it will set the self property to true.

Afterwards it will remove that message ID from the list, so that it’s available again inside the message ID pool.

When the connection to the Websocket server is lost, it will call the reconnect() function which will attempt to initialize the connection again after 30 seconds.

Lastly, we have the two public functions of our service, receive() and send(). Let’s start with the receive() function since this is the easiest of the two. The only thing this function does is returning the deferred used to send messages at.

The send() function on the other hand sends the message as a JSON object (stringified) and with a newly generated ID. This ID is added to the messageIds array, so that it can be used by the getMessage() function to check if the message was added by this client or by another client.

Styling

That was all Java and JavaScript code we need, so let’s finish our application by giving it some cool styles. I’m using the following CSS code:

body, * {
  font-family: 'Open Sans', sans-serif;
  box-sizing: border-box;
}

.container {
  max-width: 1000px;
  margin: 0 auto;
  width: 80%;
}

input[type=text] {
  width: 100%;
  border: solid 1px #D4D4D1;
  transition: .7s;
  font-size: 1.1em;
  padding: 0.3em;
  margin: 0.2em 0;
}

input[type=text]:focus {
  -webkit-box-shadow: 0 0 5px 0 rgba(69, 155, 231, .75);
  -moz-box-shadow: 0 0 5px 0 rgba(69, 155, 231, .75);
  box-shadow: 0 0 5px 0 rgba(69, 155, 231, .75);
  border-color: #459be7;
  outline: none;
}

.info {
  float: right;
}

form:after {
  display: block;
  content: '';
  clear: both;
}

button {
  background: #459be7;
  color: #FFF;
  font-weight: 600;
  padding: .3em 1.9em;
  border: none;
  font-size: 1.2em;
  margin: 0;
  text-shadow: 0 0 5px rgba(0, 0, 0, .3);
  cursor: pointer;
  transition: .7s;
}

button:focus {
  outline: none;
}

button:hover {
  background: #1c82dd;
}

button:disabled {
  background-color: #90BFE8;
  cursor: not-allowed;
}

.count {
  font-weight: 300;
  font-size: 1.35em;
  color: #CCC;
  transition: .7s;
}

.count.danger {
  color: #a94442;
  font-weight: 600;
}

.message time {
  width: 80px;
  color: #999;
  display: block;
  float: left;
}

.message {
  margin: 0;
}

.message .self {
  font-weight: 600;
}

.message span {
  width: calc(100% - 80px);
  display: block;
  float: left;
  padding-left: 20px;
  border-left: solid 1px #F1F1F1;
  padding-bottom: .5em;
}

hr {
  display: block;
  height: 1px;
  border: 0;
  border-top: solid 1px #F1F1F1;
  margin: 1em 0;
  padding: 0;
}

Demo

Before running our application on a webserver, check some things first. First of all, make sure you have set your context root to /spring-ng-chat/. If you don’t do that, your AngularJS service will have troubles connecting to the WebSocket server, as it connects to /spring-ng-chat/chat. If you don’t want this, you can always change the SOCKET_URL property in the AngularJS service.

Second, if you’re running this application from an embedded Tomcat in Eclipse, you may have to add your Maven dependencies to your deployment assembly. You can do this by going to your project properties, clicking on Deployment assembly and by adding the library.

deployment-assembly

Finally, make sure that the web container you’re using, supports the WebSockets Java API. If this isn’t the case, you will probably have to update your web container.

If all of that is ready, then you can start running your application, which should look like this:

initial-app

If you start writing your message, you will see that the button is now enabled and that the counter is running:

app-message

If you go too far, you will see that the button is now disabled again, and the counter is now showing a negative value in a red color:

message-limit

Once you enter a message and send it, you will see that it appears in the message list as a bold message (because you sent it). You will also see that your current message is reset to an empty string in the text box:

message-sent

If you open the application in a new window, you should see that it is empty now. WebSockets are real time, so only messages that are received at a given time, will be listed, there is no history.

If you send a message in the other window, you will see that the message appears in both screens. One will have it in bold, while the other one will see it as regular text.

multiple-messages

As you can see, the WebSockets are working properly and you will see the messages appear real time because the client sends the message to the server, which will in turn send the message to all clients.

This server-client message model is only possible thanks to WebSockets.

Achievement: Wrote a chat application with Spring, AngularJS and SockJS

Seeing this means you finished this tutorial about writing a simple chat application using WebSockets with Spring, AngularJS and SockJS. If you’re interested in the full code example, you can find it on Github. If you want to try out the code yourself, you can download an archive from Github.

Tagged , , , , .

g00glen00b

Consultant at Cronos and Tech lead at Aquafin. Usually you can find me trying out new libraries and technologies. Loves both Java and JavaScript.

  • Eduardo Di Nizo

    Hi, I’m trying to run your project but I’m having this problem. Any idea?
    Opening Web Socket… :8085/spring-ng-chat/chat/info
    Failed to load resource: the server responded with a status of 404 ()
    Whoops! Lost connection to undefined

    • Not really, this article is pretty old already, but it looks like the backend side of the websocket is not working correctly.

      • Eduardo Di Nizo

        Yes, I’m working on it. Thank you!

  • Hiếu Phan Văn
    • Looks like AngularJS isn’t loading for some reason, check your browser console if there’s any error.
      Could be that there is an issue with the article itself, since it’s pretty old already.

      • Hiếu Phan Văn

        I have fixed this bug.
        Thanks and regards

  • Hiếu Phan Văn

    Hi @g00glen00b:disqus
    I’m having an error

  • Nguyen Dang

    Great article
    But anyone can tell me how can I create android app connect to this server
    Thanks

  • Rama Nathan

    Great tutorial. Any ideas how to integrate this in an Ionic App? Thanks!

  • Javier Frias

    Hello nice tutorial, I have a question, let say you are building a real web application and you implement this solution for a chat, how do you persist the conversation and retake it later?

    • In that case you’ll have to store every message somewhere in the database as well, and initially retrieve the history of the conversation (through REST or through WebSockets as well) and push it to your model with AngularJS.

  • Partyk1d24

    npm ERR! 404 ‘stomp-websocket’ is not in the npm registry.
    npm ERR! 404 You should bug the author to publish it (or use the name yourself!)

    • Hmmz, the article is a bit outdated indeed, it’s on my schedule to rewrite this one to Spring boot one day.

  • mac

    Thanks for great tutorial, but i have a problem. My receive function works only one time (when one message is sent), For next mesages it doesn’t response (but listener receives all mesages/works good). It’s propably problem with promise. Can you help me?

  • vaztor

    And how can send message for specific user? It’s look like Spring documentation.

  • sudeep biswas

    hello I face one problem. The problem is my controller is not scan. @ComponentScan not scan my base package..

  • sudeep biswas

    hello I face one problem. The problem is my controller is not scan. @ComponentScan not scan my base package.

  • Ameer Ali

    your application complains that it can’t find angular.min.js, sockjs.min.js, lodash.min.js, stomp.min.js on the web developer console in safari. How can I fix it? The context path i’m using is set to localhost:8080/spring-ng-chat/. I’ve never used bower for javascript dependencies, Would really like to try out your application.

    Thanks

    • You’ll have to manually execute the

      bower install

      command. This should download the dependencies for you.

  • Pingback: Quora()

  • Prasanth

    When I am connecting to websocket, it gives the following error in console

    Opening Web Socket… —- sockjs.js:2940
    WebSocket connection to ‘ws://localhost:8080/ddwm-web/chat/854/b2atxbsm/websocket’ failed: Error during WebSocket handshake: Unexpected response code: 404 —- stomp.js:134
    Web Socket Opened…
    stomp.js:134 >>> CONNECT
    accept-version:1.1,1.0
    heart-beat:10000,10000

    • Check if your context path is the same as the one in the WebSocket URL and that the webcontainer you’re using supports WebSockets.

  • Prasanth

    Hello,

    Thanks for excellent tutorial. Your code is very easy to understand. Everything is perfectly working for me. It will be very helpful for me if you provide Stomp Client example, sending messages from server without client request.

    I tried to do by following this doc “http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-client” , but fully confused. Help me out from this.

  • Pingback: An example of an AngularJS Spring & websocket application | ginojava()

  • Matteo

    Hello,

    congratulations, good example!

    It would be nice to be able to change this example and allow authentication (with Spring Security) and the sender’s name in the chat!

  • Biraj Choudhury

    When I execute the app , it Gives the following errors

    http://localhost:8080/spring-ng-chat/libs/lodash/dist/lodash.min.js – 404

    Uncaught ReferenceError: _ is not defined

    I see you have used underscore , but it is not there in the bower dependencies, but even adding it doesnt solve the problem.

    Please suggest

    • Hi, it’s not using Underscore but Lodash (a similar library). But in my Bower configuration I accidently made the mistake of using the latest versions. However, Lodash already changed their location of the minified files, so it cannot find them anymore (hence the 404 error and the error that it’s not defined).

      Please update your bower.json file with the following configuration:

      {
        "name": "spring-ng-chat",
        "version": "0.0.1-SNAPSHOT",
        "dependencies": {
          "sockjs": "0.3.4",
          "stomp-websocket": "2.3.4",
          "angular": "1.3.8",
          "lodash": "2.4.1"
        }
      }
      • Biraj Choudhury

        Thanks, sorted that out, another thing is , when the server and webapp are on the same domain it works fine, but when the webapp is on a different domain and I connect using service.SOCKET_URL = “http://localhost:8080/spring-ng-chat/chat”; it doesnt work anymore, first it pings for /spring-ng-chat/chat/info , if I provide an info endpoint, then it requests strange urls like /spring-ng-chat/chat/145/4vlmlwkf/xhr_streaming etc, I have CORS filters turned on, so that is not a problem. Can you please tell me how it make it work on different domains . Thanks in advance.

        • Hmm, I actually have no experience with cross domain requests with WebSockets. Anyways, I think those weird URLs are one of the fallback-options. Spring WebSockets are compliant with SockJS and when it’s not possible to connect through WebSockets it will fallback on longpolling or another option and I think that’s what happening.

        • george_zz

          when process cross domain, you should use full http url as your destination prefix, hope this help you~~

  • flysnow

    Hi g00glen00b:
    Why it had error when i startup tomcat in intellij idea, follow is the error,can you help me check it ?
    [2015-02-04 05:52:05,508] Artifact spring-ng-chat:war exploded: Deploy took 5,108 milliseconds
    二月 04, 2015 5:52:10 下午 org.apache.catalina.startup.HostConfig deployDirectory
    INFO: Deploying web application directory F:toolsA_devZ_Othertomcattomcat-7.0.57-64webappsmanager
    二月 04, 2015 5:52:10 下午 org.apache.catalina.startup.HostConfig deployDirectory
    INFO: Deployment of web application directory F:toolsA_devZ_Othertomcattomcat-7.0.57-64webappsmanager has finished in 153 ms
    17:52:13.225 [http-apr-8080-exec-7] ERROR o.s.w.s.m.StompSubProtocolHandler – Failed to parse TextMessage payload= CONNECT
    ac.., byteCount=231, last=true] in session s1u16cc1. Sending STOMP ERROR to client.
    org.springframework.messaging.simp.stomp.StompConversionException: Illegal header: ‘ socket.stomp.subscribe(service.CHAT_TOPIC, function(data) {‘. A header must be of the form :.
    at org.springframework.messaging.simp.stomp.StompDecoder.readHeaders(StompDecoder.java:227) ~[spring-messaging-4.1.1.RELEASE.jar:4.1.1.RELEASE]
    at org.springframework.messaging.simp.stomp.StompDecoder.decodeMessage(StompDecoder.java:147) ~[spring-messaging-4.1.1.RELEASE.jar:4.1.1.RELEASE]
    at org.springframework.messaging.simp.stomp.StompDecoder.decode(StompDecoder.java:116) ~[spring-messaging-4.1.1.RELEASE.jar:4.1.1.RELEASE]
    at org.springframework.messaging.simp.stomp.BufferingStompDecoder.decode(BufferingStompDecoder.java:133) ~[spring-messaging-4.1.1.RELEASE.jar:4.1.1.RELEASE]
    at org.springframework.web.socket.messaging.StompSubProtocolHandler.handleMessageFromClient(StompSubProtocolHandler.java:198) ~[spring-websocket-4.1.1.RELEASE.jar:4.1.1.RELEASE]

    Second,why i can’t open the image in this tutoria (eg.app-example).
    Thanks!

    • That’s a weird error. I’m not sure if Apache Tomcat 7.0.57 provides the correct websocket API though. I know that in early Tomcat 7.x versions, they provided their own API and not the JEE standard.

  • Stephane

    An emphasis on the use case would help as well. A chat is not a use case, it’s an application. For example, a use case could be “The server notifies one client”. It would tell us if we can make use of the SendTo annotation and the MessageMapping annotation each on its own controller.

    • I tried to treat your feedback and added some extra content. However, it’s not my style to tell why you should use it, but how you should use it.
      I’m targetting people that know they want to use a specific library/framework, but have no clue how to use it.

      • Stephane

        I’m with you 🙂 and totally agree. I just felt like a newbie would understand the “how-to” better if the use case was narrower. It’s always easier to understand the answer when we have clearly laid out the problem we face. Thanks for the update and the good work !

  • Stephane

    Nice article that goes into detail on certain things, like the note on not confusing the AngularJS controller with the Spring controller. It could benefit from some additional clarifications thought. 1- What is a message broker ? 2- Why do we need one ? 3- What is Stomp.js ? 4- Does using Spring websockets implies using Stomp.js ? For example, could I use Spring websockets with SockJS or with Socket.io or with Primus ?

  • Magnus

    would be real helpful to actually have maven execution for bower in the pom file. something like this should work

    org.codehaus.mojo
    exec-maven-plugin

    exec-bower-install
    generate-sources

    bower

    install

    exec

    • g00glen00b

      True, we do that for our projects at a client as well. I didn’t want to spend too much attention on Bower in this tutorial though.

    • Stephane

      Hej Magnus, why would you do this ? To install the bower components at Maven build time ? But, don’t you build your webapp before hand with a grunt build ? As I do a grunt build I end up with a dist version of the app which is then copied into the Maven project with the maven-war-plugin configuration: my-${product} I guess you are doing things in a different if not better fashion ?

      • Most things you can do with Grunt (like minification, concatenation, running Jasmine tests, …) are also possible with Maven.
        He probably doesn’t use Grunt (we don’t use it either in Java projects), so building his dist version is done from Maven itself (then it makes sense that you can also run Bower from within Maven).

        • Stephane

          So you have some Gruntfile.js like configuration in your Maven pom.xml file ? You’d have an example of such a configuration ?

          • Not a Gruntfile.js file, but you can have Maven plugins that do the same thing, for example we use the following Maven plugins in our project:

            – lesscss-maven-plugin: Less compilation (similar to grunt-less)
            – minify-maven-plugin: CSS + JavaScript minification + concatenation (similar to grunt-uglify + concat)
            – jasmine-maven-plugin: Running Jasmine tests (similar to karma)

          • Stephane

            I had no clue of such plugins. Thanks for the enlightenment. I’ll do some searching around to see if I can replace my grunt usage with such plugins.