Skip to content

Как работает HTTP-запрос

JuniorMiddleSeniorФундамент ~45 мин

Границы темы

Scope: общая семантика HTTP и путь обычного HTTPS-запроса через DNS, транспорт, TLS, reverse proxy и приложение.

Не рассматриваем подробно: алгоритмы TCP congestion control, криптографию TLS, QPACK/HPACK и бинарный wire format HTTP/2 и HTTP/3. Это отдельные фундаментальные темы.

Результаты обучения

После статьи читатель сможет:

  1. проследить запрос от URL до байтов ответа;
  2. отличить ресурс, representation, method semantics, framing и transport;
  3. объяснить, где возникают DNS-, connection-, TLS-, HTTP- и application-ошибки;
  4. выбрать корректные method/status/cache headers для базового API;
  5. объяснить различия 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Байты содержимого сообщенияЛюбым состоянием сервера
IntermediaryProxy, gateway или tunnel между client и originОбязательно кешем

End-to-end сценарий

Рассмотрим:

text
https://api.example.com/users/42?view=short
Диаграмма
  1. Клиент разбирает URL: scheme, authority, path и query.
  2. Resolver получает адрес origin или edge.
  3. Клиент переиспользует существующее соединение либо устанавливает новое.
  4. Для HTTPS стороны выполняют TLS handshake и согласуют application protocol через ALPN.
  5. HTTP implementation кодирует request в формат выбранной версии.
  6. Reverse proxy проверяет ограничения, завершает TLS, маршрутизирует и добавляет доверенные служебные поля.
  7. Приложение аутентифицирует principal, авторизует действие, валидирует input и выполняет use case.
  8. Ответ проходит обратно через intermediaries; кеш может сохранить representation.

Что происходит под капотом

Semantics не равна синтаксису

Диаграмма
ВерсияTransportMultiplexingСущественная особенность
HTTP/1.1Обычно TCP; TLS для HTTPSНет встроенных независимых streamsТекстовая start-line и fields
HTTP/2Обычно TLS поверх TCPStreams в одном connectionBinary framing и header compression
HTTP/3QUIC поверх UDPНезависимые QUIC streamsПотеря в одном stream не блокирует другие

Методы и статусы сохраняют значение между версиями. Переход на HTTP/2 не делает неидемпотентный POST идемпотентным.

Безопасность и идемпотентность методов

MethodSafeIdempotentТипичный смысл
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=60

304 не содержит новую 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 resolvedDNSdig, resolver configuration
Connection refusedTransport/listenerIP, port, ss, firewall
TLS certificate errorTLS/PKIhostname, chain, clock, trust store
400/413/431HTTP parsing/limitsrequest syntax, content/header size
401/403Identity/access policycredentials, principal, decision log
404Routing или resource lookupmatched route, normalized target
502/504Proxy/upstreamupstream health, timeout budget
500Applicationtrace 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 и типичные ошибки

ОшибкаПочему возникаетКорректирующая модель
Секрет в URLURL попадает в history, logs и telemetryПередавать credential в предназначенном поле
POST всегда означает createМетод не задаёт CRUD-операцию автоматическиСледовать semantics target resource
Retry любого запросаНе учитывается повторный side effectIdempotency 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:

  1. запрос с curl --verbose;
  2. access log proxy с request ID;
  3. structured log приложения с тем же ID;
  4. условный запрос через ETag;
  5. искусственный 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-запроса.
  1. Отправка HTTP request
  2. Разрешение DNS-имени
  3. Обработка application handler
  4. Установка transport connection и TLS
Проверено: 0 из 3

Самопроверка

  1. Почему HTTP/2 меняет framing, но не semantics метода GET?
  2. Чем safe отличается от idempotent?
  3. Почему 304 нельзя интерпретировать как пустой успешный 200?
  4. Где следует доверенно формировать client IP?
  5. Почему retry с exponential backoff всё равно может быть опасен?
Ответы и критерии
  1. Semantics общая для версий, а HTTP/2 определяет другое binary framing.
  2. Safe не просит изменить состояние; idempotent допускает повтор с тем же intended effect.
  3. 304 указывает использовать сохранённую representation и обновить metadata.
  4. На известном trusted edge; входные forwarding fields клиента нужно удалить или нормализовать.
  5. Операция может быть неидемпотентной, а множество клиентов — синхронно повторять запросы и усиливать перегрузку.

Краткое резюме

HTTP нужно мыслить слоями: semantics → version-specific framing → transport → TLS → intermediaries → application. Такая модель позволяет выбирать корректное поведение и быстро локализовать сбой.

Следующие шаги

  • TCP, QUIC и TLS;
  • HTTP caching;
  • reverse proxy и load balancing;
  • authentication/authorization;
  • timeouts, retries и idempotency.

Источники