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
Ретраи включайте только для идемпотентных методов, иначе рискуете получить двойную запись. Для стриминга ретраи обычно выключают.

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% — по ним проще понять источник обрыва или таймаута.

Отладка типовых проблем
- 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 по требованиям. При необходимости используйте менеджмент‑плэйн с централизованной конфигурацией.
Чек‑лист перед продом
- Работают 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 и таймауты, а дальше подкручивайте производительность и отказоустойчивость под ваш трафик.


