Integración de WebSocket en aplicaciones Spring Boot

WebSocket es un protocolo de comunicación bidireccional que permite la interaccción en tiempo real entre cliente y servidor. Los casos de uso más comunes incluyen salas de chat entre usuarios y sistemas de conversación con inteligencia artificial.

Dependencias necesarias

Para habilitar el soporte de WebSocket en un proyecto Spring Boot, se debe agregar el siguiente starter al archivo de configuración de Maven:

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

Configuración del servidor WebSocket

Es fundamental registrrar correctamente los endpoints WebSocket en el contexto de Spring. Si no se configura adecuadamente, aparecerá el error No mapping for GET /** en la consola, ya que Spring no podrá resolver las rutas definidas por la anotación @ServerEndpoint. El bean ServerEndpointExporter se encarga de detectar y registrar automáticamente los endpoints anotados.

@Configuration
@EnableWebSocket
@Slf4j
public class WsConfiguration implements WebSocketConfigurer {

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

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry handlerRegistry) {
        log.info("Inicializando configuración WebSocket...");
        // Registrar handlers personalizados si es necesario
        // handlerRegistry.addHandler(chatWebSocketHandler, "/ws/chat/{uid}");
    }
}

Estructura del endpoint

@Slf4j
@Component
@ServerEndpoint(value = "/ws/chat/{uid}")
public class ChatEndpoint {
    // ...
}

Atributos internos principales

// Contador de conexiones activas (thread-safe)
private static final AtomicInteger activeConnections = new AtomicInteger(0);

// Registro concurrente de sesiones activas
private static final ConcurrentHashMap<String, ChatEndpoint> sessionRegistry = new ConcurrentHashMap<>();

// Sesión del cliente actual
private Session clientSession;

// Identificador único del cliente
private String uid;

Ciclo de vida de la conexión

El método handleOpen se ejecuta cuando un cliente establece conexión. Se almacena la sesión en el registro y se incrementa el contador de conexiones activas:

@OnOpen
public void handleOpen(Session clientSession, @PathParam("uid") String uid) {
    this.clientSession = clientSession;
    this.uid = uid;
    sessionRegistry.put(uid, this);
    activeConnections.incrementAndGet();
    log.info("Conexión establecida - Cliente: {}, Sesión: {}, Total activos: {}",
             uid, clientSession.getId(), activeConnections.get());
}

El método handleClose elimina la sesión del registro y decrementa el contador:

@OnClose
public void handleClose() {
    sessionRegistry.remove(this.uid);
    activeConnections.decrementAndGet();
    log.info("Conexión cerrada - Cliente: {}, Sesión: {}, Total activos: {}",
             this.uid, clientSession.getId(), activeConnections.get());
}

Recepción de mensajes

El método handleMessage procesa los mensajes entrantes, incluyendo el mecanismo de latido (heartbeat) para mantener viva la conexión:

@OnMessage
public void handleMessage(String payload, Session clientSession) {
    // Respuesta al mecanismo de heartbeat
    if ("ping".equalsIgnoreCase(payload)) {
        dispatchMessage("pong");
        log.debug("Heartbeat respondido para sesión: {}", clientSession.getId());
        return;
    }
    // Lógica de procesamiento de mensajes (persistencia, enrutamiento, etc.)
    // repository.save(payload);
}

Manejo de errores

@OnError
public void handleError(Session clientSession, Throwable cause) {
    log.error("Error en sesión {}: {}", clientSession.getId(), cause.getMessage(), cause);
}

Envío de mensajes al cliente

private void dispatchMessage(String content) {
    if (!clientSession.isOpen()) {
        log.warn("Intento de envío a sesión cerrada: {}", clientSession.getId());
        return;
    }
    try {
        clientSession.getBasicRemote().sendText(content);
    } catch (IOException ex) {
        log.error("Fallo al enviar mensaje a sesión {}: {}", clientSession.getId(), ex.getMessage());
    }
}

Métodos estáticos para comunicación dirigida y braodcast

Para enviar un mensaje a un usuario específico, se busca su endpoint en el registro y se invoca el método de despacho:

public static void sendToClient(String uid, String content) {
    if (uid != null && !uid.isBlank() && sessionRegistry.containsKey(uid)) {
        ChatEndpoint endpoint = sessionRegistry.get(uid);
        log.info("Enviando mensaje a {}: {}", uid, content);
        endpoint.dispatchMessage(content);
    }
}

Para enviar un mensaje a todos los clientes conectados simultáneamente:

public static void broadcastMessage(String content) {
    sessionRegistry.values().forEach(endpoint -> endpoint.dispatchMessage(content));
}

Etiquetas: WebSocket Spring Boot java comunicación en tiempo real @ServerEndpoint

Publicado el 7-19 12:38