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

Лекция 3. Протокол HTTP в деталях

Введение

В прошлой лекции мы рассмотрели сетевые основы: модели OSI и TCP/IP, транспортные протоколы TCP и UDP, общий обзор прикладных протоколов. Теперь мы спускаемся на один уровень глубже и разбираем главный протокол современного веба — HTTP.

HTTP — это «язык», на котором браузер разговаривает с сервером, мобильное приложение — с REST API, а один микросервис — с другим. Любой backend-разработчик обязан понимать, как устроено HTTP-сообщение, что означают методы и коды состояния, какие заголовки за что отвечают и что вообще происходит между нажатием кнопки и появлением ответа на экране.

В этой лекции мы:

  • разберём, что такое HTTP и его ключевые свойства;
  • посмотрим на структуру запроса и ответа «изнутри», на уровне сырого текста;
  • изучим методы, коды состояния и важнейшие заголовки;
  • проследим полный жизненный цикл запроса от DNS до рендеринга;
  • кратко познакомимся с версиями HTTP/1.1, HTTP/2 и HTTP/3.

1. Что такое HTTP

HTTP (HyperText Transfer Protocol) — протокол прикладного уровня для передачи данных в сети Интернет. Изначально он создавался Тимом Бернерсом-Ли в 1989 году для передачи гипертекстовых документов (HTML-страниц), но сегодня по HTTP передаётся практически всё: JSON-данные API, изображения, видео, файлы, потоки событий.

HTTP работает по модели «запрос — ответ» (Request–Response):

  • клиент (браузер, мобильное приложение, другой сервис) формирует и отправляет запрос;
  • сервер принимает запрос, обрабатывает его и возвращает ответ.

Между ними могут находиться промежуточные узлы (proxy) — кэширующие, балансирующие нагрузку или фильтрующие серверы.

Важно: сам HTTP описывает только формат и семантику сообщений. Их доставку обеспечивает транспортный уровень — как правило, надёжный TCP (для HTTP/3 — QUIC поверх UDP).

1.1. Ключевые свойства HTTP

Текстовый (в HTTP/1.x). Сообщение представляет собой обычный человекочитаемый текст в кодировке ASCII/UTF-8. Это позволяет легко отлаживать обмен «глазами» через curl, telnet или вкладку Network в браузере. (В HTTP/2 и HTTP/3 формат стал бинарным ради эффективности, но логическая модель сообщения осталась прежней.)

Без состояния (stateless). Сервер не обязан помнить ничего о предыдущих запросах клиента. Каждый запрос самодостаточен и обрабатывается независимо от других. Это упрощает масштабирование (любой сервер из кластера может обработать любой запрос), но создаёт задачу: как «узнавать» пользователя между запросами? Решается это поверх HTTP — с помощью cookie, сессий и токенов (например, JWT в заголовке Authorization).

Расширяемый. Набор заголовков не фиксирован: клиент и сервер могут вводить собственные заголовки, не ломая совместимость.

💡 «Без состояния» — это про протокол, а не про приложение. Приложение вполне может быть «с состоянием» (помнить корзину пользователя), но это состояние хранится в базе данных или хранилище сессий, а в HTTP-запросе лишь передаётся идентификатор (cookie/токен).

2. Структура HTTP-запроса

Любой HTTP-запрос состоит из трёх частей:

  1. Стартовая строка (Request Line)
  2. Заголовки (Headers)
  3. Тело (Body) — необязательное

Схематично:

МЕТОД URI ВЕРСИЯ ← стартовая строка
Заголовок1: значение ← заголовки
Заголовок2: значение
← пустая строка-разделитель
тело запроса ← тело (опционально)

2.1. Стартовая строка (Request Line)

GET /api/products HTTP/1.1

Стартовая строка содержит три элемента, разделённых пробелами:

  • HTTP-метод — что нужно сделать с ресурсом (GET, POST, PUT, DELETE, …);
  • URI (Uniform Resource Identifier) — путь к ресурсу (/api/products), часто с параметрами строки запроса (?category=books&page=2);
  • версия протоколаHTTP/1.1, HTTP/2 и т. д.

2.2. Заголовки запроса (Request Headers)

Заголовки — это пары Имя: Значение, передающие метаданные о запросе. Каждый заголовок — на отдельной строке.

Обязательные / базовые:

  • Host — доменное имя сервера (обязателен в HTTP/1.1, позволяет одному IP обслуживать несколько сайтов — виртуальные хосты);
  • User-Agent — информация о клиенте (тип браузера, приложение).

О содержимом (если есть тело):

  • Content-Type — формат данных тела (application/json, text/html, multipart/form-data);
  • Content-Length — размер тела в байтах;
  • Content-Encoding — способ сжатия (gzip, br).

