Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Integrating WebSocket in Spring Boot Applications

Tech 4

To enable WebSocket functionality in a Spring Boot project, include the following dependency in your pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

Configure WebSocket support by creating a configuration class:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfiguration {
    @Bean
    public ServerEndpointExporter endpointExporter() {
        return new ServerEndpointExporter();
    }
}

Implement a WebSocket endpoint to manage client connections and messages. This example demonstrates a chat server that broadcasts messages to all connected clients:

import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@ServerEndpoint("/chat/{user}")
public class ChatEndpoint {
    private static AtomicInteger activeConnections = new AtomicInteger(0);
    private static Map<String, Session> sessionRegistry = new ConcurrentHashMap<>();

    @OnOpen
    public void handleConnectionOpen(Session session, @PathParam("user") String user) {
        activeConnections.incrementAndGet();
        sessionRegistry.put(session.getId(), session);
        System.out.println(LocalDateTime.now() + ": Connection established with user " + user + ", ID: " + session.getId() + ", Total connections: " + activeConnections);
    }

    @OnClose
    public void handleConnectionClose(Session session, @PathParam("user") String user) {
        activeConnections.decrementAndGet();
        sessionRegistry.remove(session.getId());
        System.out.println(LocalDateTime.now() + ": Connection closed for user " + user + ", ID: " + session.getId() + ", Total connections: " + activeConnections);
    }

    @OnError
    public void handleConnectionError(Throwable error, Session session, @PathParam("user") String user) {
        error.printStackTrace();
    }

    @OnMessage
    public void handleIncomingMessage(Session session, String message, @PathParam("user") String user) throws IOException {
        System.out.println(LocalDateTime.now() + ": Message from session " + session.getId() + ": " + message);
        broadcastMessage(message);
    }

    private void broadcastMessage(String content) {
        sessionRegistry.values().forEach(session -> {
            session.getAsyncRemote().sendText(content);
        });
    }
}

For scenarios reuqiring dependancy injection in WebSocket endpoints, implement ApplicationContextAware too access Spring beans:

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.Instant;

@Component
@ServerEndpoint("/echo")
public class EchoEndpoint implements ApplicationContextAware {
    private Session currentSession;
    private static ApplicationContext springContext;
    private MessageRepository messageRepo;

    @OnMessage
    public void processMessage(String text) throws IOException {
        Message msg = new Message();
        msg.setReceiverId(1L);
        msg.setSenderId(2L);
        msg.setText(text);
        messageRepo.save(msg);
        System.out.println("[WebSocket] Received message: " + text + " from session " + currentSession.getId());

        if (text.equalsIgnoreCase("bye")) {
            currentSession.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Goodbye"));
            return;
        }

        currentSession.getAsyncRemote().sendText("[" + Instant.now().toEpochMilli() + "] Echo: " + text);
    }

    @OnOpen
    public void initializeConnection(Session session, EndpointConfig config) {
        this.currentSession = session;
        this.messageRepo = springContext.getBean(MessageRepository.class);
        System.out.println("[WebSocket] New connection: " + session.getId());
    }

    @OnClose
    public void terminateConnection(CloseReason reason) {
        System.out.println("[WebSocket] Connection closed: " + currentSession.getId() + ", reason: " + reason);
    }

    @OnError
    public void handleError(Throwable exception) throws IOException {
        System.out.println("[WebSocket] Error in session " + currentSession.getId() + ": " + exception.getMessage());
        currentSession.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, exception.getMessage()));
    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        EchoEndpoint.springContext = context;
    }
}

A Vue.js client can connect to the WebSocket server and send/receive messages. This example uses a simple chat interface:

<template>
  <div>
    <table>
      <thead>
        <tr>
          <th>Message ID</th>
          <th>Sender</th>
          <th>Time</th>
          <th>Content</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="msg in messages" :key="msg.id">
          <td>{{ msg.id }}</td>
          <td>{{ msg.sender }}</td>
          <td>{{ new Date(msg.timestamp).toLocaleTimeString() }}</td>
          <td>{{ msg.content }}</td>
        </tr>
      </tbody>
    </table>
    <input type="text" v-model="inputText" placeholder="Enter message" />
    <button @click="sendMessage">Send</button>
    <button @click="disconnect">Disconnect</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      socket: null,
      user: '',
      messages: [],
      inputText: ''
    };
  },
  created() {
    this.user = localStorage.getItem('username');
    if (!this.user) {
      this.$router.push({ name: 'Login' });
    }
    this.initializeSocket();
  },
  beforeDestroy() {
    if (this.socket) {
      this.socket.close();
    }
  },
  methods: {
    initializeSocket() {
      const url = `ws://localhost:8080/chat/${this.user}`;
      this.socket = new WebSocket(url);
      this.socket.onopen = (event) => console.log('Connected:', event);
      this.socket.onmessage = (event) => {
        const data = JSON.parse(event.data);
        this.messages.push(data);
      };
      this.socket.onerror = (event) => console.error('Error:', event);
      this.socket.onclose = (event) => console.log('Disconnected:', event);
    },
    sendMessage() {
      const payload = {
        id: Date.now(),
        content: this.inputText,
        sender: this.user,
        timestamp: new Date().getTime()
      };
      this.socket.send(JSON.stringify(payload));
      this.inputText = '';
    },
    disconnect() {
      localStorage.removeItem('username');
      this.socket.close();
      this.$router.push({ name: 'Login' });
    }
  }
};
</script>

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.