1. 概述
Envoy 的 gRPC HTTP/1.1 桥接功能为 gRPC 和 HTTP/1.1 协议之间的双向转换提供了强大而灵活的解决方案。它允许使用标准的 HTTP/1.1 协议访问 gRPC 服务,同时保持了 API 的完整性和性能。
该功能的主要特点包括:
-
支持所有 gRPC 方法类型(一元、流式等)
-
提供了完整的错误处理和状态码转换
-
与 Envoy 的架构深度集成,性能优异
-
支持灵活的配置选项,包括路由级配置
-
与其他 Envoy 功能配合良好,如认证、限流和路由
通过合理配置 gRPC HTTP/1.1 桥接过滤器,您可以快速将现有的 gRPC 服务暴露为 HTTP/1.1 API,或者将 HTTP/1.1 客户端与 gRPC 服务集成,提高了开发效率和系统的可维护性。
2. 解决的问题
gRPC HTTP/1.1 桥接功能主要解决以下问题:
2.1 API 兼容性
-
允许使用 HTTP/1.1 协议访问 gRPC 服务,无需修改后端代码
-
支持将 gRPC 请求转换为 HTTP/1.1 请求,反之亦然
-
提供了与现有 HTTP 客户端和工具的兼容性
2.2 开发效率
-
简化了系统集成,允许使用标准的 HTTP/1.1 客户端访问 gRPC 服务
-
减少了编写和维护 gRPC 到 HTTP/1.1 适配器的需求
-
提供了对 Protobuf 格式请求的自动处理
2.3 性能优化
-
直接在 Envoy 层面进行协议转换,避免了额外的网络开销
-
支持流式处理和双向通信
-
提供了高效的序列化和反序列化机制
2.4 功能完整性
-
支持所有 gRPC 方法类型,包括一元、服务器流式、客户端流式和双向流式
-
自动处理 gRPC 状态码和消息的转换
-
提供了与其他 Envoy 功能的集成支持
3. 架构设计
3.1 核心组件架构

3.2 类图

4. 配置用例
4.1 基础配置
css
http_filters:- name: envoy.filters.http.grpc_http1_bridge typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config upgrade_protobuf_to_grpc: true
route_config: name: local_route virtual_hosts: - name: backend domains: ["*"] routes: - match: { prefix: "/" } route: { cluster: grpc_backend }
static_resources: clusters: - name: grpc_backend connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: grpc_backend endpoints: - lb_endpoints: - endpoint: address: socket_address: address: grpc-backend.example.com port_value: 50051
4.2 高级配置
bash
http_filters:- name: envoy.filters.http.grpc_http1_bridge typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config upgrade_protobuf_to_grpc: true
static_resources: listeners: - name: listener_0 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 http_filters: - name: envoy.filters.http.grpc_http1_bridge typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config upgrade_protobuf_to_grpc: true - name: envoy.filters.http.router route_config: name: local_route virtual_hosts: - name: backend domains: ["*"] routes: - match: { prefix: "/" } route: { cluster: grpc_backend } clusters: - name: grpc_backend connect_timeout: 0.25s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: grpc_backend endpoints: - lb_endpoints: - endpoint: address: socket_address: address: grpc-backend.example.com port_value: 50051
4.3 路由级配置
bash
route_config: name: local_route virtual_hosts: - name: backend domains: ["*"] routes: - match: prefix: "/api" route: cluster: grpc_backend per_filter_config: envoy.filters.http.grpc_http1_bridge: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config upgrade_protobuf_to_grpc: true
4.4 反向桥接配置
css
http_filters:- name: envoy.filters.http.grpc_http1_reverse_bridge typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_http1_reverse_bridge.v3.Config upstream_content_type: "application/json" withhold_grpc_frames: true response_size_header: "Content-Length"
route_config: name: local_route virtual_hosts: - name: backend domains: ["*"] routes: - match: { prefix: "/" } route: { cluster: http_backend }
5. 工作流程分析
5.1 过滤器执行流程

5.2 请求转换流程

6. 代码实现ER图

