# 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: <https://github.com/Induxsoft/w3se>

### 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.
