OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

gRPC‑Web на практике: проксирование фронта к gRPC‑сервисам через Envoy

Если бэкенд говорит на gRPC, а браузер — нет, помогает gRPC‑Web и Envoy. Разбираем архитектуру, минимальный конфиг, CORS, таймауты и стриминг, TLS/HTTP2, ретраи и health‑check, логи и отладку. Пошаговые примеры и типовые ловушки, чтобы быстрого запуска в прод без сюрпризов.
gRPC‑Web на практике: проксирование фронта к gRPC‑сервисам через Envoy

gRPC — удобный RPC‑протокол для быстрой и типобезопасной связи сервисов. Но браузеры не умеют в родной gRPC поверх HTTP/2 с трейлерами. Для этого существует gRPC‑Web: тонкая надстройка, которая упаковывает запрос/ответ в HTTP, понятный фронтенду, и обратно. На практике самое удобное место для такой трансляции — прокси Envoy. Он принимает запросы с фронта (HTTP/1.1 или HTTP/2), выполняет CORS, таймауты, ретраи, логирование и отправляет чистый gRPC в backend по HTTP/2.

Архитектура и поток данных

Базовая схема:

  • Frontend обращается к Envoy по HTTP/1.1 или HTTP/2, используя клиентскую библиотеку gRPC‑Web.
  • В Envoy установлен HTTP‑фильтр grpc_web, который транскодирует gRPC‑Web в нативный gRPC, и обратно.
  • Далее Envoy проксирует трафик в backend‑кластер по HTTP/2, используя настройки балансировки, ретраев и health‑check.

Ключевое: downstream (браузер → Envoy) может быть HTTP/1.1, а upstream (Envoy → сервис) — обязательно HTTP/2 для полноценной поддержки gRPC.

Минимальный конфиг Envoy для gRPC‑Web

Ниже — рабочий скелет envoy.yaml для локального старта (порт 8080), с HTTP‑фильтрами gRPC‑Web и CORS, и проксированием к бэкенду на 50051.

static_resources:
  listeners:
  - name: listener_http
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          codec_type: AUTO
          access_log:
          - name: envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
              log_format:
                text_format: "[%START_TIME%] %DOWNSTREAM_REMOTE_ADDRESS% \"%REQ(:METHOD)% %REQ(:PATH)%\" %RESPONSE_CODE% grpc=%RESP(grpc-status)% %RESPONSE_FLAGS% %DURATION%ms\n"
          route_config:
            name: local_route
            virtual_hosts:
            - name: grpc_web_host
              domains: ["*"]
              routes:
              - match:
                  prefix: "/api"
                route:
                  cluster: backend_grpc
                  timeout: 0s
                typed_per_filter_config:
                  envoy.filters.http.cors:
                    "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.CorsPolicy
                    allow_origin_string_match:
                    - safe_regex:
                        google_re2: {}
                        regex: "^https://app.example.com$"
                    allow_methods: "POST, OPTIONS"
                    allow_headers: "content-type,x-grpc-web,x-user-agent,grpc-timeout,authorization"
                    expose_headers: "grpc-status,grpc-message"
                    allow_credentials: false
                    max_age: "86400"
          http_filters:
          - name: envoy.filters.http.grpc_web
          - name: envoy.filters.http.cors
          - name: envoy.filters.http.router

  clusters:
  - name: backend_grpc
    type: STRICT_DNS
    connect_timeout: 2s
    lb_policy: ROUND_ROBIN
    http2_protocol_options: {}
    load_assignment:
      cluster_name: backend_grpc
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 127.0.0.1
                port_value: 50051
    health_checks:
    - timeout: 1s
      interval: 5s
      unhealthy_threshold: 2
      healthy_threshold: 2
      grpc_health_check: {}

admin:
  address:
    socket_address:
      address: 127.0.0.1
      port_value: 9901

Комментарии к конфигу:

  • codec_type: AUTO позволяет приём и HTTP/1.1, и HTTP/2 от фронта.
  • http_filters: порядок важен — сначала grpc_web, потом cors, затем router.
  • timeout: 0s на маршруте отключает общий таймаут — полезно для длительных server‑streaming ответов. Для unary‑запросов лучше ставить явный таймаут.
  • http2_protocol_options: {} на кластере включает HTTP/2 к бэкенду, без него gRPC работать не будет.
  • В cors указываем точное происхождение, заголовки и методы. allow_credentials: false при звёздочке в Origin — обязательное ограничение стандарта; если нужны куки/авторизация — явно перечисляйте домены. Хорошая практическая памятка по заголовкам — «CORS: шпаргалка для Nginx и Apache»: как правильно выставлять CORS.

Запуск локально

docker run --rm -p 8080:8080 -p 9901:9901 -v $PWD/envoy.yaml:/etc/envoy/envoy.yaml envoyproxy/envoy:v1.31-latest

После старта Envoy доступен на 8080. Admin‑интерфейс — на 9901 (держите открытым только локально или за ACL).

Frontend: вызов gRPC‑Web

На фронте используем официальный клиент. Пример вызова unary‑метода:

