Транспорт бэкенда
HTTP/1.1: запрос, ответ, заголовки
1991 год. Тим Бернерс-Ли отправляет первый HTTP-запрос - три строки текста. 1997 год - RFC 2068, Roy Fielding добавляет кеширование, методы, статус-коды. 2025 год - тот же текстовый протокол движет LLM API, streaming inference, webhook-колбеками. 30 лет и ни одного обратно-несовместимого разрыва.
- LLM API (OpenAI, Anthropic) - HTTP POST с JSON-телом и Bearer-токеном в Authorization
- Streaming inference - chunked transfer encoding, SSE через text/event-stream
- Vector DB HTTP API - Qdrant/Pinecone принимают JSON по REST, ETag для кеша коллекций
- Webhook callbacks от LLM-провайдеров при batch inference - POST на эндпоинт клиента
- CDN-слой (Cloudflare Workers) - Cache-Control и ETag в каждом ответе API
Анатомия HTTP-сообщения
1991 год. Тим Бернерс-Ли отправляет первый HTTP-запрос. Три строки текста по TCP-соединению. Ни заголовков, ни статусов - только `GET /page`. Сейчас тот же принцип движет 5 млрд устройств, а средний запрос к Cloudflare несёт 50 заголовков. Механика не изменилась.
HTTP-сообщение - это текст, разделённый на три части: **стартовая строка** (метод/URL или статус), **заголовки** (ключ-значение, по одному на строку), **тело** (отделено пустой строкой). Всё. Никакой магии - только ASCII по TCP.
**Persistent connections (Keep-Alive):** HTTP/1.0 закрывал TCP после каждого запроса. HTTP/1.1 держит соединение открытым по умолчанию - заголовок `Connection: keep-alive`. Для страницы с 30 ресурсами это разница между 30 TCP-handshake и 1.
Есть проблема, которую Keep-Alive не решает: **head-of-line blocking**. HTTP/1.1 обрабатывает запросы последовательно по одному соединению. Если первый запрос завис - остальные ждут. Браузеры обходят это через 6-8 параллельных TCP-соединений. HTTP/2 решает проблему мультиплексингом - но это следующий урок.
**Chunked Transfer Encoding:** когда размер тела неизвестен заранее, сервер ставит `Transfer-Encoding: chunked` вместо `Content-Length`. Каждый чанк начинается с размера в hex. LLM streaming inference работает именно так - токены летят отдельными чанками по мере генерации.
Что обязательно присутствует в HTTP/1.1 запросе, но отсутствовало в HTTP/0.9?
Методы и статус-коды
Опасное заявление из RFC 7231: **POST не создаёт ресурс. PUT создаёт.** Но 90% REST API используют POST для создания, потому что URL нового ресурса неизвестен клиенту заранее. Это не ошибка - это намеренное нарушение семантики ради практичности. Понимание разницы между формальной спецификацией и реальным использованием - признак зрелости.
**Безопасность и идемпотентность - разные вещи.** Безопасный метод не меняет состояние сервера (GET, HEAD, OPTIONS). Идемпотентный метод при повторном вызове с теми же параметрами даёт тот же результат (GET, PUT, DELETE). DELETE идемпотентен, но не безопасен. POST - ни то, ни другое.
**404 vs 410:** `404 Not Found` означает "сервер не нашёл", но не говорит почему. `410 Gone` - "было, удалено навсегда". Google и Bing по 410 удаляют URL из индекса немедленно. По 404 - могут переиндексировать ещё несколько недель.
Для LLM API особенно важны: `429 Too Many Requests` с заголовком `Retry-After` (rate limit), `402 Payment Required` (кончились кредиты) и `413 Payload Too Large` (превышен лимит контекста до отправки). Правильная обработка этих кодов - разница между production-ready клиентом и прототипом.
Метод DELETE называют идемпотентным. Что это значит на практике?
Заголовки и cookies
HTTP не имеет состояния. Каждый запрос независим - сервер не помнит предыдущий. Но приложения работают с состоянием: сессии, корзины, права. Cookies - это компромисс: браузер хранит маленькие строки и отправляет их в каждом запросе. Сервер читает cookie - и "помнит" пользователя. Это не нарушение stateless-природы HTTP, а слой поверх него.
**CORS preflight:** браузер перед POST/PUT с кастомными заголовками отправляет `OPTIONS`-запрос. Сервер отвечает `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers`. Если ответ устраивает - браузер шлёт основной запрос. Backend без поддержки OPTIONS ломает любой frontend на другом домене.
**SameSite=None** без флага **Secure** - распространённая ошибка при настройке cross-site cookies. Браузеры с 2020 года отклоняют такие cookies молча. Симптом: аутентификация работает в HTTP-разработке, ломается в HTTPS-продакшне - или наоборот.
Какой флаг cookie защищает от XSS-атаки, которая пытается украсть session-токен через JavaScript?
Кеширование и ETag
Каждый запрос к Cloudflare обрабатывается за 3 мс. Из них 2 мс - это передача по сети. На origin-сервер большинство запросов не доходит вообще. Это не трюк - это HTTP-кеширование, которое Roy Fielding встроил в протокол в 1997 году как часть REST-архитектурного стиля.
**ETag для embedding cache:** запросы на эмбеддинги одного текста стоят одинаково независимо от частоты. Кеширование через ETag или Content-Hash позволяет не платить за повторные вычисления. Qdrant и Pinecone используют этот принцип в HTTP API - `ETag` версии коллекции в ответах.
**no-store vs no-cache** - самая частая путаница в HTTP-кешировании. `no-cache` НЕ означает "не кешировать" - оно означает "кешировать, но проверять актуальность при каждом использовании". `no-store` означает "не хранить вообще". Для чувствительных данных (токены, медицина, финансы) нужен именно `no-store`.
**Last-Modified как альтернатива ETag:** сервер возвращает `Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT`, клиент присылает `If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT`. Менее точен - работает с точностью до секунды. ETag предпочтительнее, но Last-Modified проще реализовать для статических файлов.
HTTP без состояния - значит нельзя поддерживать сессии пользователей
Stateless означает: каждый запрос самодостаточен. Состояние хранится вне протокола - в cookies, JWT-токенах, URL-параметрах
Stateless - архитектурное ограничение транспортного слоя, не прикладного. Cookies прикреплены к каждому запросу, делая его самодостаточным. Сервер не хранит контекст между запросами - но клиент передаёт идентификатор контекста в каждом запросе
Сервер вернул `Cache-Control: no-cache`. Что произойдёт при следующем запросе того же ресурса?
HTTP/1.1: что нужно знать backend-разработчику
- HTTP-сообщение = стартовая строка + заголовки + пустая строка + тело; всё текст по TCP
- Keep-Alive держит TCP-соединение; chunked encoding - для тел неизвестного размера (LLM streaming)
- Safe = не меняет состояние (GET); idempotent = повтор безопасен (GET, PUT, DELETE); POST - ни то, ни другое
- 404 - не нашёл; 410 - нет навсегда; 429 - rate limit; 304 - кеш актуален
- HttpOnly защищает cookie от XSS; SameSite=None требует Secure; no-cache != no-store
Связанные темы
HTTP - фундамент: поверх него REST, WebSocket upgrade, gRPC по HTTP/2
- REST API — REST - архитектурный стиль поверх HTTP-методов и статусов
- HTTP/2 и HTTP/3 — Мультиплексинг решает head-of-line blocking; QUIC заменяет TCP
- TLS/HTTPS — TLS-handshake - до первого HTTP-байта
- LLM API Integration — Первый HTTP-клиент с Bearer-токеном и JSON - это LLM API
Вопросы для размышления
- Клиент отправляет PUT /users/42 дважды - с одинаковыми данными. Какой должен быть результат и какой статус-код при первом и втором вызове?
- Для API эмбеддингов, который возвращает одинаковые векторы для одинакового текста, какую стратегию кеширования выбрать и почему?
- Чем отличается поведение браузера при cookie с SameSite=Lax от SameSite=Strict - в каком сценарии это важно?
Связанные уроки
- bt-04-dns-tls — TLS-handshake предшествует любому HTTPS-запросу
- bt-06-rest — REST-семантика строится поверх методов и статусов HTTP
- bt-07-http2-http3 — HTTP/2 решает head-of-line blocking из HTTP/1.1
- aie-05-api-integration — LLM API - HTTP POST с Bearer-токеном и JSON-телом
- bt-02-osi-tcp — HTTP работает поверх TCP, сессия начинается с TCP-handshake
- net-21-http-basics