Перейти к содержимому
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after a top-up of $10 or more. Until Nov 30.

How it works

Как работает HTTP: запросы, ответы, заголовки и коды состояния

Разбираем настоящий обмен HTTP построчно: методы, URL, заголовки, классы кодов состояния и cookie, с проверкой через curl на локальном сервере.

Начальный10 мин чтенияАвтор: NotHoly

Что такое HTTP

HTTP — это протокол, которым браузеры, приложения и API обмениваются сообщениями. RFC 9110 определяет смысл сообщений: методы, коды состояния, поля заголовков и так далее. RFC 9112 определяет, как сообщения HTTP/1.1 выглядят в виде обычного текста в соединении TCP. Обзор MDN короче и написан с точки зрения браузера, его удобно читать вместе с RFC.

Схема простая. Клиент отправляет запрос, сервер возвращает один ответ. Обмен всегда начинает клиент. Обычный HTTP ничего не шифрует и не помнит, кто вы, между запросами. Шифрование добавляет TLS, о котором рассказывает следующая статья. Память между запросами дают cookie и токены, до них мы дойдём ниже.

Почему это важно

Большая часть поведения веба, которое вам предстоит изучать, проходит через HTTP. Форма отправляет значение в теле, API доверяет заголовку, редирект уводит пользователя в другое место, кэш хранит не ту копию страницы. Когда позже вы будете изучать трафик через прокси вроде OWASP ZAP, он покажет эти же сообщения. Если вы не умеете читать сырой запрос, вывод прокси мало чем поможет.

Форма сообщения

Запрос HTTP/1.1 — это обычный текст такого вида:

http
GET / HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: curl/8.12.1
Accept: */*

Первая строка называется строкой запроса: метод, цель запроса и версия протокола. Каждая следующая строка содержит поле заголовка, записанное как name: value. Пустая строка завершает секцию заголовков. Если у сообщения есть тело, оно идёт после этой пустой строки. При передаче каждая строка заканчивается на CRLF (возврат каретки и перевод строки), и редакторы это от вас скрывают.

Ответ устроен так же, но наверху стоит строка состояния: версия, код состояния и текстовое пояснение.

http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 15

<h1>hello</h1>

Методы

Метод говорит, что клиент хочет сделать. RFC 9110 определяет GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS и TRACE. PATCH описан в RFC 5789.

При чтении трафика важны два свойства:

  • Безопасные методы (GET, HEAD, OPTIONS, TRACE) задуманы как доступные только для чтения с точки зрения клиента. RFC 9110, раздел 9.2.1.
  • Идемпотентные методы (GET, HEAD, PUT, DELETE, OPTIONS, TRACE) можно повторять с тем же предполагаемым эффектом, что и у одного запроса. RFC 9110, раздел 9.2.2. POST не безопасен и не идемпотентен.

Эти свойства описывают обещанный смысл метода. Что произойдёт на самом деле, всё равно решает сервер. Обработчик GET, удаляющий запись, нарушает контракт, и ревьюер безопасности будет искать именно такую ошибку.

URL и цель запроса

Возьмите URL http://127.0.0.1:8000/missing?lang=en#top и разделите его на части:

  • Схема: http, сообщает клиенту, какой протокол использовать.
  • Authority: 127.0.0.1:8000, хост и необязательный порт.
  • Путь: /missing.
  • Параметры запроса: lang=en, после ?.
  • Фрагмент: top, после #.

Схему и фрагмент клиент на сервер не отправляет. Строка запроса HTTP содержит путь и параметры. Authority попадает в заголовок Host. HTTP/1.1 требует Host, потому что на одном IP-адресе может находиться много сайтов, и по этому заголовку сервер решает, какой сайт должен ответить.

Заголовки

Имена заголовков не зависят от регистра, поэтому Content-Type и content-type означают одно и то же поле (RFC 9110, раздел 5.1). Со значениями иначе: многие из них чувствительны к регистру, так что обратного не предполагайте.

Заголовки запроса, которые встречаются чаще всего:

  • Host: обязателен в каждом запросе HTTP/1.1. RFC 9112, раздел 3.2, требует, чтобы сервер отвечал 400, если заголовка нет.
  • User-Agent: клиентское ПО.
  • Accept: медиатипы, которые может обработать клиент.
  • Cookie: cookie, которые браузер отправляет обратно на сервер.
  • Authorization: учётные данные, часто bearer-токен.
  • Content-Type и Content-Length: описывают тело, если оно есть.

Заголовки ответа, которые встречаются чаще всего:

  • Content-Type: медиатип тела, например text/html.
  • Content-Length: размер тела в байтах.
  • Date: часы сервера, в GMT.
  • Server: имя программы. Многие серверы его отдают. В продакшене его часто скрывают.
  • Last-Modified: когда сервер в последний раз менял ресурс.
  • Set-Cookie: просит браузер сохранить cookie.

Заголовки — это данные, которые контролирует отправитель. Браузер отправляет то, что заложено в его коде, а скрипт или прокси может отправить что угодно. Классический пример — X-Forwarded-For: его добавляет прокси, но отправить его может и клиент. Никогда не доверяйте заголовку только потому, что он выглядит официально.

Коды состояния

Код состояния состоит из трёх цифр. Первая цифра задаёт класс, как определено в RFC 9110, раздел 15:

Класс Значение Примеры
1xx Информационный. Запрос получен, обработка продолжается 100 Continue
2xx Успех 200 OK, 201 Created, 204 No Content
3xx Перенаправление. Клиент должен сделать ещё один шаг 301 Moved Permanently, 304 Not Modified
4xx Ошибка клиента. С запросом что-то не так 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 405 Method Not Allowed
5xx Ошибка сервера. Сервер не смог обработать корректный запрос 500, 501 Not Implemented, 502, 503

Даже если клиент не знает конкретный код, он может действовать по классу. 4xx означает, что запрос надо изменить. 5xx означает, что тот же запрос может сработать позже.

Два кода часто путают. 401 означает, что в запросе нет действующих учётных данных. 403 означает, что сервер понял запрос и отказывает в нём, даже при наличии учётных данных.

Текстовое пояснение, например Not Found, сервер выбирает сам. RFC 9112, раздел 4, говорит, что клиентам следует его игнорировать. Пишите логику по числу и никогда по словам.

Cookie не входят в RFC 9110. Они определены в RFC 6265. Цикл выглядит так:

  1. Сервер отправляет Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Lax.
  2. Браузер сохраняет значение вместе с атрибутами.
  3. В последующих запросах к подходящему сайту браузер отправляет Cookie: session=abc123.

Сессионная cookie — это идентификатор, который выдаёт сервер. Паролем он не считается и личной тайной пользователя тоже. Тот, у кого есть это значение, обычно может действовать от имени сессии, пока она не истечёт или не будет отозвана. HttpOnly не даёт скриптам страницы прочитать значение. Secure разрешает отправку только через HTTPS. SameSite ограничивает случаи, когда браузер отправляет cookie в межсайтовых запросах. К этим атрибутам мы вернёмся в статьях о веб-безопасности.

Лабораторная работа

Нужны curl и Python 3. В Windows 11 с Git Bash есть curl, в большинстве систем Linux и macOS тоже. Сервером будет встроенный в Python http.server. Запускайте его только на своей машине с привязкой к 127.0.0.1 и никогда не открывайте в сеть.

Вывод ниже получен при запуске этих команд на Windows 11 с Git Bash, curl 8.12.1 и Python 3.12.10. На этой машине python3 не было в пути Git Bash, поэтому сервер запускался по полному пути к исполняемому файлу Python 3.12. Используйте python3, py -3 или python, смотря что работает в вашей системе. Даты, время и строки версий у вас будут другими. Индикатор прогресса curl из вывода ниже удалён, а длинные HTML-тела сокращены.

Шаг 1: создайте сайт и запустите сервер

В первом терминале:

bash
mkdir httplab && cd httplab && echo '<h1>hello</h1>' > index.html
python3 -m http.server 8000 --bind 127.0.0.1

Оставьте этот терминал открытым. Сервер пишет по одной строке лога на запрос, так что каждый запрос, отправленный ниже, появится здесь.

Шаг 2: прочитайте полный обмен

Во втором терминале:

bash
curl -v http://127.0.0.1:8000/

Вывод:

text
*   Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
> 
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:16:48 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
< 
<h1>hello</h1>
* shutting down connection #0

Читайте его в таком порядке:

  • Строки, начинающиеся с *, содержат заметки curl о соединении.
  • Строки, начинающиеся с >, показывают, что отправил curl. Curl сам добавил Host, User-Agent и Accept.
  • Строки, начинающиеся с <, показывают, что вернул сервер.
  • Пустая строка > завершает заголовки запроса. Пустая строка < завершает заголовки ответа.
  • <h1>hello</h1> — это тело. Content-Length: 15 соответствует файлу, потому что echo добавил после тега перевод строки.
  • HTTP 1.0, assume close after body показывает несовпадение. Curl запросил HTTP/1.1, а сервер ответил по HTTP/1.0. Простой сервер Python говорит на HTTP/1.0, поэтому curl закрывает соединение, как только прочитает тело.

Шаг 3: запросите несуществующий файл

bash
curl -i http://127.0.0.1:8000/missing

Флаг -i печатает заголовки ответа вместе с телом. Вывод с сокращённым HTML-телом:

text
HTTP/1.0 404 File not found
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 335

<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 404</p>
        <p>Message: File not found.</p>
        <p>Error code explanation: 404 - Nothing matches the given URI.</p>
    </body>
</html>

Посмотрите на первый терминал. В логе появилось:

text
127.0.0.1 - - [08/Nov/2026 05:16:49] code 404, message File not found
127.0.0.1 - - [08/Nov/2026 05:16:49] "GET /missing HTTP/1.1" 404 -

Код состояния 404 относится к классу ошибок клиента: путь не соответствует никакому ресурсу. Пояснение File not found сформулировал сам сервер. Тело представляет собой HTML-страницу, которую сервер сгенерировал для ошибки. Любой статус может нести любое тело, поэтому читайте и то, и другое.

В строке лога стоит 05:16, а в заголовке Date стоит 08:16. Лог использует местное время, заголовок использует GMT, поэтому они различаются на величину местного смещения.

Шаг 4: отправьте метод, который сервер не реализует

bash
curl -i -X POST http://127.0.0.1:8000/

Вывод с сокращённым HTML-телом:

text
HTTP/1.0 501 Unsupported method ('POST')
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:49 GMT
Connection: close
Content-Type: text/html;charset=utf-8
Content-Length: 357

<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 501</p>
        <p>Message: Unsupported method ('POST').</p>
        <p>Error code explanation: 501 - Server does not support this operation.</p>
    </body>
</html>

В строке лога написано code 501, message Unsupported method ('POST'). Флаг -X меняет только метод. Curl отправил те же заголовки, что и раньше.

В простом сервере вообще нет обработчика POST, так что 501 Not Implemented здесь честный ответ. RFC 9110, раздел 15.6.2, определяет 501 для сервера, который не поддерживает функцию, нужную для запроса. Настоящее приложение, которое знает ресурс, но отказывает в POST на нём, обычно ответило бы 405 Method Not Allowed. Ответ 405 должен включать заголовок Allow со списком методов, которые принимает ресурс.

Шаг 5: запросите только заголовки через HEAD

bash
curl -I http://127.0.0.1:8000/index.html

Вывод:

text
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.10
Date: Sun, 08 Nov 2026 08:16:50 GMT
Content-type: text/html
Content-Length: 15
Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT

Флаг -I отправляет запрос HEAD. Статус и заголовки совпадают с ответом на GET, но тела нет. Content-Length всё ещё показывает 15, потому что описывает представление, которое вернул бы GET. Это поведение определено в RFC 9110, раздел 9.3.2. В логе стоит "HEAD /index.html HTTP/1.1" 200 -.

HEAD пригодится, когда надо проверить, существует ли файл, какого он размера или когда менялся в последний раз, и при этом не скачивать его.

bash
curl -v -H 'Cookie: session=demo123' http://127.0.0.1:8000/

Нужные строки вывода:

text
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/8.12.1
> Accept: */*
> Cookie: session=demo123
> 
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Date: Sun, 08 Nov 2026 08:18:28 GMT
< Content-type: text/html
< Content-Length: 15
< Last-Modified: Sun, 08 Nov 2026 08:13:48 GMT
< 

