93 lines
2.5 KiB
Go
93 lines
2.5 KiB
Go
package infra
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"net/http"
|
|
"time"
|
|
|
|
"nhooyr.io/websocket"
|
|
)
|
|
|
|
// WSHandler retorna un http.HandlerFunc que upgradea la conexion HTTP a WebSocket,
|
|
// crea un WSClient, lo registra en el hub y lanza dos goroutines:
|
|
// - readPump: lee mensajes del Conn y los publica al hub.Broadcast
|
|
// - writePump: consume del client.Send y escribe al Conn
|
|
//
|
|
// El cliente se desregistra del hub cuando alguna de las pumps termina (cliente
|
|
// desconectado, error de I/O, o canal cerrado). Asigna un ID hex aleatorio si
|
|
// no se sobreescribe externamente.
|
|
func WSHandler(hub *WSHub, origins []string) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
conn, err := WSUpgrader(w, r, origins)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
client := &WSClient{
|
|
Hub: hub,
|
|
Conn: conn,
|
|
Send: make(chan []byte, 64),
|
|
ID: randomID(),
|
|
}
|
|
hub.Register <- client
|
|
|
|
// writePump
|
|
go wsWritePump(client)
|
|
// readPump (bloqueante en el handler para mantener viva la request)
|
|
wsReadPump(client)
|
|
}
|
|
}
|
|
|
|
// wsReadPump lee mensajes del Conn y los publica al hub.Broadcast.
|
|
// Termina si Read retorna error (cliente desconectado o cerrado).
|
|
// Al terminar, desregistra el cliente y cierra la conexion.
|
|
func wsReadPump(client *WSClient) {
|
|
defer func() {
|
|
// Unregister no bloqueante: si el hub ya esta cerrado, no esperamos
|
|
select {
|
|
case client.Hub.Unregister <- client:
|
|
case <-client.Hub.done:
|
|
}
|
|
_ = client.Conn.Close(websocket.StatusNormalClosure, "")
|
|
}()
|
|
|
|
ctx := context.Background()
|
|
for {
|
|
_, data, err := client.Conn.Read(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
// Encolar al hub.Broadcast sin bloquear si esta lleno
|
|
select {
|
|
case client.Hub.Broadcast <- data:
|
|
default:
|
|
// Hub saturado: dropear mensaje del cliente para no bloquear el read
|
|
}
|
|
}
|
|
}
|
|
|
|
// wsWritePump consume del canal Send del cliente y escribe al Conn.
|
|
// Termina si el canal se cierra (hub desregistro al cliente) o si Write falla.
|
|
func wsWritePump(client *WSClient) {
|
|
for msg := range client.Send {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
err := client.Conn.Write(ctx, websocket.MessageText, msg)
|
|
cancel()
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// randomID genera un identificador hex aleatorio de 16 caracteres (8 bytes).
|
|
// No es criptograficamente perfecto para autenticacion — solo identificacion.
|
|
func randomID() string {
|
|
b := make([]byte, 8)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "anon"
|
|
}
|
|
return hex.EncodeToString(b)
|
|
}
|