Девять частей мы прокладывали дорогу: нашли адрес, открыли соединение, зашифровали его. Только теперь браузер наконец говорит то, ради чего всё затевалось: «дай мне страницу». Это HTTP — самый простой протокол во всей цепочке, текстовый и читаемый глазами. Посмотрим, как выглядит запрос и ответ, и чем три живые версии протокола отличаются друг от друга.

Это десятая часть цикла «Что происходит, когда открываешь сайт».

Запрос и ответ руками

HTTP/1.1 настолько прост, что его можно набрать вручную. openssl s_client откроет TLS-соединение, а дальше мы просто пишем текст:

$ printf 'GET /robots.txt HTTP/1.1\r\nHost: alekseychikun.ru\r\nConnection: close\r\n\r\n' \
  | openssl s_client -quiet -connect alekseychikun.ru:443 -servername alekseychikun.ru -alpn http/1.1

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Fri, 16 Oct 2026 09:18:21 GMT
Server: nginx
Vary: Accept-Encoding
X-Cache-Status: MISS
Connection: close
Transfer-Encoding: chunked

af
User-agent: *
Disallow: /wp-admin/
…
0

Всё сообщение — строки текста, разделённые \r\n. Пустая строка отделяет заголовки от тела. Разберём, что здесь важно.

Запрос

  • первая строка — метод, путь и версия: GET /robots.txt HTTP/1.1. Метод говорит, что сделать: GET — получить, POST — отправить данные, HEAD — только заголовки;
  • Host — имя сайта. На одном адресе живут десятки сайтов, и без этого заголовка сервер не знает, чей файл отдать. Это и есть главная причина, почему у одного IP может быть много сайтов;
  • Connection: close — «после ответа закрой». Без него соединение остаётся открытым для следующих запросов.

Ответ

  • код — 200 OK. Первая цифра — класс: 2xx — успех, 3xx — иди по другому адресу, 4xx — ошибка в запросе (404 — нет такого, 403 — нельзя), 5xx — сломалось на сервере;
  • Content-Type — что внутри и в какой кодировке. По нему браузер решает, показывать как страницу, текст или скачивать;
  • Vary: Accept-Encoding — подсказка кэшам: ответ зависит от того, умеет ли клиент сжатие. Кэш должен хранить сжатую и несжатую копии отдельно;
  • X-Cache-Status — наш собственный заголовок: показывает, взят ли ответ из кэша страниц (HIT) или собран заново (MISS);
  • Transfer-Encoding: chunked — сервер не знал заранее длину ответа и шлёт тело кусками. af — длина куска в шестнадцатеричном виде (175 байт), 0 — конец.

Сжатие: в пять раз меньше

Если клиент написал в запросе Accept-Encoding: gzip, br, сервер сожмёт ответ. Страница блога этого сайта:

$ curl -so /dev/null -w '%{size_download}\n' https://alekseychikun.ru/blog/
44912
$ curl -so /dev/null -H 'Accept-Encoding: gzip, br' -w '%{size_download}\n' https://alekseychikun.ru/blog/
8750

44,9 КБ без сжатия против 8,8 КБ со сжатием — в пять с лишним раз. Помните «начальную порцию» TCP из пятой части, около 14 КБ? Сжатая страница влезает в неё целиком и приходит за один круг. Несжатая — нет. Браузеры просят сжатие всегда, а вот самописные скрипты и интеграции часто забывают — и платят трафиком и временем.

HTTP/2: один провод, много разговоров

Наш curl из седьмой части договорился в ALPN о версии h2. Вот как выглядит тот же запрос в HTTP/2:

* [HTTP/2] [1] OPENED stream for https://alekseychikun.ru/robots.txt
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: alekseychikun.ru]
* [HTTP/2] [1] [:path: /robots.txt]

Смысл тот же, форма другая. HTTP/2 — бинарный: заголовки упакованы и сжаты, первая строка превратилась в псевдозаголовки :method, :path, а Host стал :authority. Главное новшество — потоки: [1] в выводе — номер потока. По одному соединению параллельно идут десятки запросов, и ответы приходят вперемешку, кусками.

В HTTP/1.1 запросы по соединению идут строго по очереди, поэтому браузеры открывали к сайту по шесть соединений и всё равно ждали. Со второй версией хватает одного — и одного рукопожатия TCP и TLS на всю страницу.

HTTP/3: долой TCP

У HTTP/2 осталась одна беда, и она в TCP. Если потерялся один пакет, TCP задерживает всё, что пришло после него, пока потерянное не перешлют, — даже данные других потоков, которые к потерянному не относятся. На мобильной сети с потерями это заметно.

HTTP/3 работает поверх QUIC — протокола на UDP, который сам следит за доставкой, но для каждого потока отдельно. Потеря в одном потоке не тормозит другие. Заодно QUIC объединяет рукопожатие соединения и TLS в одно: новое соединение стоит один круг вместо двух, а повторное — может не стоить ни одного.

HTTP/1.1HTTP/2HTTP/3
форматтекстбинарныйбинарный
под нимTCP + TLSTCP + TLSQUIC (UDP), TLS 1.3 встроен
параллельные запросыпо очереди, 6 соединенийпотоки в одном соединениипотоки, независимые при потерях
новое соединение2 круга (TCP + TLS 1.3)2 круга1 круг

Как браузер узнаёт, что сайт умеет HTTP/3: первый ответ по HTTP/2 содержит заголовок Alt-Svc: h3=":443", и следующие запросы браузер пробует по QUIC. Если UDP на пути режется, тихо остаётся на HTTP/2. Этот сайт пока отвечает без Alt-Svc, то есть работает на HTTP/2.

Шпаргалка по кодам, которые вы увидите чаще всего

КодЧто значитГде искать причину
301 / 308насовсем переехало по другому адресупоисковики переносят вес на новый адрес
302 / 307временно по другому адресучасто — переадресация на вход
304не изменилось, берите из своего кэшанорма, экономит трафик
403нельзяправа на файлы, правило защиты, бан
404нет такогоопечатка в адресе, удалённая страница
499клиент ушёл, не дождавшись (только в журналах nginx)медленный ответ или робот
502прокси не получил ответ от сервера за нимупал PHP или приложение
504прокси не дождался ответазависший запрос, нет таймаута

Сайт отвечает странными кодами или медленно отдаёт ресурсы?

Пришлите адрес — посмотрю заголовки, сжатие и версию протокола и скажу, что включить.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.