Строка тела здесь опущена. Сервер ответил теми же 200, что и раньше, потому что логики для cookie в нём нет и заголовок он игнорирует. Ради этого упражнение и задумано. Браузер отправляет cookie, но что они значат, решает сервер. Сервер, который никогда не устанавливает cookie, не отреагирует на присланную вами.

Шаг 7: остановите сервер

Когда закончите, нажмите Ctrl+C в первом терминале.

Частые ошибки

  • Считать 200 успехом. В ответе 200 может прийти страница с ошибкой. Проверяйте тело и то, что сделало приложение.
  • Считать GET безвредным. Безопасность метода описывает намерение клиента. Если GET меняет данные, сервер неисправен, и атакующие это попробуют.
  • Писать логику по текстовому пояснению. Клиентам следует использовать число. Серверы могут менять слова как угодно.
  • Доверять заголовкам клиента. Host, User-Agent, Cookie и X-Forwarded-For приходят со стороны клиента. Проверяйте их на сервере.
  • Считать HTTP приватным. Обычный HTTP может прочитать любой на пути между вами и сервером. Поэтому настоящие сайты используют HTTPS. В лабораторной работе это допустимо только потому, что трафик не покидает 127.0.0.1.
  • Ожидать, что -X изменит заголовки. Он меняет только метод. Заголовки добавляются через -H.