О том, что клиент готов принять:

  • Accept — желаемый формат ответа (application/json);
  • Accept-Encoding — поддерживаемые методы сжатия;
  • Accept-Language — предпочитаемый язык.

Аутентификация и сессии:

  • Authorization — учётные данные (Bearer <токен>, Basic <base64>);
  • Cookie — данные сессии, сохранённые ранее.

Кэширование (условные запросы):

  • If-Modified-Since — «отдай, только если изменилось после этой даты»;
  • If-None-Match — то же, но по ETag.

2.3. Тело запроса (Request Body)

Тело отделяется от заголовков одной пустой строкой и содержит передаваемые серверу данные.

  • GET, HEAD, DELETE — обычно без тела;
  • POST, PUT, PATCH — содержат данные (чаще всего JSON).

3. Структура HTTP-ответа

Ответ устроен симметрично запросу и тоже состоит из трёх частей:

  1. Статус-строка (Status Line)
  2. Заголовки (Headers)
  3. Тело (Body)

3.1. Статус-строка (Status Line)

HTTP/1.1 200 OK

Содержит:

  • версию протоколаHTTP/1.1;
  • код состояния — трёхзначное число (200, 404, 500);
  • фразу состояния — текстовое пояснение (OK, Not Found, Internal Server Error).

3.2. Заголовки ответа (Response Headers)

О содержимом:

  • Content-Type — MIME-тип тела (application/json; charset=utf-8);
  • Content-Length — размер тела;
  • Content-Encoding — способ сжатия;
  • Content-Disposition — как обработать содержимое (например, attachment; filename="report.pdf" — скачать как файл).

Управление кэшем:

  • Cache-Control — директивы кэширования (max-age, no-store, private);
  • ETag — «отпечаток» версии ресурса для валидации кэша;
  • Last-Modified — дата последнего изменения;
  • Expires — момент устаревания кэша.

Безопасность:

  • Strict-Transport-Security — требовать HTTPS;
  • Content-Security-Policy — ограничение источников ресурсов;
  • X-Frame-Options — защита от clickjacking.

Прочее:

  • Location — адрес нового/перемещённого ресурса (для 201 Created и редиректов 3xx);
  • Set-Cookie — установка cookie в браузере.

3.3. Тело ответа (Response Body)

Тело содержит сами данные: HTML-страницу, JSON/XML, изображение, файл, бинарные данные. Для некоторых ответов (например, 204 No Content, 304 Not Modified) тело отсутствует.

4. Методы HTTP

Метод указывает, какое действие требуется выполнить над ресурсом. Для понимания REST API важны два свойства методов:

  • Безопасный (safe) — не изменяет состояние сервера (только чтение);
  • Идемпотентный (idempotent) — повторный одинаковый запрос даёт тот же результат, что и однократный (не вызывает дополнительных побочных эффектов).
МетодНазначениеТелоБезопасныйИдемпотентный
GETПолучение ресурсанетдада
POSTСоздание ресурса / отправка данныхданетнет
PUTПолная замена/создание ресурсаданетда
PATCHЧастичное обновление ресурсаданетнет
DELETEУдаление ресурсаредконетда
HEADКак GET, но только заголовки (без тела)нетдада
OPTIONSУзнать поддерживаемые методы/возможностинетдада

Пояснения к ключевым различиям:

  • PUT vs PATCH. PUT заменяет ресурс целиком (нужно передать все поля), PATCH меняет только указанные поля. Поэтому PUT идемпотентен, а PATCH — в общем случае нет.
  • POST не идемпотентен. Два одинаковых POST /api/users создадут двух пользователей. Это важно учитывать при повторных отправках форм.
  • HEAD удобен, чтобы проверить существование ресурса или его размер/дату изменения, не скачивая тело.
  • OPTIONS активно используется механизмом CORS: браузер делает «предварительный» (preflight) запрос, спрашивая сервер, разрешён ли основной запрос с другого источника.

5. Коды состояния HTTP

Код состояния — трёхзначное число, сообщающее результат обработки запроса. Первая цифра задаёт класс ответа.

КлассЗначение
1xxИнформационные — запрос принят, обработка продолжается
2xxУспех — запрос выполнен
3xxПеренаправление — нужно дополнительное действие
4xxОшибка клиента — проблема в запросе
5xxОшибка сервера — сбой на стороне сервера

5.1. Самые важные коды

