awesome-everything EN
↑ Обратно к восхождению

Сети и протоколы

HTTP-стриминг, SSE, WebSocket и gRPC

Суть HTTP — не только запрос-ответ: chunked transfer, SSE, WebSocket и gRPC каждый по-своему пробивают брешь в модели единственного ответа для реального времени и двустороннего общения.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 12 min

REST API возвращают полный ответ. Но живые дашборды, чат, цены в реальном времени и потоки токенов AI требуют другого: данные поступают по частям или в двух направлениях. HTTP растягивается до этих случаев — через chunked transfer, SSE, апгрейды WebSocket и gRPC-трейлеры — но каждый выбор несёт свои компромиссы.

Chunked transfer encoding

HTTP/1.1 поддерживает Transfer-Encoding: chunked для потоковой передачи ответов без заранее известной общей длины. Сервер отправляет чанки с префиксом размера до тех пор, пока чанк нулевой длины не закрывает тело:

HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: text/plain

5\r\n
Hello\r\n
6\r\n
 World\r\n
0\r\n
\r\n

Это позволяет серверу начать отправку данных до того, как вычислен весь ответ — полезно для генерируемого HTML, потоковых AI-завершений и слежки за логами. HTTP/2 и HTTP/3 не используют chunked encoding: DATA-фреймы нативно обрабатывают тела переменной длины без явного слоя фреймирования.

Server-Sent Events (SSE)

SSE (Content-Type: text/event-stream) — долгоживущий HTTP-ответ, доставляющий читаемые JavaScript’ом события от сервера браузеру в одном направлении. Браузер делает один GET-запрос; сервер держит соединение открытым и толкает события:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache

data: {"temp": 72.4}\n\n
data: {"temp": 72.6}\n\n

SSE построен на стандартном HTTP, проходит через все прокси без апгрейда протокола, автоматически переподключается при разрыве соединения и работает на HTTP/2 (где едет в одном потоке среди многих, не блокируя другие запросы). Ограничение: только сервер → клиент — клиент не может толкать данные обратно через тот же SSE-поток (нужен отдельный запрос).

SSE — правильный выбор для: живых дашбордов, потоков уведомлений, потоковой передачи AI-токенов (text/event-stream — то, как ChatGPT-подобные интерфейсы стримят токены). WebSocket — правильный выбор для: двустороннего реального времени (чат, совместное редактирование, состояние мультиплеерной игры).

WebSocket

WebSocket (RFC 6455) начинается как HTTP/1.1 и апгрейдится до бинарного двустороннего фрейм-протокола:

  1. Клиент отправляет GET /ws HTTP/1.1 с Upgrade: websocket и Connection: Upgrade.
  2. Сервер отвечает 101 Switching Protocols.
  3. TCP-соединение теперь — WebSocket-туннель: бинарные фреймы (текстовые или бинарные) текут в обоих направлениях.

WebSocket несовместим с мультиплексированием HTTP/2 — после переключения соединения на протокол WebSocket оно больше не является HTTP/2-соединением. Решение: RFC 8441 (WebSocket over HTTP/2) — клиент отправляет расширенный CONNECT-запрос с :protocol: websocket, создавая один HTTP/2-поток, ведущий себя как WebSocket-туннель. Это позволяет одному HTTP/2-соединению мультиплексировать несколько WebSocket-соединений рядом с обычными запросами.

Сравнение вариантов HTTP-стриминга
SSE — направление
Сервер → Клиент
SSE — требует апгрейда протокола
Нет — обычный HTTP
WebSocket — направление
Двустороннее
WebSocket — поддержка HTTP/2
RFC 8441 extended CONNECT
gRPC — транспорт
HTTP/2 (всегда)
gRPC — двусторонний стриминг
Да (4 режима: unary, server-streaming, client-streaming, bidi)

gRPC: трейлеры и зависимость от HTTP/2

gRPC — высокопроизводительный RPC-фреймворк, работающий поверх HTTP/2. Ключевая механика:

  • gRPC кодирует запросы и ответы как Protocol Buffers (бинарно сериализованные структуры) в HTTP/2 DATA-фреймах.
  • Каждый gRPC-вызов — один HTTP/2-поток. Множество одновременных RPC мультиплексируются на одном соединении.
  • Трейлеры несут результат: gRPC-статус и сообщение приходят в trailing HEADERS-фреймах после DATA-фреймов (тела). gRPC-статус-код отделён от HTTP-статус-кода — gRPC всегда шлёт 200 OK на HTTP-уровне; реальный результат (OK, NOT_FOUND и т.д.) приходит в трейлере grpc-status.

gRPC-Web существует потому, что браузеры не могут надёжно читать HTTP-трейлеры. gRPC-Web переписывает trailing-заголовки сервера как финальное base64-кодированное сообщение в теле ответа, которое браузеры умеют читать. Прокси (Envoy, gRPC-Web proxy) транслирует между настоящим gRPC (с трейлерами) и gRPC-Web (трейлеры встроены в тело).

