WebSocket using Spring Boot java

Partha Sai Guttikonda
5 min readApr 30, 2022

--

šŸš€ We will work on both global and private messaging from Spring boot app.

Photo by Pavan Trikutam on Unsplash

šŸ“ø Introduction:

WebSocket is a duplex protocol used mainly in the client-server communication channel. Itā€™s bidirectional in nature which means communication happens to and fro between client-server.
WebSocket endpoints starts with ws:// or was://.

šŸš¶ Prerequisite:

  1. Basic knowledge on Spring Boot Java, html and javascript.
  2. java and gradle OR maven needs to be installed.(Iā€™m using gradle).

šŸŽÆGoals:

  1. We will be creating a public web-socket api in Spring Boot Java.(global message sending).
  2. We will create a web-socket api for user wise message sending.(we demo a way with which we can assign unique id to every handshake request.)
  3. Play around web-socket to trigger messages by http call to server.
    Note ā†’ Complete code with ui is available at the end of the article.

šŸ§² Web socket api:

UI will be listening to the ws endpoint continuously, So that any event trigger to it, Will be handled in UI. Also UI can send messages to server using ws endpoints.

šŸ›  Working:

from wallarm.com
  1. UI will be sending HTTP upgrade hand shake request to server.
  2. As a response server will return connection open.
  3. now UI can listen to the end point and also can send data to the end point.
  4. if we want to close the connection. It can be done by either UI or server.
Photo by Volodymyr Hryshchenko on Unsplash

šŸ“” Spring Boot code for public message:-

Configuration:

  • Letā€™s Enable WebSocket Message Broker. needs to be done in config file.
  • In this code we need to set the Hand Shake end point (ā€˜/websocketā€™).
  • Followed by that we will be setting prefixes for the websocker listening endpoints and sending messages endpoints.(ā€œ/receiveā€,ā€wsā€)
  • As per the code all the listening endpoints will be like this
    ā€œws://localhost:8080/receive/messageā€
    ā€œws://localhost:8080/user/receive/private-messageā€
  • where as sending messages to server will be like this
    ā€œws://localhost:8080/ws/messageā€
    ā€œws://localhost:8080/ws/private-messageā€
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WSConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(final MessageBrokerRegistry registry){
registry.enableSimpleBroker("/receive");
registry.setApplicationDestinationPrefixes("/ws");

}

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

DTO:

  • We are using two Data transfer object one is Message and Response Message which contains one String object ,Getter and Setters.
public class Message {
private String messageContent;

public String getMessageContent() {
return messageContent;
}

public void setMessageContent(String messageContent) {
this.messageContent = messageContent;
}
}
public class ResponseMessage {
private String content;

public ResponseMessage() {
}

public ResponseMessage(String content) {
this.content = content;
}
}

Controller:

  • Now we are ready to create a controller to web socket.
import dto.Message;
import dto.ResponseMessage;
import java.security.Principal;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;

@Controller
public class MessageController {

@MessageMapping("/message")
@SendTo("/receive/message")

public ResponseMessage getMessage(final Message message) throws InterruptedException {
Thread.sleep(1000);
System.out.println("recived message");
return new ResponseMessage(HtmlUtils.htmlEscape(message.getMessageContent()));
}

}
  • As per the above code UI will be listening to ā€œws://localhost:8080/receive/messageā€
  • Sending messages to ā€œws://localhost:8080/ws/messageā€
  • On sending message we are responding the same message to all the listeners to the endpoint.
Photo by Brett Jordan on Unsplash

šŸ¤ Spring Boot code for private message:-

ClientHandshakeHandler:

  • Here we will be placing the logic for identifying the UIā€™s based on the login, ip address etc. It can be any thing.Here Iā€™m just passing random UUID as identification parameter.
import com.sun.security.auth.UserPrincipal;
import java.security.Principal;
import java.util.Map;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;

