Contexte et mesure de performance
Le blog Go montre qu’un programme appelant http.Get 100 fois sur index.golang.org passe de ~710 ms avec Go 1.26 à ~150 ms avec Go 1.27, soit près de cinq fois plus rapide. Le test utilise GODEBUG=http2client=0 pour forcer HTTP/1, ce qui expose le coût de l’établissement de chaque connexion.
Mécanisme de réutilisation des connexions HTTP/1
En HTTP/1.1 les connexions sont persistantes tant qu’aucun Connection: close n’est envoyé. La réutilisation dépend de la capacité du client à lire le corps de la réponse jusqu’à la fin, déterminée par Content‑Length ou Transfer‑Encoding. Si le corps reste partiellement lu, net/http considère la connexion comme « polluée » et la ferme, obligeant le client à créer une nouvelle connexion pour la requête suivante.
func getStatus(url string) (int, error) {
resp, err := http.Get(url)
if err != nil {
return 0, err
}
defer resp.Body.Close()
return resp.StatusCode, nil
}
Dans cet exemple, le corps n’est jamais consommé ; la fermeture du corps déclenche la perte de la connexion sous Go 1.26.
Modification introduite dans Go 1.27
Go 1.27 modifie la logique de Response.Body.Close. Au lieu d’abandonner la connexion lorsqu’un corps reste non lu, le runtime vide le flux restant (ou le marque comme consommé) et rend la connexion disponible pour le pool de réutilisation. Cette optimisation supprime le besoin d’un io.Copy(io.Discard, resp.Body) explicite. Le gain observé provient de la suppression des trois allers‑retours réseau (TCP + TLS + HTTP) qui, dans le scénario de test, représentent environ 300 ms de latence.
Implications et bonnes pratiques
Les programmes qui ne nécessitent que le code d’état HTTP doivent désormais s’attendre à une réutilisation efficace sans code additionnel. Toutefois, si le corps est partiellement lu (par exemple un décodage JSON qui s’arrête avant le caractère de fin de ligne), la même contrainte s’applique : le reste du flux doit être consommé ou ignoré pour que la connexion soit réutilisable. Les développeurs peuvent donc choisir entre io.ReadAll, io.Copy(io.Discard, …) ou laisser le nouveau comportement de Go 1.27 gérer le drainage automatiquement. Cette évolution ne touche pas HTTP/2, où la multiplexation évite déjà ce problème. En production, la réduction du temps de connexion se traduit par une charge CPU moindre (moins de handshakes TLS) et une latence globale plus prévisible, surtout dans les environnements à haute latence inter‑régionale où chaque RTT peut dépasser 50 ms.