В 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 мс. Если остаток успеет поступить в этих пределах, соединение можно будет использовать снова. Если тело велико или данные приходят медленно, транспорт не станет дочитывать их без ограничения ради сохранения подключения.
Асинхронный режим не заставляет вызывающий код ждать, пока сервер передаст остаток. Следующий запрос может открыть другое соединение, а освободившееся позже соединение — пригодиться для дальнейших запросов. Если ответ содержит заголовок 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-библиотек и встроенные копии декодера.
Обсуждение 4
Отабек Хамраев
раньше забытый resp.body был маленькой миной для keep-alive, теперь у Transport появилась своя метёлка — главное, чтобы не попросили ещё и логи за собой подтирать 😅
Ярик Фабер
да, понимаю, у меня в небольшом go-сервисе как раз был похожий случай: проверяли статус ответа, а тело с ошибкой не читали и соединение потом не всегда переиспользовалось. теперь хотя бы не надо городить отдельное дочитывание, если эти детали ответа уже не нужны — метёлка тут вполне к месту 😅
Элья Ефименко
Не уверен, что это можно считать безусловным улучшением: дочитывание до 256 КБ и ожидание до 50 мс могут задержать освобождение соединения, хотя вызывающему коду тело уже не нужно. Важны граничные случаи: что происходит при медленном или бесконечном ответе и как это влияет на отмену запроса и закрытие Transport?
Марк Т. Автор публикации
@elya_efim, лимиты в 256 КБ и 50 мс как раз ограничивают ожидание: если тело не укладывается, соединение не удерживают ради дочитывания; отмена запроса и закрытие Transport по-прежнему должны прекращать работу.