Introducción: El dilema del desarrollador
\n\nImagina: estás escribiendo un chat, un terminal bursátil o un juego en línea en Go. El cliente necesita recibir actualizaciones al instante. El viejo y confiable AJAX con sondeo periódico (polling) queda descartado: demasiadas solicitudes inútiles que agotan tanto el servidor como la batería del usuario. Quedan dos candidatos: Long Polling (sondeo largo) y WebSocket.
\n\nAmbos resuelven el problema del "tiempo real", pero lo hacen de maneras fundamentalmente diferentes. ¿Cuál elegir para tu aplicación Go? Vamos a analizarlo sin rodeos, con ejemplos de código reales y un análisis honesto de pros y contras. Este artículo es tu guía en el mundo de las comunicaciones en tiempo real con Go.
\n\n¿Qué es Long Polling en Go?
\n\nLong Polling es una evolución del polling normal. El cliente envía una solicitud HTTP al servidor, pero el servidor no responde inmediatamente si no hay datos. "Se cuelga" (mantiene la conexión abierta) y espera hasta que ocurra un nuevo evento. Tan pronto como ocurre el evento, el servidor envía la respuesta y el cliente crea inmediatamente una nueva solicitud de Long Polling. El ciclo se repite.
\n\nEn Go, esto se implementa de manera muy elegante gracias a las gorutinas y los canales. El servidor puede "poner a dormir" el manejador de la solicitud hasta que aparezcan los datos.
\n\nEjemplo de servidor Long Polling en Go
\n\npackage main\n\nimport (\n "fmt"\n "net/http"\n "time"\n)\n\n// Canal por el que llegan nuevos mensajes\nvar messages = make(chan string, 10)\n\nfunc longPollHandler(w http.ResponseWriter, r *http.Request) {\n select {\n case msg := <-messages:\n fmt.Fprintf(w, "Nuevo mensaje: %s", msg)\n case <-time.After(30 * time.Second):\n // Timeout — si no hay mensajes en 30 segundos\n http.Error(w, "Timeout", http.StatusGatewayTimeout)\n }\n}\n\nfunc main() {\n // Gorutina que simula la llegada de nuevos mensajes\n go func() {\n for {\n time.Sleep(5 * time.Second)\n messages <- fmt.Sprintf("Hora: %s", time.Now().String())\n }\n }()\n\n http.HandleFunc("/poll", longPollHandler)\n http.ListenAndServe(":8080", nil)\n}\n\n\nEl cliente en JavaScript se vería así: después de recibir la respuesta (o un timeout), llama inmediatamente a /poll de nuevo. Simple, confiable, funciona a través de cualquier proxy y firewall.
WebSocket en Go: Dúplex completo
\n\nWebSocket es un protocolo que, después del handshake (HTTP Upgrade), convierte la conexión en un canal bidireccional permanente. El servidor puede enviar datos al cliente en cualquier momento, y el cliente al servidor. Sin solicitudes repetidas, sin timeouts.
\n\nEn Go, la biblioteca estándar no tiene soporte integrado para WebSocket, pero existe el estándar de oro: el paquete gorilla/websocket. Es estable, bien documentado y utilizado en producción por miles de proyectos.
Ejemplo de servidor WebSocket en Go
\n\npackage main\n\nimport (\n "log"\n "net/http"\n "time"\n\n "github.com/gorilla/websocket"\n)\n\nvar upgrader = websocket.Upgrader{\n CheckOrigin: func(r *http.Request) bool {\n return true // Para desarrollo. ¡En producción, verificar!\n },\n}\n\nfunc wsHandler(w http.ResponseWriter, r *http.Request) {\n conn, err := upgrader.Upgrade(w, r, nil)\n if err != nil {\n log.Print("Error al actualizar:", err)\n return\n }\n defer conn.Close()\n\n // Bucle infinito de lectura y escritura\n for {\n // Leer mensaje del cliente\n _, message, err := conn.ReadMessage()\n if err != nil {\n log.Println("Error de lectura:", err)\n break\n }\n log.Printf("Recibido: %s", message)\n\n // Enviar respuesta (o notificación push)\n err = conn.WriteMessage(websocket.TextMessage, []byte("Servidor: "+time.Now().String()))\n if err != nil {\n log.Println("Error de escritura:", err)\n break\n }\n }\n}\n\nfunc main() {\n http.HandleFunc("/ws", wsHandler)\n log.Pr