Лекция 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-запрос состоит из трёх частей:
- Стартовая строка (Request Line)
- Заголовки (Headers)
- Тело (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-ответа
Ответ устроен симметрично запросу и тоже состоит из трёх частей:
- Статус-строка (Status Line)
- Заголовки (Headers)
- Тело (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 | Узнать поддерживаемые методы/возможности | нет | да | да |
Пояснения к ключевым различиям:
PUTvsPATCH.PUTзаменяет ресурс целиком (нужно передать все поля),PATCHменяет только указанные поля. ПоэтомуPUTидемпотентен, аPATCH— в общем случае нет.POSTне идемпотентен. Два одинаковыхPOST /api/usersсоздадут двух пользователей. Это важно учитывать при повторных отправках форм.HEADудобен, чтобы проверить существование ресурса или его размер/дату изменения, не скачивая тело.OPTIONSактивно используется механизмом CORS: браузер делает «предварительный» (preflight) запрос, спрашивая сервер, разрешён ли основной запрос с другого источника.
⸻
5. Коды состояния HTTP
Код состояния — трёхзначное число, сообщающее результат обработки запроса. Первая цифра задаёт класс ответа.
| Класс | Значение |
|---|---|
1xx | Информационные — запрос принят, обработка продолжается |
2xx | Успех — запрос выполнен |
3xx | Перенаправление — нужно дополнительное действие |
4xx | Ошибка клиента — проблема в запросе |
5xx | Ошибка сервера — сбой на стороне сервера |
5.1. Самые важные коды
| Код | Фраза | Когда используется |
|---|---|---|
100 | Continue | Сервер готов принять тело большого запроса |
101 | Switching Protocols | Переключение протокола (например, на WebSocket) |
200 | OK | Запрос успешно выполнен, данные в теле |
201 | Created | Ресурс создан (обычно после POST); адрес — в Location |
202 | Accepted | Запрос принят к асинхронной обработке |
204 | No Content | Успех, но тело пустое (часто после DELETE) |
301 | Moved Permanently | Ресурс навсегда переехал; новый адрес в Location |
302 | Found | Временное перенаправление |
304 | Not Modified | Ресурс не изменился — используй кэш |
400 | Bad Request | Некорректный/невалидный запрос |
401 | Unauthorized | Требуется аутентификация (нет/неверный токен) |
403 | Forbidden | Аутентификация есть, но прав недостаточно |
404 | Not Found | Ресурс не найден |
405 | Method Not Allowed | Метод не поддерживается для этого ресурса |
409 | Conflict | Конфликт (например, дубликат) |
422 | Unprocessable Entity | Синтаксис верен, но данные не прошли валидацию |
429 | Too Many Requests | Превышен лимит запросов (rate limiting) |
500 | Internal Server Error | Внутренняя ошибка приложения |
502 | Bad Gateway | Шлюз получил неверный ответ от upstream-сервера |
503 | Service Unavailable | Сервис временно недоступен (перегрузка/обслуживание) |
504 | Gateway Timeout | Шлюз не дождался ответа от upstream-сервера |
💡 Запомните разницу
401и403:401— «я не знаю, кто ты» (нет действительной аутентификации),403— «я знаю, кто ты, но тебе сюда нельзя» (нет прав). А422во FastAPI — стандартный ответ при ошибке валидации тела запроса (Pydantic).
⸻
6. Примеры сырого HTTP-обмена
6.1. GET-запрос
Запрос клиента:
GET /api/users/123 HTTP/1.1Host: api.example.comUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)Accept: application/jsonAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Ответ сервера:
HTTP/1.1 200 OKContent-Type: application/jsonContent-Length: 156Cache-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.1Host: api.example.comContent-Type: application/jsonContent-Length: 89Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
{ "name": "Jane Smith", "email": "jane@example.com", "password": "securepassword123"}Ответ сервера:
HTTP/1.1 201 CreatedContent-Type: application/jsonLocation: /api/users/124Content-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.1002. TCP Connection: SYN → SYN-ACK → ACK3. TLS Handshake: ClientHello → ServerHello → Certificate → Finished4. 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— нет. - Коды состояния делятся на классы
1xx–5xx; для 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). Семантика во всех версиях одинакова.
⸻
Вопросы для самопроверки
- Что означает свойство «без состояния» (stateless)? Как при этом приложения «узнают» пользователя между запросами?
- Из каких трёх частей состоит HTTP-запрос? Что находится в стартовой строке?
- Чем статус-строка ответа отличается от стартовой строки запроса?
- Что разделяет заголовки и тело сообщения?
- В чём разница между методами
PUTиPATCH? ПочемуPUTидемпотентен, аPATCH— нет? - Что такое «безопасный» и «идемпотентный» метод? Приведите примеры.
- Чем отличаются коды
401и403? А404и405? - Какой код состояния уместно вернуть после успешного создания ресурса методом
POST? А после удаления без возврата данных? - За что отвечают заголовки
Content-Type,Authorization,Cache-ControlиLocation? - Перечислите этапы жизненного цикла HTTP-запроса от ввода URL до отображения результата.
- Зачем нужна DNS-резолюция и на каком этапе она происходит?
- Чем HTTP/2 принципиально отличается от HTTP/1.1? Меняется ли при этом семантика методов и кодов?