7. 最佳实践
7.1 与其他过滤器配合使用
bash
http_filters:- name: envoy.filters.http.jwt_authn typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication providers: # 认证配置- name: envoy.filters.http.grpc_http1_bridge typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config upgrade_protobuf_to_grpc: true- name: envoy.filters.http.router
7.2 性能优化
bash
http_filters:- name: envoy.filters.http.grpc_http1_bridge typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config upgrade_protobuf_to_grpc: true # 其他性能优化配置
7.3 错误处理
bash
http_filters:- name: envoy.filters.http.grpc_http1_bridge typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config upgrade_protobuf_to_grpc: true- name: envoy.filters.http.fault typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault # 错误注入配置- name: envoy.filters.http.router
7.4 复杂路由配置
python
route_config: name: local_route virtual_hosts: - name: backend domains: ["*"] routes: - match: prefix: "/api/v1/grpc" route: cluster: grpc_backend per_filter_config: envoy.filters.http.grpc_http1_bridge: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config upgrade_protobuf_to_grpc: true - match: prefix: "/api/v1/http" route: { cluster: http_backend }
8. 代码实现细节
8.1 过滤器初始化
php
Http1BridgeFilter::Http1BridgeFilter( Grpc::Context& context, const envoy::extensions::filters::http::grpc_http1_bridge::v3::Config& proto_config) : context_(context), upgrade_protobuf_(proto_config.upgrade_protobuf_to_grpc()) {}
8.2 请求处理
ruby
Http::FilterHeadersStatus Http1BridgeFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool) { const bool protobuf_request = Grpc::Common::isProtobufRequestHeaders(headers); if (upgrade_protobuf_ && protobuf_request) { do_framing_ = true; headers.setContentType(Http::Headers::get().ContentTypeValues.Grpc); headers.setReferenceContentType(Http::Headers::get().ContentTypeValues.Grpc); headers.removeContentLength(); // message length part of the gRPC frame decoder_callbacks_->clearRouteCache(); }
const bool grpc_request = Grpc::Common::isGrpcRequestHeaders(headers);
const absl::optional<Http::Protocol>& protocol = decoder_callbacks_->streamInfo().protocol(); ASSERT(protocol); if (protocol.value() < Http::Protocol::Http2 && grpc_request) { do_bridging_ = true; }
return Http::FilterHeadersStatus::Continue;}
Http::FilterDataStatus Http1BridgeFilter::decodeData(Buffer::Instance& data, bool end_stream) { if (!do_bridging_ || !do_framing_) { return Http::FilterDataStatus::Continue; }
decoder_callbacks_->addDecodedData(data, true); if (end_stream && do_framing_) { decoder_callbacks_->modifyDecodingBuffer( [](Buffer::Instance& buf) { Grpc::Common::prependGrpcFrameHeader(buf); }); return Http::FilterDataStatus::Continue; }
return Http::FilterDataStatus::StopIterationAndBuffer;}
8.3 响应处理
php
Http::FilterHeadersStatus Http1BridgeFilter::encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) {
if (!do_bridging_ || end_stream) { return Http::FilterHeadersStatus::Continue; } else { response_headers_ = &headers; return Http::FilterHeadersStatus::StopIteration; }}
Http::FilterDataStatus Http1BridgeFilter::encodeData(Buffer::Instance& data, bool end_stream) { if (!do_bridging_ || end_stream) { return Http::FilterDataStatus::Continue; } else { // Buffer until the complete request has been processed. if (do_framing_) { data.drain(Grpc::GRPC_FRAME_HEADER_SIZE); } return Http::FilterDataStatus::StopIterationAndBuffer; }}
Http::FilterTrailersStatus Http1BridgeFilter::encodeTrailers(Http::ResponseTrailerMap& trailers) {
if (do_bridging_) { // Here we check for grpc-status. If it's not zero, we change the response code. We assume // that if a reset comes in and we disconnect the HTTP/1.1 client it will raise some type // of exception/error that the response was not complete. const Http::HeaderEntry* grpc_status_header = trailers.GrpcStatus(); if (grpc_status_header) { uint64_t grpc_status_code; if (!absl::SimpleAtoi(grpc_status_header->value().getStringView(), &grpc_status_code) || grpc_status_code != 0) { response_headers_->setStatus(enumToInt(Http::Code::ServiceUnavailable)); } response_headers_->setGrpcStatus(grpc_status_header->value().getStringView()); }
const Http::HeaderEntry* grpc_message_header = trailers.GrpcMessage(); if (grpc_message_header) { response_headers_->setGrpcMessage(grpc_message_header->value().getStringView()); }
// Since we are buffering, set content-length so that HTTP/1.1 callers can better determine // if this is a complete response. response_headers_->setContentLength( encoder_callbacks_->encodingBuffer() ? encoder_callbacks_->encodingBuffer()->length() : 0); }
// NOTE: We will still write the trailers, but the HTTP/1.1 codec will just eat them and end // the chunk encoded response which is what we want. return Http::FilterTrailersStatus::Continue;}