import { EchoServiceClient } from "./generated/echo_grpc_web_pb";
import { EchoRequest } from "./generated/echo_pb";

const client = new EchoServiceClient("https://api.example.com");

const req = new EchoRequest();
req.setMessage("hello");

client.echo(req, {}, (err, resp) => {
  if (err) {
    console.error("gRPC-Web error", err.code, err.message);
    return;
  }
  console.log("response:", resp.getMessage());
});

Важно помнить специфику gRPC‑Web:

  • Браузер почти всегда получает HTTP‑код 200, а статус операции в grpc-status и grpc-message. Обрабатывайте ошибки на уровне клиента.
  • Поддерживается unary и server‑streaming. Client‑streaming и bidi‑streaming в gRPC‑Web недоступны.
  • Режимы сериализации: бинарный (application/grpc-web) и текстовый (grpc-web-text, base64). Предпочтителен бинарный для производительности.

CORS без боли

Чаще всего проблемы именно в CORS. Минимум, что нужно разрешить для gRPC‑Web:

  • POST и OPTIONS методы;
  • заголовки: content-type, x-grpc-web, x-user-agent, grpc-timeout, плюс ваши авторизационные (authorization или cookie);
  • в expose_headers вернуть grpc-status и grpc-message — иначе клиент не увидит статус.

Проверка preflight:

curl -i -X OPTIONS -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: content-type,x-grpc-web" http://localhost:8080/api/your.Service/Method

Таймауты, ретраи и стриминг

По умолчанию Envoy может иметь маршрутный таймаут (например, 15 секунд), что ломает долгие стримы. Для stream‑методов используйте timeout: 0s на маршруте или выносите их в отдельный префикс/вирт‑хост с особыми значениями. Для unary‑методов задавайте ограничение через grpc-timeout на клиенте и маршрутный timeout в Envoy, например 5–10 секунд.

route:
  cluster: backend_grpc
  timeout: 10s
  retry_policy:
    retry_on: 5xx,connect-failure,refused-stream,reset
    num_retries: 2
    per_retry_timeout: 2s

Ретраи включайте только для идемпотентных методов, иначе рискуете получить двойную запись. Для стриминга ретраи обычно выключают.

Фрагменты конфигурации Envoy и access‑логи для gRPC‑Web.

TLS и HTTP/2: нюансы

Для браузера чаще используется HTTPS, а к бэкенду — HTTP/2 с TLS или h2c (HTTP/2 без TLS) внутри приватной сети. Важные моменты:

  • Upstream должен уметь HTTP/2: в кластере включён http2_protocol_options.
  • При h2c укажите соответствующую схему подключения к upstream. Для TLS — настройте доверенный CA и SNI, если требуется. Подбор и выпуск сертификатов упростят SSL-сертификаты. Также может пригодиться путеводитель по HTTPS и HSTS: настройка HTTPS с HSTS.
  • На downstream можно оставить codec_type: AUTO, чтобы клиент поднимал HTTP/2 по ALPN при необходимости.

Ограничения размеров и буферизация

Большие сообщения могут упираться в лимиты либо на сервере gRPC, либо в Envoy. Для Envoy есть настройки HTTP‑менеджера и кластера, ограничивающие размер заголовков и тела. Если передаёте большие payload‑ы, проверьте:

  • лимиты на стороне фреймворка gRPC сервиса (max receive/send message size);
  • маршрутные таймауты и буферизацию в Envoy;
  • правила защиты upstream (WAF, прокси), которые могут резать бинарные данные.

Здоровье и балансировка

Envoy умеет выполнять gRPC health‑check через стандартный grpc.health.v1.Health. Это быстрее и точнее HTTP‑пинга. Для балансировки используйте ROUND_ROBIN и при необходимости включайте outlier detection, чтобы выкидывать сбойные инстансы.

outlier_detection:
  consecutive_5xx: 5
  interval: 10s
  base_ejection_time: 30s
  max_ejection_percent: 50

Логи и метрики

Для диагностики пригодится расширенный формат access‑логов с gRPC‑статусом, временем и флагами:

access_log:
- name: envoy.access_loggers.stdout
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
    log_format:
      text_format: "[%START_TIME%] %REQ(:METHOD)% %REQ(:PATH)% status=%RESPONSE_CODE% grpc=%RESP(grpc-status)% dur=%DURATION%ms bytes=%BYTES_SENT% flags=%RESPONSE_FLAGS%\n"

Следите за %RESPONSE_FLAGS% (например, UH, UF, DC) и %RESPONSE_CODE_DETAILS% — по ним проще понять источник обрыва или таймаута.

Поток: HTTP/1.1 → gRPC‑Web → Envoy → gRPC по HTTP/2.