Сжатие

Серверы сжимают тела ответов через Content-Encoding: gzip|br|zstd. Brotli (br) сжимает текст примерно на 20% лучше gzip при сопоставимой скорости декомпрессии. Серверы выбирают алгоритм на основе заголовка Accept-Encoding клиента.

Угроза безопасности — атаки CRIME и BREACH: эти атаки эксплуатировали HTTPS + сжатие для утечки сессионных токенов через наблюдение за изменениями размера ответа, когда атакующий внедрял контролируемый контент. Меры защиты: отключить сжатие для ответов, включающих и управляемые атакующим данные, и конфиденциальные данные в одном теле ответа. Сжатие статических ресурсов безопасно (предварительно вычисленное, нет пользовательского ввода). Динамические API-ответы, смешивающие сессионные токены с отражёнными запросом данными, должны либо пропускать сжатие, либо использовать случайное набивание.

HTTP-ограничение скорости

Серверы возвращают 429 Too Many Requests с Retry-After: <секунды> для регулирования агрессивных клиентов. Распространённые алгоритмы:

  • Token bucket: корзина пополняется с фиксированной скоростью; всплески разрешены до ёмкости. Лучше всего для рабочих нагрузок с пиками, но в среднем умеренных.
  • Leaky bucket: запросы вытекают с фиксированной скоростью; излишки ставятся в очередь или сбрасываются. Сглаживает пики.
  • Fixed window: подсчёт запросов за временное окно (например, 100/минута). Уязвим к пикам на границе окна.
  • Sliding window: подсчёт запросов за последние N секунд. Точнее, требует больше памяти.

CDN (Cloudflare, Fastly) ограничивают скорость на краю до того, как запросы достигают origin — первая линия обороны. Origin-серверы добавляют второй слой. Клиенты должны уважать Retry-After и использовать экспоненциальный откат для предотвращения “стада грома” повторных запросов.

Викторина

Почему gRPC использует HTTP/2-трейлеры для статуса вместо HTTP-статус-кода?

Викторина

Когда выбирать SSE вместо WebSocket?

Расставь шаги по порядку

Упорядочьте шаги WebSocket-хендшейка поверх HTTP/1.1:

  1. 1 Клиент отправляет GET /ws HTTP/1.1 с Upgrade: websocket и Sec-WebSocket-Key
  2. 2 Сервер отвечает 101 Switching Protocols с Sec-WebSocket-Accept
  3. 3 HTTP-протокол завершается на этом TCP-соединении
  4. 4 Двусторонний фрейм-протокол WebSocket начинается на том же TCP-соединении
  5. 5 Клиент и сервер теперь могут отправлять фреймы друг другу в любой момент
Почему это работает

Почему gRPC-Web переписывает трейлеры в тело. Браузерные API XHR и fetch() не могут читать HTTP-трейлеры — API просто не предоставляет к ним доступ. Трейлеры — заголовки, приходящие после тела ответа, и HTTP-клиент браузера их скрывает. gRPC-Web решает это, кодируя трейлеры как финальное фреймированное сообщение в теле, с особым байтом-префиксом для различия данных ответа и трейлеров. JavaScript-библиотека gRPC-Web парсит это финальное сообщение для извлечения grpc-status. На стороне сервера прокси Envoy или nginx-grpc-web транслирует между настоящим gRPC (с трейлерами) и gRPC-Web (трейлеры встроены в тело).

Вспомните перед уходом
  1. 01
    В чём разница между SSE и WebSocket и когда выбирать каждый?
  2. 02
    Почему атаки CRIME и BREACH нацелены на HTTPS+сжатие и как защититься?
  3. 03
    Как RFC 8441 позволяет WebSocket сосуществовать с мультиплексированием HTTP/2?
Итог

HTTP-стриминг выходит за рамки единственного запрос-ответ через chunked transfer encoding (тела переменной длины), SSE (долгоживущие текстовые потоки событий сервер→клиент), WebSocket (двусторонние бинарные фреймы через апгрейд 101, или RFC 8441 extended CONNECT на HTTP/2) и gRPC (бинарные RPC на HTTP/2, с RPC-статусом в trailing HEADERS-фреймах). gRPC-Web прокси переписывают трейлеры в фреймы тела для совместимости с браузерами. Сжатие (Brotli/gzip через Content-Encoding) экономит полосу пропускания, но требует осторожности с ответами, смешивающими управляемые данные с конфиденциальными (CRIME/BREACH). Ограничение скорости через 429 + Retry-After использует алгоритмы token bucket или sliding window; CDN-ограничение на краю — первая линия обороны.

Связанные уроки
Продолжить восхождение ↑HTTP/3 в продакшне: внутренности QUIC, fallback и наблюдаемость
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.