Implementing WebSocket in Projects
Recently, our project had a requirement where users open a tab from our System A to operate System B, and when they click a certain operation button, it calls back our system’s API to change some data. We needed to execute a series of actions when events occur, such as page refreshing. The underlying requirement actually tells us that our system needs the ability for the backend to actively push messages to the frontend. How to achieve this? WebSocket.
So, I began building support for it.
Tech Stack
First, let me introduce the current project’s tech stack:
- React
v16.4.2
- SpringBoot
v2.1.6.RELEASE
WebSocket technology itself is quite mature, so configuration and development for these two areas isn’t very difficult. Here are the key code blocks:
SpringBoot
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocket").setAllowedOrigins("*").withSockJS();
}
}
@RestController
public class GreetingController {
private final SimpMessagingTemplate simpMessagingTemplate;
public GreetingController(SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
System.out.println("Request: get message: " + message);
return new Greeting("Response: Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
@GetMapping("/broadcast")
public String sendBroadcastCommand(@RequestParam String command) {
simpMessagingTemplate.convertAndSend("/topic/broadcast", command);
return command;
}
}
React
<script src="static/js/stomp.min.js"></script>
<script src="static/js/sockjs-1.0.0.min.js"></script>
function watchBroadcast() {
const socket = new SockJS('/websocket');
const client = Stomp.over(socket);
client.connect(
{},
() => {
client.subscribe('/topic/broadcast', res => {
console.log(res.body)
});
}
);
}
Result
When the /broadcast
API is called, the backend sends messages to the frontend.
It’s that simple, but there are still some pitfalls in actual development. Let’s continue:
Important Notes
- If the backend has
interceptors
, remember to set up a whitelist for paths like/websocket
, otherwise you’ll get a302
error. SocketJS versions need to match between frontend and backend
. Here my backend SocketJS version is1.0.0
, so the frontend sockjs must also be1.0.0
, otherwise compatibility errors will occur.- During development, I encountered an issue where everything worked fine locally but returned a 500 error when deployed. The reason was that deployment used
spring-boot-starter-undertow
, which wasn’t the container used locally. I ended up removing it. Note that undertow should be supported, but it might be a version issue. - In my scenario, the backend API is called and then sends messages to the frontend, so I’m using GetMapping and simpMessagingTemplate together. If you need two-way communication, the first example is sufficient. Testing with both GetMapping and SendTo annotations didn’t work, so it seems this approach is necessary for this scenario.
- WebSocket doesn’t add new port numbers, still using 80 and 443, but since communication uses the WS protocol, container layers like Nginx and browser clients need to support it.
- Multiple connections can be established for the same service, and multiple subscriptions can be made for the same event, so be careful to avoid duplicate processing issues and wasted resources.
Conclusion
- Why do we need WebSocket? Consider this statement: “HTTP protocol has a flaw: communication can only be initiated by the client, while WebSocket enables two-way communication between web frontend and backend.” So when to use it is quite clear.
- After implementing WebSocket in our project, similar requirements became much simpler (such as server-side directional collection of client information), making it worthwhile.