In this post I try to describe how to get up and running with Spring with raw Websockets and angular2. Code for this post is located here
Websockets allow http clients to have a persistent connection with server, which allows for low latency interaction between server and client. Setting up Websocket with spring boot is super easy, thanks to spring boot. For this article I have taken the spring boot Websockets example from here, I have changed to use raw Websockets instead of SockJS.
With spring to handle request from clients via Websocket, we need implement a WebsocketHandler. The CounterHandler class in my case TextWebsocketHandler
which is a subclass of WebsocketHandler
. I have overridden two methods from the super class. The method afterConnectionEstablished
allows us do something after connection is established and the handleTextMessage
method is to do something when a message is received from client.
@Component
public class CounterHandler extends TextWebsocketHandler {
WebsocketSession session;
// This will send only to one client(most recently connected)
public void counterIncrementedCallback(int counter) {
System.out.println("Trying to send:" + counter);
if (session != null && session.isOpen()) {
try {
System.out.println("Now sending:" + counter);
session.sendMessage(new TextMessage("{\"value\": \"" + counter + "\"}"));
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("Don't have open session to send:" + counter);
}
}
@Override
public void afterConnectionEstablished(WebsocketSession session) {
System.out.println("Connection established");
this.session = session;
}
@Override
protected void handleTextMessage(WebsocketSession session, TextMessage message)
throws Exception {
if ("CLOSE".equalsIgnoreCase(message.getPayload())) {
session.close();
} else {
System.out.println("Received:" + message.getPayload());
}
}
}
Now you might be wondering how do you send messages asynchronously to client. To send a message to client you need to get hold of client’s session. This is the reason I have captured the client session after client is connected. One drawback of my implementation is that only one client receives messages from server at any time. However this is easy to solve by separating the code to a new wrapper class with the websocket, so we have all the client sessions. The CounterService
class periodically calls the counterIncrementedCallback
method which send the counter value to the recently connected client. Simple isn’t it?
@Component
public class CounterService {
private AtomicInteger counter = new AtomicInteger(0);
@Autowired
CounterHandler counterHandler;
@Scheduled(fixedDelay = 1000)
public void sendCounterUpdate() {
counterHandler.counterIncrementedCallback(counter.incrementAndGet());
}
}
Websocket configuration for spring looks as below
@Configuration
@EnableWebsocket
@EnableScheduling
public class WebsocketConfig implements WebsocketConfigurer {
@Autowired
CounterHandler counterHandler;
@Override
public void registerWebsocketHandlers(WebsocketHandlerRegistry registry) {
registry.addHandler(counterHandler, "/counter");
}
}
On the client side (code in src/main/resources/static), angular2 wrapper for Websocket is copied from https://github.com/afrad/angular2-websocket and the code looks straight forward. The getDataStream
method returns a Subject
which our code listens to update the value of counter
subscribe($event) {
console.log("trying to subscribe to ws");
this.ws = new $Websocket("ws://localhost:8088/counter");
this.ws.send("Hello");
this.ws.getDataStream().subscribe(
res => {
var count = JSON.parse(res.data).value;
console.log('Got: ' + count);
this.counter = count;
},
function(e) { console.log('Error: ' + e.message); },
function() { console.log('Completed'); }
);
}
Reference: * Spring Docs: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html