Что дальше

  • Прочитайте RFC 9110, раздел 9 (методы) и раздел 15 (коды состояния). Документ длинный, просматривайте нужные разделы.
  • Прочитайте RFC 9112 о фрейминге сообщений: как клиент и сервер договариваются о том, где заканчивается тело, включая Content-Length и Transfer-Encoding.
  • Прочитайте обзор HTTP на MDN, чтобы увидеть те же понятия с точки зрения браузера.
  • Дальше статья что такое HTTPS и TLS показывает тот же обмен с добавленным шифрованием. Если вы её ещё не читали, статья что такое порт объясняет часть :8000 в URL.

Прокси вроде ZAP показывает эти же сообщения, поэтому статья служит основой для следующих материалов о веб-уязвимостях. API NotHoly на api.notholy.com/v1 тоже HTTP API с телами в JSON, работающий через TLS, так что описанные заголовки и коды состояния применимы и при обращении к нему.

Источники

  1. RFC 9110: HTTP Semantics
  2. RFC 9112: HTTP/1.1
  3. MDN Web Docs: An overview of HTTP
  4. RFC 6265: HTTP State Management Mechanism (cookies)

Ещё по теме: Сети и веб

Сети и веб

TCP/IP и порты: как сервисы слушают на вашей машине

Порт — это пронумерованная дверь на вашей машине. Разбираем, как TCP и UDP работают с портами, затем запускаем, находим и проверяем локальный сервер на 127.0.0.1.

Начальный10 мин чтения

Сети и веб

Что такое TLS и как HTTPS защищает трафик

TLS 1.3 даёт HTTPS конфиденциальность, целостность и аутентификацию сервера. Разбираем рукопожатие, цепочки сертификатов и то, чего замок в адресной строке не доказывает.

Начальный10 мин чтения