Skip to main content

Command Palette

Search for a command to run...

First WebSocket & Stomp Experience

Updated
5 min readView as Markdown

For a project where I want to be able to offer more interactivity and concurrent modifications, I decided to use a WebSocket. Since my backend is written in Spring, after some research, I decided to use Stomp on top of the WebSocket layer as that would give me the best integration with Spring. All in all, the experience went pretty smooth, but there were some caveats which I will reveal in this blog post.

The initial setup was fairly easy, but it is rather difficult to verify it works. The typical way I quickly check this is by using Postman and make a call. Although Postman supports WebSockets, it does not have support for Stomp, which means that you need to have some other client to actually check something. I decided to simply implement the StompJs client in my front-end. This is not ideal as it now becomes more difficult to figure out what is wrong if something doesn't work. Did you do something wrong in the back-end or in the front-end?

Something you will likely bump into if you combine WebSockets with regular REST endpoints, is that the WebSocket has it's own CSRF filter/configuration. Even you have specified the CSRF to be completely disabled, which is the case for me since I use stateless services, I was still hitting CSRF issues when trying to connect with the websocket endpoint. To fix this, you have to implement/override the ChannelInterceptor (org.springframework.messaging.support.ChannelInterceptor) and simply return your own empty interceptor.

@Bean
ChannelInterceptor csrfChannelInterceptor() {
	// Disable csrf
	return new ChannelInterceptor() {
	};
}

Another word on security: Stomp implements security on it's own connect frame, so you want to make sure that the WebSocket endpoint itself (which is the endpoint to which a request is sent to initiate the connection) is not secured. While you can make this endpoint secure as well, there isn't really any point in doing so, since Stomp relies on it's own authentication. I will dedicate a next blog post to setting up JWT authentication with StompJs and Spring. Even now, with all of this configured, I still have an outstanding issue regarding the expiration of the JWT. Since the token is only checked when connecting, the connection remains valid even after the used token is no longer valid.

Another thing you will hit is the mismatch in default setup between StompJs and Spring. By default StompJs will have a heartbeat configured for incoming and outgoing heartbeats. Spring however has by default no outgoing heartbeats configured. This means that your client will constantly disconnect and re-connect because it is not receiving the expected heartbeat. To configure a heartbeat in Spring you have to not only configure the heartbeat values, but also provide a scheduler since sending the heartbeat needs to be done asynchronously and initiated by the server.

@Override
public void configureMessageBroker(final MessageBrokerRegistry config) {
	config.enableSimpleBroker("/topic", "/queue")
			.setHeartbeatValue(new long[] { 10000, 10000 })
			.setTaskScheduler(scheduler);
	config.setUserDestinationPrefix("/user");
	config.setApplicationDestinationPrefixes("/app");
}

My initial approach for my WebSocket endpoint was simple. I had a single endpoint to which I send all my requests. This is the way WebSockets worked in my head. I did however quickly ran into issues with this method just blowing up as it had to support all different types of actions, causing it to know about everything. The thing is that, even though you have only a single connection, this doesn't mean you can't distinguish different endpoints on that connection. So I eventually took a similar approach as with regular REST endpoints. Instead of having a single endpoint to which I send all actions, I now have dedicated endpoints, one per resource, and I can even go beyond that and can introduce a separate endpoint for creating and updating. A separate endpoint for deleting will not be possible since the endpoint does not have a method and is completely identified by the URL (theoretically you can have a dedicated URL for delete by adding a /delete suffix, but this violates the REST rules, and from that point of view it is not a good idea).

Besides your endpoints which are used to send updates, you will want to have two extra endpoints:

  1. For initial loading of data (subscribe on app/meetings/id)

  2. To subscribe on events sent by the back-end (subscribe on topic/meetings/id)

Note the difference in prefix between the two endpoints. This is because the first is managed by Spring itself, and the latter one is handled by the broker. The second subscribe never hits your actual application logic.

Note that the first endpoint can also be done with a regular REST endpoint if desired, but I decided to go with a subscribe since this makes most sense. Why would I open a new connection to load the initial data? I may as well use the existing one. One caveat however, is that with this approach, you have to unsubscribe from that endpoint after receiving the initial data.

This were the issues I encountered when setting up WebSockets and Stomp for the first time with Spring. I did not go into too much details, and I have not given any code examples, which I can do If desired. Generally though, the documentation from Spring and StompJs is clear enough to get you going when you know what to look for.