КодФразаКогда используется
100ContinueСервер готов принять тело большого запроса
101Switching ProtocolsПереключение протокола (например, на WebSocket)
200OKЗапрос успешно выполнен, данные в теле
201CreatedРесурс создан (обычно после POST); адрес — в Location
202AcceptedЗапрос принят к асинхронной обработке
204No ContentУспех, но тело пустое (часто после DELETE)
301Moved PermanentlyРесурс навсегда переехал; новый адрес в Location
302FoundВременное перенаправление
304Not ModifiedРесурс не изменился — используй кэш
400Bad RequestНекорректный/невалидный запрос
401UnauthorizedТребуется аутентификация (нет/неверный токен)
403ForbiddenАутентификация есть, но прав недостаточно
404Not FoundРесурс не найден
405Method Not AllowedМетод не поддерживается для этого ресурса
409ConflictКонфликт (например, дубликат)
422Unprocessable EntityСинтаксис верен, но данные не прошли валидацию
429Too Many RequestsПревышен лимит запросов (rate limiting)
500Internal Server ErrorВнутренняя ошибка приложения
502Bad GatewayШлюз получил неверный ответ от upstream-сервера
503Service UnavailableСервис временно недоступен (перегрузка/обслуживание)
504Gateway TimeoutШлюз не дождался ответа от upstream-сервера

💡 Запомните разницу 401 и 403: 401 — «я не знаю, кто ты» (нет действительной аутентификации), 403 — «я знаю, кто ты, но тебе сюда нельзя» (нет прав). А 422 во FastAPI — стандартный ответ при ошибке валидации тела запроса (Pydantic).

6. Примеры сырого HTTP-обмена

6.1. GET-запрос

Запрос клиента:

GET /api/users/123 HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Ответ сервера:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 156
Cache-Control: private, max-age=3600
{
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"created_at": "2023-01-15T10:30:00Z"
}

Обратите внимание: у GET-запроса тела нет, поэтому за заголовками сразу идёт конец сообщения. В ответе пустая строка отделяет заголовки от тела с JSON.

6.2. POST-запрос

Запрос клиента:

POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 89
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
{
"name": "Jane Smith",
"email": "jane@example.com",
"password": "securepassword123"
}

Ответ сервера:

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/users/124
Content-Length: 45
{
"id": 124,
"message": "User created successfully"
}

Здесь у запроса есть тело (JSON), его формат указан в Content-Type, а размер — в Content-Length. Сервер вернул 201 Created и адрес нового ресурса в заголовке Location.

🛠 Попробуйте сами: выполните curl -v https://httpbin.org/get — флаг -v покажет полный сырой обмен заголовками запроса и ответа.

7. Полный жизненный цикл HTTP-запроса

Рассмотрим, что происходит, когда пользователь открывает страницу или приложение делает запрос к API. Путь от действия пользователя до отображения данных проходит несколько этапов.

7.1. Этапы обработки

1. Инициация запроса (на клиенте):

  • пользователь кликает по ссылке или приложение вызывает API;
  • DNS-резолюция — доменное имя (shop.example.com) преобразуется в IP-адрес (192.168.1.100);
  • установка TCP-соединения — «тройное рукопожатие» SYN → SYN-ACK → ACK;
  • TLS handshake (для HTTPS) — согласование шифрования и обмен сертификатами.

2. Формирование и отправка запроса:

  • клиент собирает HTTP-сообщение (метод, URI, заголовки, тело);
  • сообщение инкапсулируется в TCP/IP-пакеты и маршрутизируется по сети до сервера.

3. Обработка на сервере:

  • парсинг запроса (извлечение метода, URI, заголовков, тела);
  • маршрутизация — определение обработчика (во FastAPI — функции-эндпоинта по пути и методу);
  • аутентификация/авторизация — проверка токена и прав;
  • выполнение бизнес-логики и обращение к базе данных;
  • формирование ответа (статус, заголовки, тело).

4. Возврат ответа:

  • ответ инкапсулируется и передаётся по сети обратно клиенту;
  • клиент парсит ответ, обрабатывает данные и отображает результат (рендеринг страницы или обновление UI).

7.2. Пример полного цикла

Сценарий: получение списка товаров интернет-магазина.

1. DNS Lookup: shop.example.com → 192.168.1.100
2. TCP Connection: SYN → SYN-ACK → ACK
3. TLS Handshake: ClientHello → ServerHello → Certificate → Finished
4. HTTP Request:
GET /api/products HTTP/1.1
Host: shop.example.com
Accept: application/json
Authorization: Bearer token123
5. Server Processing:
- разобрать запрос
- направить в обработчик /api/products
- проверить аутентификацию
- запрос к БД: SELECT * FROM products
- сформировать JSON-ответ
6. HTTP Response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1024
[{"id": 1, "name": "Laptop", "price": 999.99}, ...]
7. Client Processing:
- разобрать JSON
- обновить DOM / состояние приложения
- отобразить список товаров

