Saltearse al contenido

Introducción a WebSockets

Introducción a WebSockets

WebSocket es un protocolo de comunicación bidireccional y full-duplex que opera sobre una única conexión TCP. Permite la comunicación en tiempo real entre clientes y servidores, lo que es ideal para aplicaciones que requieren actualizaciones frecuentes como chats, juegos en línea o dashboards en tiempo real.

Ventajas de WebSockets:

  • Comunicación bidireccional en tiempo real
  • Menor latencia que HTTP polling
  • Reducción de la sobrecarga del servidor
  • Eficiencia en el uso de ancho de banda

WebSockets en Spark

Spark utiliza la implementación de WebSocket de Jetty, que viene incluida en el framework. Esto significa que no necesitamos agregar dependencias adicionales para usar WebSockets en nuestra aplicación Spark.

Configuración Básica

Para usar WebSockets en Spark, necesitamos importar lo siguiente:

1
import static spark.Spark.*;
2
import org.eclipse.jetty.websocket.api.*;
3
import org.eclipse.jetty.websocket.api.annotations.*;

Implementación Básica de WebSocket

Aquí tienes un ejemplo básico de cómo implementar WebSockets en Spark:

1
public class WebSocketExample {
2
public static void main(String[] args) {
3
webSocket("/websocket", WebSocketHandler.class);
4
init(); // Inicializa Spark
5
}
6
7
@WebSocket
8
public static class WebSocketHandler {
9
10
@OnWebSocketConnect
11
public void onConnect(Session session) throws Exception {
12
System.out.println("Conexión WebSocket abierta");
13
}
14
15
@OnWebSocketClose
16
public void onClose(Session session, int statusCode, String reason) {
17
System.out.println("Conexión WebSocket cerrada");
18
}
19
20
@OnWebSocketMessage
21
public void onMessage(Session session, String message) {
22
System.out.println("Mensaje recibido: " + message);
23
try {
24
session.getRemote().sendString("Eco: " + message);
25
} catch (Exception e) {
26
e.printStackTrace();
27
}
28
}
29
30
@OnWebSocketError
31
public void onError(Session session, Throwable error) {
32
System.out.println("Error WebSocket: " + error.getMessage());
33
}
34
}
35
}

Anotaciones de WebSocket

Spark utiliza anotaciones de Jetty para manejar eventos de WebSocket:

  • @WebSocket: Marca una clase como un manejador de WebSocket.
  • @OnWebSocketConnect: Se llama cuando se establece una nueva conexión.
  • @OnWebSocketClose: Se llama cuando se cierra una conexión.
  • @OnWebSocketMessage: Se llama cuando se recibe un mensaje.
  • @OnWebSocketError: Se llama cuando ocurre un error.

Manejo de Sesiones

Cada conexión WebSocket está representada por un objeto Session. Podemos usar este objeto para enviar mensajes al cliente:

1
@OnWebSocketMessage
2
public void onMessage(Session session, String message) {
3
try {
4
session.getRemote().sendString("Mensaje recibido: " + message);
5
} catch (IOException e) {
6
e.printStackTrace();
7
}
8
}

Broadcast de Mensajes

Para enviar un mensaje a todos los clientes conectados, puedes mantener una lista de sesiones:

1
public class WebSocketHandler {
2
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());
3
4
@OnWebSocketConnect
5
public void onConnect(Session session) {
6
sessions.add(session);
7
}
8
9
@OnWebSocketClose
10
public void onClose(Session session, int statusCode, String reason) {
11
sessions.remove(session);
12
}
13
14
public static void broadcast(String message) {
15
sessions.forEach(session -> {
16
try {
17
session.getRemote().sendString(message);
18
} catch (IOException e) {
19
e.printStackTrace();
20
}
21
});
22
}
23
}

Seguridad en WebSockets

Autenticación

Podemos implementar autenticación en la conexión WebSocket:

1
@OnWebSocketConnect
2
public void onConnect(Session session) throws Exception {
3
String token = session.getUpgradeRequest().getHeader("Authorization");
4
if (!isValidToken(token)) {
5
session.close(StatusCode.POLICY_VIOLATION, "Token inválido");
6
}
7
}

Manejo de Errores y Excepciones

Es crucial manejar adecuadamente los errores en WebSockets:

1
@OnWebSocketError
2
public void onError(Session session, Throwable error) {
3
System.err.println("Error en WebSocket: " + error.getMessage());
4
try {
5
session.close(StatusCode.SERVER_ERROR, "Error interno del servidor");
6
} catch (IOException e) {
7
e.printStackTrace();
8
}
9
}

11. Limitación de Velocidad (Rate Limiting)

Para prevenir abusos, podemos implementar limitación de velocidad:

1
private static final Map<Session, Long> lastMessageTime = new ConcurrentHashMap<>();
2
3
@OnWebSocketMessage
4
public void onMessage(Session session, String message) {
5
long now = System.currentTimeMillis();
6
long lastTime = lastMessageTime.getOrDefault(session, 0L);
7
if (now - lastTime < 1000) { // Limitar a 1 mensaje por segundo
8
try {
9
session.close(StatusCode.POLICY_VIOLATION, "Demasiados mensajes");
10
} catch (IOException e) {
11
e.printStackTrace();
12
}
13
return;
14
}
15
lastMessageTime.put(session, now);
16
// Procesar el mensaje
17
}

Mejores Prácticas

  1. Manejo de Reconexiones: Implementa lógica de reconexión en el cliente para manejar desconexiones inesperadas.
  2. Heartbeats: Envía mensajes periódicos para mantener la conexión activa y detectar desconexiones.
  3. Validación de Mensajes: Siempre valida y sanitiza los mensajes entrantes para prevenir ataques.
  4. Escalabilidad: Para aplicaciones a gran escala, considera usar un broker de mensajes como RabbitMQ o Apache Kafka junto con WebSockets.
  5. Monitoreo: Implementa logging y monitoreo para rastrear el estado de las conexiones WebSocket.
  6. Manejo de Estado: Usa un mecanismo de manejo de estado (como una base de datos) para mantener la consistencia en caso de reinicio del servidor.