Go 1.27 cambia algo que llevaba años en las listas de buenas prácticas: ya no hace falta vaciar a mano el cuerpo de una respuesta HTTP/1 antes de cerrarlo para que la conexión se reutilice. El equipo de Go lo cuenta en su blog (14 de octubre de 2026) con un benchmark que pasa de ~710 ms a ~150 ms.
Qué cambia
En HTTP/1 las respuestas llegan una detrás de otra por la misma conexión, así que el cliente solo puede reutilizarla cuando el cuerpo actual se ha leído entero. Hasta Go 1.26, si el código llamaba a resp.Body.Close() sin haber leído todo, net/http descartaba la conexión y abría otra, pagando de nuevo TCP y TLS. El artículo estima unos 3 RTT con TLS 1.2 (1 de TCP y 2 de TLS); con TLS 1.3 el handshake puede bajar a 1 RTT. HTTP/2 no tiene este problema porque multiplexa streams.
En Go 1.27, net/http.Transport vacía de forma asíncrona el cuerpo HTTP/1 sin leer durante un máximo de 50 ms o 256 KB, y así puede devolver la conexión al pool. Si la respuesta lleva Connection: close no se intenta vaciar, porque sería trabajo perdido.
El benchmark del artículo hace 100 peticiones secuenciales forzando HTTP/1 con GODEBUG=http2client=0. Según los autores (Nicholas Husin y Damien Neil), tarda ~710 ms en Go 1.26 y ~150 ms en Go 1.27, casi 5 veces menos. Es una medición en una sola máquina y contra un solo servidor, sin varianza, así que conviene leerla como orden de magnitud y no como promesa.
Por qué importa en producción
Los clientes que más lo notan son los que hablan HTTP/1 con muchas peticiones seguidas: llamadas a APIs internas detrás de un proxy que no negocia HTTP/2, sondas, scrapers, clientes de servicios que dejan cuerpos sin leer en rutas de error o al hacer HEAD y GET condicionales. En esos casos cada conexión descartada es latencia extra y más sockets en TIME_WAIT; en el servidor de enfrente, más handshakes TLS.
El cambio también limpia código: los helpers del tipo io.Copy(io.Discard, resp.Body) con tope de tamaño y timeout, que muchos proyectos arrastran, dejan de ser necesarios. El artículo recomienda simplemente defer resp.Body.Close().
Dos matices que cuenta el propio texto: el límite de 256 KB existe porque con cuerpos grandes sin leer es más barato abrir una conexión nueva que drenar, y el comportamiento "puede evolucionar en futuras versiones". Tampoco documenta un GODEBUG para desactivarlo, así que si te preocupa un efecto lateral hay que probarlo, no apagarlo. Otro detalle: un json.Decoder que deja sin leer el salto de línea final también rompía la reutilización en casos pequeños.
Qué haría yo
- Medir antes de actualizar: en un servicio que haga muchas llamadas HTTP/1, anotar conexiones nuevas por segundo y latencia p99 del cliente (
httptraceo las métricas del proxy/LB). - Actualizar a Go 1.27 en un entorno de pruebas y repetir la medición con el mismo tráfico.
- Buscar los helpers de "drenar y cerrar" y comprobar si siguen aportando algo. Retirarlos solo si la medición no empeora.
- Revisar los puntos donde se cierra el cuerpo sin leerlo (rutas de error, redirecciones) y asegurarse de que siempre hay un
Close. - Vigilar CPU y memoria del proceso: el drenado asíncrono mueve trabajo de la ruta de la petición a segundo plano.
Fuentes
- Go blog, "HTTP/1 connection reuse" (Nicholas Husin y Damien Neil, 14 de octubre de 2026): https://go.dev/blog/http-connection-reuse