Отладка типовых проблем

  • CORS: preflight не проходит. Проверьте, что OPTIONS попадает в маршрут и что фильтр CORS включён до роутера. Убедитесь, что allow_headers содержит все заголовки клиента.
  • На фронте всегда 200, но «не работает». Смотрите grpc-status и grpc-message в ответе, а не HTTP‑код.
  • Стриминг обрывается через N секунд. Проверяйте маршрутный timeout и stream_idle_timeout в HTTP‑менеджере/кластерных настройках. Для стримов поставьте timeout: 0s.
  • Периодические RST_STREAM. Это может быть ретрай по refused-stream или лимит на потоках upstream. Проверьте max_concurrent_streams на сервере и в http2_protocol_options.
  • Большие сообщения режутся. Увеличьте лимиты в сервисе и проверьте политики безопасности между Envoy и backend.

Полезные команды

# Проверка preflight
curl -i -X OPTIONS -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: content-type,x-grpc-web" http://localhost:8080/api/your.Service/Method

# Unary запрос в gRPC-Web (payload.bin содержит сериализованный protobuf)
curl -i -H "Content-Type: application/grpc-web" -H "X-Grpc-Web: 1" -H "Origin: https://app.example.com" --data-binary @payload.bin http://localhost:8080/api/your.Service/Method

# Проверка gRPC upstream напрямую (без Envoy)
grpcurl -d '{"message":"hello"}' -plaintext 127.0.0.1:50051 your.Service/Echo

Производительность и устойчивость

  • Keepalive: включите HTTP/2 keepalive к upstream, чтобы быстрее выявлять обрывы и избегать «залипших» соединений.
  • Пулы соединений: регулируйте конкаренси через max_requests_per_connection и max_concurrent_streams, чтобы не душить сервер.
  • Ретраи: ограничивайте количество и используйте только для безопасных операций. Настройте per_retry_timeout и джиттер задержек на стороне клиента.
  • Лимиты: добавьте circuit breakers, чтобы защищать backend при всплесках.
circuit_breakers:
  thresholds:
  - priority: DEFAULT
    max_connections: 1024
    max_pending_requests: 1024
    max_requests: 2048
    max_retries: 3

Безопасность

  • Всегда ограничивайте allow_origin конкретными доменами. Для мультидоменов используйте безопасные паттерны string match.
  • Если используете cookie‑аутентификацию, учитывайте SameSite и включайте allow_credentials с точным списком доменов.
  • Сегментируйте API‑префикс (например, /api) отдельным виртуальным хостом с жёсткими маршрутными правилами.
  • Логируйте и анонимизируйте чувствительные заголовки в access‑логах.

Деплой и инфраструктура

Envoy удобно размещать на отдельном инстансе рядом с фронтом или как edge‑прокси. Для гибкости и изоляции поднимайте его на VDS и держите конфигурацию в Git. Внешний TLS заверните на Envoy, а внутрь сети ведите h2c или mTLS по требованиям. При необходимости используйте менеджмент‑плэйн с централизованной конфигурацией.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Чек‑лист перед продом

  • Работают unary и streaming методы, время и статус корректны на фронте.
  • CORS проходит preflight, выставлены expose_headers для grpc-status/grpc-message.
  • Для stream‑маршрутов timeout: 0s или нужные увеличенные таймауты.
  • Upstream по HTTP/2, включён health‑check и балансировка.
  • Ретраи только для идемпотентных методов, ограничены по времени и количеству.
  • Достаточные лимиты размеров сообщений, настроены circuit breakers.
  • Логи содержат gRPC‑статус, флаги и время, алёрты повешены на ошибки/дедлайны.
  • TLS настроен на внешнем фронте, сертификаты и цепочки валидны.

Итоги

Envoy снимает ключевые барьеры между браузерным frontend и gRPC backend, предоставляя готовый мост gRPC‑Web, CORS, балансировку и наблюдаемость. С правильными таймаутами, ретраями и health‑check получается надёжный слой, который прозрачно транскодирует трафик, масштабируется вместе с сервисами и не требует костылей на фронте. Начните с минимального конфига, явно задайте CORS и таймауты, а дальше подкручивайте производительность и отказоустойчивость под ваш трафик.

Поделиться статьей

Вам будет интересно

Grafana Agent Flow на VDS: единый агент для metrics, logs и traces (Prometheus, Loki, Tempo, OTLP) OpenAI Статья написана AI (GPT 5)

Grafana Agent Flow на VDS: единый агент для metrics, logs и traces (Prometheus, Loki, Tempo, OTLP)

Grafana Agent в режиме Flow — лёгкий агент на одном VDS для метрик, логов и трейсов с отправкой в Prometheus/VictoriaMetrics, Loki ...
systemd-nspawn на VDS: лёгкие контейнеры, изоляция и сеть без Kubernetes OpenAI Статья написана AI (GPT 5)

systemd-nspawn на VDS: лёгкие контейнеры, изоляция и сеть без Kubernetes

Как запустить и подружить systemd-nspawn с вашим VDS: развертывание контейнеров, изоляция, bind mounts, сеть и cgroup-лимиты, упра ...
Node.js keepalive и http.Agent: практическая настройка с Nginx и upstream-пулами OpenAI Статья написана AI (GPT 5)

Node.js keepalive и http.Agent: практическая настройка с Nginx и upstream-пулами

Разбираем пул http.Agent в Node.js и практику keepalive: какие параметры важны (maxSockets, freeSocketTimeout, socketActiveTTL), к ...