when automagical becomes autotragical...
I have a love/hate relationship with frameworks that are automagical.
On one hand, they can save you a lot of time and effort, preventing you from writing tons of boilerplate code, and just less code in general (every line of code is one you have to support, right?).
There's a dark side to this however. Often times automagical frameworks have rules, usually modeled by conventions. If you violate these, the framework will slap you across the face with a stacktrace. Sometimes the stacktrace is meaningful. Sometimes it's outright esoteric. Sometimes it tells you something that you can't accurately discern without digging into the bowels of the code that you're trying to leverage without creating bowels yourself.
In this case, Spring was guilty of said crimes on several occasions.
Don't get me wrong; I'm quite fond of Spring. However, in this case the solution involved going roughly shoulder deep into Spring's bowels to find simple, yet not obvious solutions to problems. Here's a list of some of the problems I faced, in the event that someone out on the intarwebs is searching for a way to make sense of it all:
- Spring Boot, at least when I wrote my code, seems to despise Tomcat 7.0.51 (though this appears to have been resolved now). This is particularly important, because in 7.0.51 they fixed a bug that prevented pure websockets from working correctly with... Spring (among other things no doubt).
- When using Spring's WebSocket API, their scheduling API ends up hijacked. If you need to add your own scheduled bean, you need to override things (as shown in my code).
- If you have a queue for your messages, and you don't have the URI start with 'queue', you can't use user specific message queues. Regular queues will work just fine.
- The STOMP heartbeat configuration doesn't work for browser clients since you have to deterministically configure which clients you have that will need to support a heartbeat on the backend. I had to roll my own here.
- You can't inject the
SimpMessagingTemplateinto your configuration class or it's bootstrapping path if you're using constructor injection. This can be important if you're trying to set up a
ChannelInterceptorto filter out messages before they hit a resource in your application. (Also, how important is it to shave off the 'le' exactly? It really can't just be called
- When I was using Spring 4.0.0.RELEASE, none of this was working correctly. Switching to 4.0.1.RELEASE magically fixed all my problems. I won't ask how or why.
There were several other issues that I've long since repressed. I wrote this code a while ago, and became so irritated with the number of gotchas that I didn't feel motivated at all to blog about it.
All of that aside, Spring's implementation is still leaps and bounds better than what Jetty is offering. For reasons I can't comprehend, the people who write the Jetty API seem to think that every class should be polymorphic to every other class, making it ridiculously confusing to set up. I cannot emphasize enough how many times during my use of their API that I thought "wait... this class is an instance of that?"
if you just want a working demo to play with
Then feel free to grab my code on Github. Did I mention that every example I found online was broken out of the box?
getting your configurations built
We're going to use four different features: web sockets, security, scheduling, and auto configuration. Each one will have its own configuration class, as shown below:
auto configuration, aka spring boot
security, because we need to log in to chat right?
scheduling, because we have a heartbeat to schedule with the web socket api
web sockets, because obviously
There are a few things worth pointing out in this configuration. The
/activeUsers paths will correspond to Spring MVC controllers, while the
/topic paths will correspond to the messaging API's pub/sub model.
designing the application
Before we get into the implementation, I'd like to address the design and talk about some of the details of using STOMP here.
First, you may be wondering what STOMP is. In this case, it's not an off-broadway production; it stands for Simple (or Streaming) Text Oriented Messaging Protocol. There's more about this on Wikipedia and the STOMP website, but in a nutshell it's a command based protocol for sending messages, interoperable with any provider or client implementing the STOMP spec. In the case of this example, it can use web sockets or not; it doesn't matter since the spec works either way.
STOMP and Spring allow us to set up queues, and more interestingly user specific queues. Semantically this is what you'd want in a chat application: each user can subscribe to a queue of messages coming to them, and the front end can route them into the correct chat window. In this case, our front end will simply subscribe to
/user/queue/messages, and on the back end Spring will create a dynamic URL to map those messages to that matches the session of the user. The nice thing here is that each user subscribes to the same queue, but they only get their own messages. Even more sophisticated is that Spring can map multiple sessions for one user, so you can be signed in at multiple locations and get the same chat. All of this maps up with Spring Security as well, so as long as you have that configured all of your sessions will be mapped to the correct user queue, all messages received by the client will show up in a chat window corresponding to the other person in chat.
STOMP and Spring also allow us to set up topics, where every subscriber will receive the same message. This is going to be very useful for tracking active users. In the UI, each user subscribes to a topic that reports back which users are active, and in our example that topic will produce a message every 2 seconds. The client will reply to every message containing a list of users with its own heartbeat, which then updates the message being sent to other clients. If a client hasn't checked in for more than 5 seconds (i.e. missed two heartbeats), we consider them offline. This gives us near real time resolution of users being available to chat. Users will appear in a box on the left hand side of the screen, clicking on a name will pull up a chat window for them, and names with an envelope next to them have new messages.
writing an implementation
Since we're writing a chat client, we need some way of modeling a message, as shown below:
We also need a place to send messages to; in this case a controller just like you would create in Spring MVC:
There's some interesting stuff happening here. First, we're injecting an instance of
SimpMessagingTemplate into our controller. This class is what allows us to send messages to individual user queues via the method
convertAndSendToUser. In our controller we're also assigning the sender ourselves based on the session information that Spring Security has identified, as allowing the client to specify who the sender was produces the same problem the email spec currently has. It's worth noting here that the message is sent to both the sender and the recipient, indicating that the message has passed through the server before being seen in the sender's client's chat window. We ignore the case of sending a message to the recipient in case you're messaging yourself for some reason so that you don't get double messaging (though I should probably just ditch that case altogether).
Lastly, I'd like to draw your attention to the
@MessageMapping annotation, which binds the method the annotation is on to the path we set up in our configuration class earlier.
That's actually all we need server side to send and receive messages believe it or not. Next is determining who's signed in.
We're going to start with a controller that receives a heartbeat from users who are signed into chat:
In this case, we're receiving a message that contains information in the header about who the user is, and mark their most recent heartbeat in a class called the
ActiveUserService, which can be seen below:
When we call the
mark method, we set an updated time for when the user last checked in, which is stored in a Google Guava
LoadingCache instance. We also have a method that will aggregate the user names of all the active users, based on the metric of them checking in within the last 5 seconds. This is the information we want to send back to our topic, which is handled with the code below:
In this class, we combine the service and the template to send a message to a topic every two seconds, letting all clients know which users are available to chat. In return, users can reply when receiving a message, telling the server they're still active, creating a round trip heartbeat.
putting it all together with a user interface
And here's where I step out into the realm of things I actively try to avoid: creating a user interface. I am using jQuery here, which makes the task far easier, but I'm still admittedly not great in this department.
Before we can do anything, we need to connect (this fires when the document is ready):
In this method, we do three very important things:
- We capture the user name for the session (this is used for figuring out who we're chatting with and for coloring text in the chat window).
- We subscribe to a queue that's bound to our user, and call
showMessagewhen we receive one.
- We subscribe to a topic that sends out all the active user names, and call
showActivewhen we get an update.
There's something slightly peculiar here worth calling out: notice that we construct the
SockJS instance with
/app/chat. Spring seems to have some kind of handshake here that I don't fully understand. If you change the value in this constructor, it will fail saying that
/app/chat/info doesn't exist, so Spring appears to expose the resource
/chat/info here to facilitate a handshake with SockJS.
showing a message
Whenever we receive a message, we want to render it in the chat window for that user, which is done in the code below:
First, we figure out which chat window we should load based on comparing the recipient to our username. We then create a span with the new message, colored to indicate the sender, append it to the window, and scroll to the bottom of the window to show the latest message. If we're updating a chat window that's not currently visible to the user, we render an envelope character next to their name in the user list to indicate that there are pending messages. Below is the code to create the envelope:
getting the chat window
In the example above we obtain a chat window to update with message, but we have to create it if it doesn't already exist, as shown below:
We send the message to
/app/chat, which routes to our
MessageController class, which will then produce a message to the appropriate queues. After sending the message, we clear our the textarea for the next message.
showing active users
As I mentioned above, whenever the server tells us about the active users, the client reports back that it's active, allowing us to have a heartbeat. The first method in the code below sends the message back to the server, while the second renders the active users:
There's a lot going on here, so let me break it down:
- First, capture who was previously selected.
- Second, capture which users had pending messages. We'll want to re-render this with the new list.
- Create a new
divfor the next user list.
- As we process users, preserve the one that was previously selected by assigning the appropriate CSS class (
- Bind a click event to each user that will hide the current chat window, removed the select class from all users and show them as unselected, remove the pending messages status, pull up the chat window for that user, and mark that user as selected.
- If a user was added to the new list that previously had messages pending, redisplay the envelope icon.
try it out!
Like I mentioned, this is available on Github for you to play with. I know there's a lot covered in this blog post, and I'll probably update it several times to fill in gaps as a read it and/or get feedback. I hope this helps you get started using Spring and STOMP for doing some pretty sophisticated messaging between the browser and the server.