public class ClientHandshakeHandler extends DefaultHandshakeHandler {

private final Logger logger = LoggerFactory.getLogger(ClientHandshakeHandler.class);

@Override
protected Principal determineUser(ServerHttpRequest req, WebSocketHandler weHandler, Map<String, Object> attributes) {
final String randId = UUID.randomUUID().toString();
logger.info("{}",attributes.get("name"));
logger.info("User opened client unique ID {}, ipAddress {}",randId,req.getRemoteAddress());
return new UserPrincipal(randId);
}

}

Configuration-(In the Same config file)

  • The above created ClienthandshakeHandler will be passed in the configuration on we sockets so that for every new handshake we will create a new uuid for the UI.
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WSConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(final MessageBrokerRegistry registry){
registry.enableSimpleBroker("/receive");
registry.setApplicationDestinationPrefixes("/ws");
}

@Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/websocket")
.setHandshakeHandler(new ClientHandshakeHandler())
.withSockJS();

}
}

Controller-(In the same controller file)

import dto.Message;
import dto.ResponseMessage;
import java.security.Principal;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;

@Controller
public class MessageController {

@MessageMapping("/message")
@SendTo("/receive/message")
public ResponseMessage getMessage(final Message message) throws InterruptedException {
Thread.sleep(1000);
System.out.println("recived message");
return new ResponseMessage(HtmlUtils.htmlEscape(message.getMessageContent()));
}

@MessageMapping("/private-message")
@SendToUser("/receive/private-message")

public ResponseMessage getPrivateMessage(final Message mess, final Principal principal) throws InterruptedException {
Thread.sleep(1000);
System.out.println("recived message private"+principal.getName());
return new ResponseMessage(HtmlUtils.htmlEscape("Sending personal message to user"+principal.getName()+": "+ mess.getMessageContent()));

}

}
  • Now will send the message to only the ui which send the request.

šŸ„· Play Around:

  • Trigger public and private message using Rest api call.

Controller for HTTP request:

@RestController
public class WSController {

@Autowired
WSService wsService;

@PostMapping("/send-message")
public void sendMessage(@RequestBody Message message){
wsService.sendMessage(message.getMessageContent());
}

@PostMapping("/send-private-message/{id}")
public void sendPrivateMessage(@RequestBody Message message, @PathVariable String id){
wsService.sendPrivateMessage(message.getMessageContent(),id);
}

@RequestMapping(value = "/send-notification-global",method = RequestMethod.POST)
public void sendNotification(@RequestBody Message message){
System.out.println("hello");
wsService.sendNotification(message.getMessageContent());
}

@RequestMapping(value = "/send-notification-private/{id}",method = RequestMethod.POST)
public void sendPrivateNotification(@RequestBody Message message, @PathVariable String id){
wsService.sendPrivateNotification(message.getMessageContent(),id);
}
}

Service for Rest api calls:

SimpMessagingTemplate.java is used to trigger web-socket events. If we provide id of the client to the method convertAndSend then it will send to that client only else will send it to all the clients listening to the ws end point.

@Service
public class WSService {

private final SimpMessagingTemplate messagingTemplate;

@Autowired
public WSService(SimpMessagingTemplate messagingTemplate){
this.messagingTemplate = messagingTemplate;
}

public void sendMessage(final String message){
ResponseMessage res = new ResponseMessage(message);
messagingTemplate.convertAndSend("/receive/message",res);
}
public void sendPrivateMessage(final String message, final String id){
ResponseMessage res = new ResponseMessage(message);
messagingTemplate.convertAndSendToUser(id,"/receive/private-message", res);
}
public void sendNotification(final String message) {
ResponseMessage res = new ResponseMessage(message);
messagingTemplate.convertAndSend("/receive/global-notification",res);
}
public void sendPrivateNotification(final String message,final String id) {
ResponseMessage res = new ResponseMessage(message);
messagingTemplate.convertAndSendToUser(id,"/receive/private-notification",res);
}

}
Demo

Complete code with UI is available at

Thank youā€¦!šŸ˜

--

--

Partha Sai Guttikonda
Partha Sai Guttikonda

Written by Partha Sai Guttikonda

Engineering Intelligence: ML in Imaging | Full-Stack AI Innovator

No responses yet