💡 Заметьте: установка соединения (этапы 1–3) выполняется один раз, после чего благодаря постоянным соединениям (Keep-Alive) по нему можно отправить много запросов, не повторяя «рукопожатия».

8. Версии HTTP

8.1. HTTP/1.1 (1997)

Долгое время — основная версия протокола. Ключевые особенности:

  • постоянные соединения (Keep-Alive) — одно TCP-соединение переиспользуется для нескольких запросов;
  • chunked transfer encoding — передача ответа по частям без знания итогового размера;
  • кэширование, сжатие контента, виртуальные хосты (через заголовок Host).

Главная проблема — head-of-line blocking: запросы по одному соединению обрабатываются по очереди, и «застрявший» запрос задерживает остальные. На практике браузеры открывали по несколько параллельных соединений, чтобы это обойти.

8.2. HTTP/2 (2015)

Сохранил семантику HTTP (те же методы, коды, заголовки), но изменил способ передачи:

  • бинарный формат вместо текстового — компактнее и быстрее парсится;
  • мультиплексирование — много параллельных запросов в одном соединении без блокировок на уровне HTTP;
  • сжатие заголовков (HPACK) — экономия трафика на повторяющихся заголовках;
  • Server Push — сервер может заранее отправить ресурсы, которые скоро понадобятся клиенту (на практике используется редко).

8.3. HTTP/3 (2022)

  • построен поверх протокола QUIC (работает на UDP, а не TCP);
  • встроенное шифрование (TLS 1.3 как часть протокола);
  • устраняет head-of-line blocking уже на транспортном уровне;
  • лучше переносит потери пакетов и смену сети (например, переход с Wi-Fi на мобильную сеть).

Для разработчика важно: логическая модель (запрос/ответ, методы, коды, заголовки) одинакова во всех версиях. Различается лишь то, как сообщения упаковываются и передаются по сети. Поэтому код приложения на FastAPI обычно не зависит от версии HTTP — её выбирают сервер и клиент.

Краткие итоги

  • HTTP — протокол прикладного уровня, работающий по модели «запрос — ответ» поверх надёжного транспорта (TCP).
  • Ключевые свойства: текстовый (в HTTP/1.x) и без состояния (stateless) — сервер не помнит предыдущие запросы; «память» о пользователе реализуется через cookie/токены поверх протокола.
  • HTTP-запрос = стартовая строка (метод + URI + версия) + заголовки + (необязательно) тело.
  • HTTP-ответ = статус-строка (версия + код + фраза) + заголовки + тело.
  • Методы различаются по семантике и свойствам безопасности и идемпотентности: GET/HEAD/OPTIONS — безопасные; GET/PUT/DELETE/HEAD/OPTIONS — идемпотентные; POST/PATCH — нет.
  • Коды состояния делятся на классы 1xx5xx; для backend важнее всего 200/201/204, 301/302/304, 400/401/403/404/422/429, 500/502/503/504.
  • Заголовки управляют содержимым (Content-Type, Content-Length), аутентификацией (Authorization, Cookie), кэшированием (Cache-Control, ETag) и безопасностью.
  • Жизненный цикл запроса: DNS → TCP-рукопожатие → (TLS) → отправка запроса → обработка на сервере (парсинг, маршрутизация, авторизация, БД) → ответ → рендеринг.
  • Версии: HTTP/1.1 (текстовый, Keep-Alive), HTTP/2 (бинарный, мультиплексирование), HTTP/3 (на базе QUIC). Семантика во всех версиях одинакова.

Вопросы для самопроверки

  1. Что означает свойство «без состояния» (stateless)? Как при этом приложения «узнают» пользователя между запросами?
  2. Из каких трёх частей состоит HTTP-запрос? Что находится в стартовой строке?
  3. Чем статус-строка ответа отличается от стартовой строки запроса?
  4. Что разделяет заголовки и тело сообщения?
  5. В чём разница между методами PUT и PATCH? Почему PUT идемпотентен, а PATCH — нет?
  6. Что такое «безопасный» и «идемпотентный» метод? Приведите примеры.
  7. Чем отличаются коды 401 и 403? А 404 и 405?
  8. Какой код состояния уместно вернуть после успешного создания ресурса методом POST? А после удаления без возврата данных?
  9. За что отвечают заголовки Content-Type, Authorization, Cache-Control и Location?
  10. Перечислите этапы жизненного цикла HTTP-запроса от ввода URL до отображения результата.
  11. Зачем нужна DNS-резолюция и на каком этапе она происходит?
  12. Чем HTTP/2 принципиально отличается от HTTP/1.1? Меняется ли при этом семантика методов и кодов?