У Go 1.27 покращать повторне використання HTTP/1-з’єднань
У Go 1.27 пакет net/http.Transport асинхронно дочитуватиме непрочитані тіла HTTP/1-відповідей — у межах 256 КБ і 50 мс. Це допоможе частіше повторно використовувати з’єднання без додаткового коду.
У Go 1.27 пакет net/http.Transport почне автоматично обробляти непрочитані тіла HTTP/1-відповідей, щоб частіше зберігати з’єднання для наступних запитів. Як пишуть Ніколас Хусін і Демієн Ніл у блозі Go від 14 жовтня 2026 року, зміна стосується випадків, коли програма закриває resp.Body, не дочитавши відповідь. Розробнику достатньо закрити resp.Body, якщо тіло більше не потрібне.
Чому закрити відповідь було недостатньо
У HTTP/1 відповіді в одному з’єднанні надходять послідовно в спільному потоці байтів. Пакет net/http розбирає рядок статусу й заголовки, перш ніж повернути керування програмі, але тіло залишається в потоці: його читає код, що викликав запит, через resp.Body. Поки тіло не прочитане до кінця, клієнт не може визначити, де почнеться наступна відповідь у тому самому з’єднанні.
Це легко не помітити, якщо програмі потрібен лише статус. Сервер може надіслати разом із ним подробиці в тілі відповіді. Код закриває resp.Body, але в Go 1.26 і попередніх версіях net/http відмовляється від з’єднання, якщо залишок не прочитаний. Для наступного запиту доводиться встановлювати нове.
Навіть розбір тіла не завжди розв’язує проблему. encoding/json.Decoder припиняє читання після закривної дужки JSON-об’єкта. Якщо після неї сервер надіслав символ нового рядка, одного непрочитаного байта достатньо, щоб з’єднання не вдалося використати повторно. Тому перевірка наявності виклику Close не показує, чи втрачає клієнт з’єднання.
Нове HTTPS-підключення потребує обміну повідомленнями через мережу: спочатку для TCP, потім для TLS. У наведеному авторами випадку з TLS 1.2 до моменту, коли можна надіслати HTTP-запит, минає три RTT — час обміну пакетами в обидва боки. Такі витрати відчутні, навіть коли сама відповідь невелика. Для HTTP/2 проблема влаштована інакше: запити використовують окремі потоки в межах одного з’єднання, і непрочитане тіло однієї відповіді не заважає надсилати інші запити.
Як Transport звільнятиме з’єднання
Починаючи з Go 1.27, net/http.Transport асинхронно дочитуватиме непрочитане тіло HTTP/1-відповіді. Робота обмежена 256 КБ і 50 мс. Якщо залишок надійде в цих межах, з’єднання можна буде використати знову. Якщо тіло велике або дані надходять повільно, Transport не дочитуватиме їх без обмежень заради збереження з’єднання.
Асинхронний режим не змушує код, що викликав запит, чекати, поки сервер передасть залишок. Наступний запит може відкрити інше з’єднання, а те, що звільниться пізніше, — знадобитися для подальших запитів. Якщо відповідь містить заголовок Connection: close, Transport не намагається дочитати тіло: зберегти таке з’єднання все одно не можна. Автори попереджають, що деталі цієї поведінки можуть змінюватися в майбутніх версіях.
Раніше те саме завдання розв’язували вручну: спрямовували решту тіла в io.Discard, а потім закривали resp.Body. Але без обмежень за обсягом і часом це не завжди розумно. Завантаження великої відповіді заради повторного використання з’єднання може тривати довше, ніж встановлення нового, а повільне передавання — затримати роботу програми. Додавання лімітів і фонової обробки ускладнювало код через ситуацію, яку розробник не завжди помічає одразу.
Що це означає на практиці
У прикладі авторів функція 100 разів запитує статус в index.golang.org, не читаючи тіла відповідей. За примусового використання HTTP/1 через GODEBUG=http2client=0 виконання тривало близько 710 мс із Go 1.26 і близько 150 мс із Go 1.27 на машині одного з авторів. Це майже п’ятикратна різниця в конкретному тесті, а не оцінка прискорення будь-якого застосунку.
Розробнику більше не потрібно додавати власне дочитування лише заради цього випадку: коли відповідь більше не потрібна, слід закрити resp.Body. Наскільки помітним буде ефект, залежить від того, як часто HTTP/1-клієнт залишає тіло непрочитаним, а потім робить нові запити. Якщо такої ситуації немає, результати прикладу до навантаження сервісу не застосовні.
Моя думка
Добре, що для цього більше не потрібні власні горутини й таймери. Правило для коду, що викликає запит, залишається простим: закривати тіло, коли воно більше не потрібне. Ліміти на фонове дочитування при цьому містяться в Transport.
Я б перевіряв ефект оновлення на запитах конкретного сервісу, а не переносив на них цифри з прикладу. Спочатку потрібно з’ясувати, чи залишається в його HTTP/1-клієнта непрочитана частина відповіді.
Джерела
Звідки новина. Текст — переказ своїми словами, факти — з джерела, думка — автора.
фулстек-розробник
Створюю вебсервіси й підтримую кілька проєктів на різних CMS. Віддаю перевагу простій архітектурі, для передавання якої не потрібна окрема інструкція.
Усі публікації автораСхожі статті
Налаштування Firefox допомогло автору нотатки відновлювати великі завантаження
Автор нотатки виявив, що зміна network.http.response.timeout допомогла йому відновлювати перервані завантаження великих файлів у Firefox. Це експериментальна порада, а відновлення залежить і від підтримки сервера.
Cloudflare відкрила Origin Range Requests для кешування частин великих файлів
Cloudflare представила Origin Range Requests для кешування частин великих файлів. Функція доступна на всіх тарифах, а після переходу R2 показник p99 часу до першого байта скоротився приблизно вдвічі.
У декодері Brotli виявили вразливість із ризиком віддаленого виконання коду
У C-декодері Brotli виявили вразливість, яка може призвести до віддаленого виконання коду. Розробникам варто перевірити версії Node.js, бібліотек Python і вбудовані копії декодера.
Обговорення 5
Левон Варданян
А якщо тіло більше за 256 КБ або читання не вкладається у 50 мс, Transport одразу закриє з’єднання чи ще спробує дочитати його у фоні? Цікаво, чи ці межі можна налаштувати для клієнтів із повільними відповідями.
Марк Т. Автор публікації
Після перевищення ліміту читання Transport закриє HTTP/1-з’єднання, а не дочитуватиме тіло далі; ці межі клієнт налаштувати не зможе.
Нікіта Яунбергс
@levon.vardanyan, за описом ці 256 КБ і 50 мс — межі фонового дочитування: якщо одну з них перевищено, Transport припиняє спробу й закриває з’єднання, а не читає тіло без обмежень; налаштувати ці ліміти через API не можна.
Іліє Негру
Ще варто прогнати через бенчмарк клієнт із частими відповідями, де потрібен лише статус: тепер ціна раннього Close — трохи фонового читання, тож цікаво, як це вплине на затримки й навантаження на диск/мережу.
Еміль Урсу
Тут є нюанс: «закрити resp.Body — і Transport сам подбає» звучить майже як безумовна гарантія повторного використання, хоча йдеться лише про HTTP/1 і обмежене фонове дочитування. Для коду, який читає тільки статус, я б усе одно не закладався на reuse: краще явно перевірити поведінку на потрібному сервері й версії Go, особливо якщо з’єднання критичні для навантаження.