Тема
Как работает HTTP-запрос
JuniorMiddleSeniorФундамент ~45 мин
Границы темы
Scope: общая семантика HTTP и путь обычного HTTPS-запроса через DNS, транспорт, TLS, reverse proxy и приложение.
Не рассматриваем подробно: алгоритмы TCP congestion control, криптографию TLS, QPACK/HPACK и бинарный wire format HTTP/2 и HTTP/3. Это отдельные фундаментальные темы.
Результаты обучения
После статьи читатель сможет:
- проследить запрос от URL до байтов ответа;
- отличить ресурс, representation, method semantics, framing и transport;
- объяснить, где возникают DNS-, connection-, TLS-, HTTP- и application-ошибки;
- выбрать корректные method/status/cache headers для базового API;
- объяснить различия HTTP/1.1, HTTP/2 и HTTP/3 без мифа «это разные API».
Основная модель
HTTP — семейство Каждый запрос имеет самостоятельную семантику и не требует встроенного протокольного состояния сессии.request/response-протоколов прикладного уровня. Клиент обращается к ресурсу и передаёт метод, target, поля и иногда content. Сервер возвращает status, поля и
Передаваемое представление текущего или желаемого состояния HTTP-ресурса.. Общая семантика не зависит от того,передано сообщение текстовым HTTP/1.1, бинарным HTTP/2 или через QUIC в HTTP/3.
Диаграмма
Основные термины
| Термин | Определение | Не путать с |
|---|---|---|
| Resource | Абстрактная цель запроса, обычно идентифицированная URI | Файлом на диске |
| Representation | Текущее представление состояния ресурса | Самим ресурсом |
| Method | Семантика действия клиента | Именем backend-функции |
| Status code | Результат обработки конкретного запроса | Бизнес-кодом ошибки |
| Field/Header | Метаданные сообщения, representation или соединения | Payload |
| Content/Body | Байты содержимого сообщения | Любым состоянием сервера |
| Intermediary | Proxy, gateway или tunnel между client и origin | Обязательно кешем |
End-to-end сценарий
Рассмотрим:
text
https://api.example.com/users/42?view=shortДиаграмма
- Клиент разбирает URL: scheme, authority, path и query.
- Resolver получает адрес origin или edge.
- Клиент переиспользует существующее соединение либо устанавливает новое.
- Для HTTPS стороны выполняют TLS handshake и согласуют application protocol через ALPN.
- HTTP implementation кодирует request в формат выбранной версии.
- Reverse proxy проверяет ограничения, завершает TLS, маршрутизирует и добавляет доверенные служебные поля.
- Приложение аутентифицирует principal, авторизует действие, валидирует input и выполняет use case.
- Ответ проходит обратно через intermediaries; кеш может сохранить representation.
Что происходит под капотом
Semantics не равна синтаксису
Диаграмма
| Версия | Transport | Multiplexing | Существенная особенность |
|---|---|---|---|
| HTTP/1.1 | Обычно TCP; TLS для HTTPS | Нет встроенных независимых streams | Текстовая start-line и fields |
| HTTP/2 | Обычно TLS поверх TCP | Streams в одном connection | Binary framing и header compression |
| HTTP/3 | QUIC поверх UDP | Независимые QUIC streams | Потеря в одном stream не блокирует другие |
Методы и статусы сохраняют значение между версиями. Переход на HTTP/2 не делает неидемпотентный POST идемпотентным.
Безопасность и идемпотентность методов
| Method | Safe | Idempotent | Типичный смысл |
|---|---|---|---|
| GET | Да | Да | Получить representation |
| HEAD | Да | Да | Получить поля без response content |
| POST | Нет | Нет | Обработать content по semantics ресурса |
| PUT | Нет | Да | Создать/заменить состояние target resource |
| DELETE | Нет | Да | Удалить связь target resource с текущей функциональностью |
| PATCH | Нет | Не гарантируется | Частично изменить ресурс |
Safe означает, что клиент не просит изменить состояние. Это не обещание отсутствия логов, метрик или billing side effects. Idempotent означает одинаковый intended effect нескольких одинаковых запросов, а не побитно одинаковый ответ.
Framing и длина сообщения
В HTTP/1.1 получатель обязан однозначно определить границу сообщения: по правилам метода/status, Content-Length, transfer coding или закрытию connection. Расхождение между intermediaries в определении границ создаёт класс request smuggling-атак.
HTTP/2 и HTTP/3 передают данные фреймами и streams, но application всё равно должна ограничивать размеры headers/content и время обработки.
Минимальный воспроизводимый пример
Запрос
bash
curl --http1.1 --verbose \
--header 'Accept: application/json' \
https://example.com/--verbose показывает connection, TLS negotiation, request fields и response fields. Тело может отличаться, поэтому диагностировать нужно по слоям, а не по одной строке.
Условный GET
http
GET /users/42 HTTP/1.1
Host: api.example.com
Accept: application/json
If-None-Match: "user-42-v7"http
HTTP/1.1 304 Not Modified
ETag: "user-42-v7"
Cache-Control: private, max-age=60304 не содержит новую representation: клиент использует сохранённую копию и обновляет её metadata.
Минимальный handler
ts
type Result =
| { status: 200; body: { id: string; name: string }; etag: string }
| { status: 404; body: { error: "not_found" } };
export async function getUser(id: string): Promise<Result> {
const user = await users.findById(id);
if (!user) return { status: 404, body: { error: "not_found" } };
return {
status: 200,
body: { id: user.id, name: user.name },
etag: `"user-${user.id}-v${user.version}"`,
};
}go
type Result struct {
Status int
Body any
ETag string
}
func getUser(ctx context.Context, id string) Result {
user, err := users.FindByID(ctx, id)
if errors.Is(err, sql.ErrNoRows) {
return Result{Status: 404, Body: map[string]string{"error": "not_found"}}
}
return Result{
Status: 200,
Body: map[string]string{"id": user.ID, "name": user.Name},
ETag: fmt.Sprintf(`"user-%s-v%d"`, user.ID, user.Version),
}
}php
<?php
function getUser(string $id): array
{
$user = $GLOBALS['users']->findById($id);
if ($user === null) {
return ['status' => 404, 'body' => ['error' => 'not_found']];
}
return [
'status' => 200,
'body' => ['id' => $user->id, 'name' => $user->name],
'etag' => sprintf('"user-%s-v%d"', $user->id, $user->version),
];
}Этот пример показывает mapping domain result → HTTP response, но production handler также должен учитывать cancellation, authorization, content negotiation, rate limits и observability.
Как диагностировать ошибку
| Наблюдение | Вероятный слой | Следующая проверка |
|---|---|---|
| DNS name not resolved | DNS | dig, resolver configuration |
| Connection refused | Transport/listener | IP, port, ss, firewall |
| TLS certificate error | TLS/PKI | hostname, chain, clock, trust store |
| 400/413/431 | HTTP parsing/limits | request syntax, content/header size |
| 401/403 | Identity/access policy | credentials, principal, decision log |
| 404 | Routing или resource lookup | matched route, normalized target |
| 502/504 | Proxy/upstream | upstream health, timeout budget |
| 500 | Application | trace ID, structured log, error cause |
Правильный timeout budget уменьшается по цепочке. Слепой retry способен умножить нагрузку и ухудшить аварию; повторять запрос безопасно только с учётом метода, идемпотентности и факта получения ответа.
Ограничения и trade-offs
- HTTP stateless на уровне semantics, но приложения часто добавляют sessions.
- Промежуточный HTTP-узел между клиентом и origin: proxy, gateway или tunnel. могут преобразовывать сообщения,поэтому нельзя доверять любому
Forwarded/X-Forwarded-*без спискаИзвестный промежуточный узел, чьи служебные поля приложение настроено принимать как доверенные.. - Кеширование уменьшает latency и нагрузку, но требует корректной модели freshness, validation и cache key.
- Multiplexing не устраняет application head-of-line blocking, медленные downstream зависимости и исчерпание ресурсов.
- TLS защищает данные в transit, но не исправляет broken authorization или XSS.
Failure modes и типичные ошибки
| Ошибка | Почему возникает | Корректирующая модель |
|---|---|---|
| Секрет в URL | URL попадает в history, logs и telemetry | Передавать credential в предназначенном поле |
| POST всегда означает create | Метод не задаёт CRUD-операцию автоматически | Следовать semantics target resource |
| Retry любого запроса | Не учитывается повторный side effect | Idempotency key и retry policy |
Доверие X-Forwarded-For | Поле может прислать клиент | Перезаписывать на trusted edge |
200 для любой ошибки | Теряется машинно-читаемая семантика | Status + стабильная error schema |
| Кеш только «на N секунд» | Игнорируются validation и shared/private caches | Явная cache policy |
Безопасность и производительность
- Ограничивать request line, fields, content и время чтения.
- Нормализовать routing один раз и не допускать разных трактовок proxy/application.
- Не логировать credentials и чувствительный content.
- Использовать parameterized queries и server-side authorization.
- Переиспользовать connections, но ограничивать pool и очередь ожидания.
- Измерять DNS, connect, TLS, time-to-first-byte и application latency раздельно.
Практическое задание
Запустите локальный HTTP-сервис за reverse proxy и соберите один trace:
- запрос с
curl --verbose; - access log proxy с request ID;
- structured log приложения с тем же ID;
- условный запрос через
ETag; - искусственный upstream timeout.
Критерии готовности:
- можно сопоставить один запрос во всех логах;
304возвращается только при совпавшем validator;- timeout превращается в контролируемый
504, а не бесконечное ожидание; - секреты не попадают в URL и logs.
Подсказка
Начните с curl --write-out для DNS/connect/TLS/TTFB и добавьте request ID на самом внешнем доверенном proxy.
Проверка знаний
ИНТЕРАКТИВНАЯ ПРОВЕРКА
HTTP: semantics, порядок и гарантии
0 / 3
01Что сохраняется при переходе с HTTP/1.1 на HTTP/2 или HTTP/3?
02Идемпотентная операция обязана возвращать побитно одинаковый response.
03Восстановите упрощённый порядок первого HTTPS-запроса.
- Отправка HTTP request
- Разрешение DNS-имени
- Обработка application handler
- Установка transport connection и TLS
Самопроверка
- Почему HTTP/2 меняет framing, но не semantics метода GET?
- Чем safe отличается от idempotent?
- Почему
304нельзя интерпретировать как пустой успешный200? - Где следует доверенно формировать client IP?
- Почему retry с exponential backoff всё равно может быть опасен?
Ответы и критерии
- Semantics общая для версий, а HTTP/2 определяет другое binary framing.
- Safe не просит изменить состояние; idempotent допускает повтор с тем же intended effect.
304указывает использовать сохранённую representation и обновить metadata.- На известном trusted edge; входные forwarding fields клиента нужно удалить или нормализовать.
- Операция может быть неидемпотентной, а множество клиентов — синхронно повторять запросы и усиливать перегрузку.
Краткое резюме
HTTP нужно мыслить слоями: semantics → version-specific framing → transport → TLS → intermediaries → application. Такая модель позволяет выбирать корректное поведение и быстро локализовать сбой.
Следующие шаги
- TCP, QUIC и TLS;
- HTTP caching;
- reverse proxy и load balancing;
- authentication/authorization;
- timeouts, retries и idempotency.
Источники
- RFC 9110: HTTP Semantics — ресурсы, методы, статусы, поля и общая semantics HTTP.
- RFC 9111: HTTP Caching — freshness, validation и поведение caches.
- RFC 9112: HTTP/1.1 — message syntax, framing и connection management HTTP/1.1.
- RFC 9113: HTTP/2 — binary framing, streams и multiplexing.
- RFC 9114: HTTP/3 — mapping HTTP semantics поверх QUIC.