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:
1import static spark.Spark.*;2import org.eclipse.jetty.websocket.api.*;3import 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:
1public class WebSocketExample {2 public static void main(String[] args) {3 webSocket("/websocket", WebSocketHandler.class);4 init(); // Inicializa Spark5 }6
7 @WebSocket8 public static class WebSocketHandler {9
10 @OnWebSocketConnect11 public void onConnect(Session session) throws Exception {12 System.out.println("Conexión WebSocket abierta");13 }14
15 @OnWebSocketClose16 public void onClose(Session session, int statusCode, String reason) {17 System.out.println("Conexión WebSocket cerrada");18 }19
20 @OnWebSocketMessage21 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 @OnWebSocketError31 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@OnWebSocketMessage2public 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:
1public class WebSocketHandler {2 private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());3
4 @OnWebSocketConnect5 public void onConnect(Session session) {6 sessions.add(session);7 }8
9 @OnWebSocketClose10 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@OnWebSocketConnect2public 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@OnWebSocketError2public 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:
1private static final Map<Session, Long> lastMessageTime = new ConcurrentHashMap<>();2
3@OnWebSocketMessage4public 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 segundo8 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 mensaje17}
Mejores Prácticas
- Manejo de Reconexiones: Implementa lógica de reconexión en el cliente para manejar desconexiones inesperadas.
- Heartbeats: Envía mensajes periódicos para mantener la conexión activa y detectar desconexiones.
- Validación de Mensajes: Siempre valida y sanitiza los mensajes entrantes para prevenir ataques.
- Escalabilidad: Para aplicaciones a gran escala, considera usar un broker de mensajes como RabbitMQ o Apache Kafka junto con WebSockets.
- Monitoreo: Implementa logging y monitoreo para rastrear el estado de las conexiones WebSocket.
- 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.