# w3se — Manual de usuario ## 1. ¿Qué es w3se? w3se es un bridge de conexiones persistentes que permite a un backend HTTP síncrono comunicarse con clientes que requieren conexiones SSE (Server-Sent Events) o WebSocket. Funciona como una capa de infraestructura entre dos mundos: los clientes que necesitan mantener una conexión abierta para recibir datos en tiempo real, y los backends que operan con el modelo tradicional de request-response. w3se no procesa ni transforma los mensajes que retransmite. Es un componente de transporte genérico que puede usarse para notificaciones en tiempo real, dashboards con datos en vivo, chat, monitoreo, streaming de resultados de procesamiento largo, o cualquier escenario que requiera push de datos desde el servidor hacia clientes conectados. ## 2. Instalación w3se es un binario único sin dependencias. Descárgalo y colócalo en cualquier directorio. Repositorio en Github: ### Linux ```bash chmod +x w3se ./w3se ``` ### Windows ``` w3se.exe ``` Para compilar desde el código fuente (requiere Go 1.22+): ```bash go build -o w3se . ``` ## 3. Inicio rápido ### 3.1 Crear la configuración del servidor Crea un archivo `w3se.config` en el mismo directorio del ejecutable: ```json { "server": { "host": "0.0.0.0", "port": 8080 }, "logging": { "level": "info", "format": "json", "output": "stdout" } } ``` ### 3.2 Crear un canal Crea un archivo `mi-servicio.channel` en el mismo directorio: ```json { "name": "mi-servicio", "paths": { "sse": "/mi-servicio/sse", "ws": "/mi-servicio/ws" }, "webhooks": { "url": "http://localhost:3000/w3se-events", "method": "POST", "timeout_ms": 5000 } } ``` ### 3.3 Iniciar w3se ```bash ./w3se ``` w3se cargará `w3se.config` y `mi-servicio.channel` automáticamente. ### 3.4 Probar con curl Abrir una conexión SSE en una terminal: ```bash curl -N http://localhost:8080/mi-servicio/sse ``` Tu backend recibirá un webhook `connection.opened` con el `connection_id`. Usa ese ID para enviar mensajes: ```bash curl -X POST http://localhost:8080/connections/EL_CONNECTION_ID/messages \ -d "Hola desde el backend" ``` La terminal con curl verá el mensaje llegar. ## 4. Configuración ### 4.1 Archivos w3se usa dos tipos de archivos, ambos en formato JSON, ubicados en el mismo directorio del ejecutable: | Archivo | Descripción | |---------|-------------| | `w3se.config` | Configuración del servidor, logging, y opcionalmente canales inline | | `*.channel` | Un canal por archivo. Se cargan todos automáticamente | ### 4.2 Precedencia de canales Los canales se pueden definir tanto en `w3se.config` (dentro del array `channels`) como en archivos `.channel` individuales. Si un archivo `.channel` define un canal con el mismo nombre que uno existente en `w3se.config`, el archivo `.channel` tiene precedencia. Esta separación permite que otros sistemas creen, modifiquen o eliminen canales escribiendo archivos `.channel` sin tocar la configuración principal. ### 4.3 Configuración completa del servidor ```json { "server": { "host": "0.0.0.0", "port": 8080, "tls": { "enabled": false, "cert": "/ruta/al/cert.pem", "key": "/ruta/a/la/key.pem" }, "trusted_proxies": ["127.0.0.1"], "read_timeout_ms": 5000, "write_timeout_ms": 10000, "max_connections": 10000, "max_message_size_bytes": 1048576, "shutdown_wait_ms": 500 }, "logging": { "level": "info", "format": "json", "output": "stdout" }, "channels": [] } ``` Todos los campos tienen valores por defecto. El archivo mínimo puede ser tan simple como `{}`. ### 4.4 Configuración completa de un canal ```json { "name": "notifications", "paths": { "sse": "/notifications/sse", "ws": "/notifications/ws" }, "auth": { "token_header": "X-Api-Key", "tokens": ["token-app-web", "token-app-mobile"], "rate_limit": { "requests_per_minute": 60, "per": "ip" } }, "webhooks": { "url": "http://127.0.0.1/notification-events", "method": "POST", "headers": { "X-Internal-Secret": "mi-secreto" }, "timeout_ms": 5000 }, "connections": { "idle_timeout_ms": 30000, "heartbeat_interval_ms": 15000 } } ``` Campos mínimos requeridos: `name`, `paths.sse`, `paths.ws`, `webhooks.url`. ## 5. Modos de despliegue ### 5.1 Directo con TLS w3se maneja TLS directamente. Útil para despliegues simples o entornos donde no hay reverse proxy. ```json { "server": { "host": "0.0.0.0", "port": 443, "tls": { "enabled": true, "cert": "/etc/ssl/certs/mi-dominio.pem", "key": "/etc/ssl/private/mi-dominio.key" } } } ``` ### 5.2 Detrás de nginx Nginx termina TLS y hace proxy a w3se. Configurar `trusted_proxies` para que el rate limiting use la IP real del cliente. **w3se.config:** ```json { "server": { "host": "127.0.0.1", "port": 8080, "trusted_proxies": ["127.0.0.1"] } } ``` **nginx.conf:** ```nginx location /notifications/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 86400; proxy_send_timeout 86400; } location /connections/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /health { proxy_pass http://127.0.0.1:8080; } ``` Es importante configurar `proxy_read_timeout` con un valor alto para que nginx no cierre las conexiones SSE/WebSocket por inactividad. ## 6. Autenticación ### 6.1 Filtrado rápido en w3se Configura tokens estáticos en el canal para descartar conexiones no autorizadas antes de invocar el webhook. Esto protege al backend de carga innecesaria. ```json { "auth": { "token_header": "X-Api-Key", "tokens": ["mi-token-secreto"] } } ``` Si `tokens` está vacío o no se define `auth`, w3se no realiza validación propia y pasa todas las conexiones al webhook. ### 6.2 Rate limiting ```json { "auth": { "rate_limit": { "requests_per_minute": 60, "per": "ip" } } } ``` Las conexiones que excedan el límite reciben HTTP 429 sin invocar webhook. ### 6.3 Autenticación en el backend El webhook `connection.opened` incluye todos los headers originales del request. El backend decide si acepta o rechaza la conexión respondiendo al webhook: - **HTTP 200-299**: conexión aceptada - **Cualquier otro código**: conexión rechazada, w3se cierra la conexión inmediatamente ## 7. Implementar un backend Tu backend necesita dos cosas: ### 7.1 Recibir webhooks Un endpoint HTTP que reciba los webhooks de w3se. Los tres eventos posibles son: **connection.opened** — Una nueva conexión se abrió. Responder 200 para aceptar. ```json { "event": "connection.opened", "channel": "notifications", "connection_id": "550e8400-e29b-41d4-a716-446655440000", "type": "sse", "remote_ip": "203.0.113.45", "path": "/notifications/sse", "headers": { ... }, "query_params": { ... }, "timestamp": "2026-04-10T18:30:00.000Z" } ``` **connection.message** — Un mensaje llegó del cliente. En SSE se produce una vez (el body del POST inicial). En WebSocket se produce por cada mensaje que envía el cliente. ```json { "event": "connection.message", "channel": "notifications", "connection_id": "550e8400-e29b-41d4-a716-446655440000", "message": { ... }, "timestamp": "2026-04-10T18:30:05.000Z" } ``` **connection.closed** — La conexión se cerró. ```json { "event": "connection.closed", "channel": "notifications", "connection_id": "550e8400-e29b-41d4-a716-446655440000", "type": "sse", "remote_ip": "203.0.113.45", "reason": "client_disconnect", "duration_seconds": 45, "messages_sent": 12, "timestamp": "2026-04-10T18:30:45.000Z" } ``` ### 7.2 Enviar mensajes Para enviar contenido al cliente, haz un POST al endpoint de mensajes de w3se: ``` POST /connections/{connection_id}/messages ``` El body es opaco — w3se lo retransmite sin modificación. El formato del contenido lo define quien envía; w3se solo lo transporta. ### 7.3 Cerrar una conexión ``` DELETE /connections/{connection_id} ``` ## 8. Ejemplo: notificaciones en tiempo real Un dashboard web se conecta por SSE para recibir alertas del sistema de monitoreo. ### 8.1 Canal ```json { "name": "alerts", "paths": { "sse": "/alerts/stream", "ws": "/alerts/ws" }, "auth": { "token_header": "Authorization", "tokens": ["Bearer dashboard-token-2026"] }, "webhooks": { "url": "http://127.0.0.1:4000/w3se-events", "method": "POST", "timeout_ms": 3000 }, "connections": { "idle_timeout_ms": 300000, "heartbeat_interval_ms": 30000 } } ``` ### 8.2 Flujo 1. El dashboard abre `GET /alerts/stream` con el header `Authorization: Bearer dashboard-token-2026`. 2. w3se valida el token, genera un `connection_id` y envía webhook `connection.opened` al backend. 3. El backend registra la conexión y responde 200. 4. Cuando el sistema de monitoreo detecta una alerta, el backend envía `POST /connections/{id}/messages` con los datos de la alerta. 5. El dashboard recibe la alerta en tiempo real. 6. El heartbeat mantiene la conexión viva cada 30 segundos. 7. Si el usuario cierra el dashboard, w3se envía webhook `connection.closed`. ## 9. Ejemplo: procesamiento largo con progreso Un usuario sube un archivo para procesamiento. El backend reporta progreso vía SSE. ### 9.1 Canal ```json { "name": "jobs", "paths": { "sse": "/jobs/progress", "ws": "/jobs/ws" }, "webhooks": { "url": "http://127.0.0.1:4000/w3se-events", "method": "POST", "timeout_ms": 5000 }, "connections": { "idle_timeout_ms": 600000, "heartbeat_interval_ms": 15000 } } ``` ### 9.2 Flujo 1. El frontend abre `POST /jobs/progress` enviando en el body el ID del job: `{"job_id": "abc123"}`. 2. w3se envía webhook `connection.opened`, luego `connection.message` con el body. 3. El backend asocia el `connection_id` con el `job_id`. 4. Conforme el procesamiento avanza, el backend envía actualizaciones: ```bash # 25% completado curl -X POST http://localhost:8080/connections/$ID/messages \ -d '{"progress": 25, "status": "processing"}' # 75% completado curl -X POST http://localhost:8080/connections/$ID/messages \ -d '{"progress": 75, "status": "processing"}' # Completado curl -X POST http://localhost:8080/connections/$ID/messages \ -d '{"progress": 100, "status": "done", "result_url": "/files/abc123.pdf"}' ``` 5. El frontend muestra la barra de progreso en tiempo real. ## 10. Ejemplo: chat bidireccional con WebSocket Dos usuarios se comunican a través de WebSocket. ### 10.1 Canal ```json { "name": "chat", "paths": { "sse": "/chat/sse", "ws": "/chat/ws" }, "auth": { "token_header": "Authorization", "tokens": [], "rate_limit": { "requests_per_minute": 120, "per": "ip" } }, "webhooks": { "url": "http://127.0.0.1:4000/w3se-events", "method": "POST", "timeout_ms": 3000 }, "connections": { "idle_timeout_ms": 300000, "heartbeat_interval_ms": 30000 } } ``` ### 10.2 Flujo 1. El usuario A abre WebSocket a `/chat/ws`. El backend recibe `connection.opened` y registra la conexión. 2. El usuario B abre otro WebSocket. El backend ahora tiene dos `connection_id`. 3. El usuario A envía un mensaje por el WebSocket. w3se lo recibe y envía webhook `connection.message` con el contenido. 4. El backend decide a quién reenviarlo y hace `POST /connections/{id_de_B}/messages` con el mensaje. 5. El usuario B lo recibe en su WebSocket. ## 11. Monitoreo ### Health check ```bash curl http://localhost:8080/health ``` Respuesta: ```json { "status": "ok", "connections": { "active": 47, "sse": 32, "ws": 15 }, "uptime_seconds": 86400 } ``` ### Logs Con formato JSON (default), cada línea es un objeto JSON que puede procesarse con herramientas como `jq`: ```bash ./w3se 2>&1 | jq '.level, .msg' ``` Niveles disponibles: - `debug` — detalle completo incluyendo payloads - `info` — conexiones abiertas/cerradas, mensajes enviados - `error` — fallos de webhook, errores de red ## 12. Resolución de problemas ### El cliente se conecta pero no recibe mensajes - Verificar que el `connection_id` en `POST /connections/{id}/messages` coincide con el de la conexión activa. - Verificar en los logs que el webhook `connection.opened` fue aceptado (HTTP 200). - Si está detrás de nginx, verificar que `proxy_read_timeout` es suficientemente alto. ### El webhook no llega al backend - Verificar que la URL del webhook en el canal es accesible desde w3se. - Verificar `webhooks.timeout_ms` — si el backend tarda más que el timeout, w3se descarta la conexión. - Revisar los logs con nivel `debug` para ver los detalles del request. ### Conexiones se cierran inesperadamente - Revisar `idle_timeout_ms` — si no hay actividad (mensajes enviados), la conexión se cierra automáticamente. - Si está detrás de un proxy, verificar que el proxy no tiene un timeout más corto que el de w3se. ### Rate limiting bloquea conexiones legítimas - Aumentar `requests_per_minute` en la configuración del canal. - Verificar que `trusted_proxies` está configurado correctamente para que el rate limiting use la IP real del cliente y no la del proxy.