Skip to content

Commit

Permalink
extensions: add grpc_http1_reverse_bridge filter (envoyproxy#5315)
Browse files Browse the repository at this point in the history
Adds a filter that allows converting a gRPC request into an HTTP/1.1
request with a custom content-type. Allows a vanilla HTTP/1.1 upstream
to handle incoming requests by reading and responding with protobuf
messages in binary octet format.

For now this shields the upstream from any gRPC association: the filter
removes the gRPC specific message prefix and manages the conversion of
the HTTP status code into grpc-status.

Signed-off-by: Snow Pettersen <[email protected]>
Signed-off-by: Fred Douglas <[email protected]>
  • Loading branch information
snowp authored and fredlas committed Mar 5, 2019
1 parent 04764cf commit e3ef155
Show file tree
Hide file tree
Showing 18 changed files with 1,056 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
/*/extensions/filters/network/thrift_proxy @zuercher @brian-pane
# jwt_authn http filter extension
/*/extensions/filters/http/jwt_authn @qiwzhang @lizan
# grpc_http1_reverse_bridge http filter extension
/*/extensions/filters/http/grpc_http1_reverse_bridge @snowp
# header_to_metadata extension
/*/extensions/filters/http/header_to_metadata @rgs1 @zuercher
# alts transport socket extension
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
load("//bazel:api_build_system.bzl", "api_proto_library")

licenses(["notice"]) # Apache 2

api_proto_library(
name = "config",
srcs = ["config.proto"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
syntax = "proto3";

package envoy.extensions.filter.http.grpc_http1_reverse_bridge.v2alpha1;
option java_package = "io.envoyproxy.envoy.extensions.filter.http.grpc_http1_reverse_bridge.v2alpha1";
option java_multiple_files = true;
option go_package = "v2";

import "validate/validate.proto";

// [#protodoc-title: Extensions gRPC Http1 Reverse Bridge]
// gRPC reverse bridge filter configuration
message FilterConfig {
// The content-type to pass to the upstream when the gRPC bridge filter is applied.
// The filter will also validate that the upstream responds with the same content type.
string content_type = 1 [(validate.rules).string.min_bytes = 1];

// If true, Envoy will assume that the upstream doesn't understand gRPC frames and
// strip the gRPC frame from the request, and add it back in to the response. This will
// hide the gRPC semantics from the upstream, allowing it to receive and respond with a
// simple binary encoded protobuf.
bool withhold_grpc_frames = 2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.. _config_http_filters_grpc_reverse_bridge:

gRPC HTTP/1.1 reverse bridge
============================

* gRPC :ref:`architecture overview <arch_overview_grpc>`
* :ref:`v2 API reference <envoy_api_field_config.filter.network.http_connection_manager.v2.HttpFilter.name>`
* This filter should be configured with the name *envoy.grpc_http1_reverse_bridge*.

This is a filter that enables converting an incoming gRPC request into a HTTP/1.1 request to allow
a server that does not understand HTTP/2 or gRPC semantics to handle the request.

The filter works by:

* Checking the content type of the incoming request. If it's a gRPC request, the filter is enabled.
* The content type is modified to a configurable value. This can be a noop by configuring
``application/grpc``.
* The gRPC frame header is optionally stripped from the request body. The content length header
will be adjusted if so.
* On receiving a response, the content type of the response is validated and the status code is
mapped to a grpc-status which is inserted into the response trailers.
* The response body is optionally prefixed by the gRPC frame header, again adjusting the content
length header if necessary.

Due to being mapped to HTTP/1.1, this filter will only work with unary gRPC calls.

gRPC frame header management
----------------------------

By setting the withhold_grpc_frame option, the filter will assume that the upstream does not
understand any gRPC semantics and will convert the request body into a simple binary encoding
of the request body and perform the reverse conversion on the response body. This ends up
simplifying the server side handling of these requests, as they no longer need to be concerned
with parsing and generating gRPC formatted data.

This works by stripping the gRPC frame header from the request body, while injecting a gRPC
frame header in the response.

If this feature is not used, the upstream must be ready to receive HTTP/1.1 requests prefixed
with the gRPC frame header and respond with gRPC formatted responses.
1 change: 1 addition & 0 deletions docs/root/configuration/http_filters/http_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ HTTP filters
ext_authz_filter
fault_filter
grpc_http1_bridge_filter
grpc_http1_reverse_bridge_filter
grpc_json_transcoder_filter
grpc_web_filter
gzip_filter
Expand Down
22 changes: 16 additions & 6 deletions docs/root/intro/arch_overview/grpc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@ application layer:
* gRPC makes use of HTTP/2 trailers to convey request status. Envoy is one of very few HTTP proxies
that correctly supports HTTP/2 trailers and is thus one of the few proxies that can transport
gRPC requests and responses.
* The gRPC runtime for some languages is relatively immature. Envoy supports a gRPC :ref:`bridge
filter <config_http_filters_grpc_bridge>` that allows gRPC requests to be sent to Envoy over
HTTP/1.1. Envoy then translates the requests to HTTP/2 for transport to the target server.
The response is translated back to HTTP/1.1.
* When installed, the bridge filter gathers per RPC statistics in addition to the standard array
of global HTTP statistics.
* The gRPC runtime for some languages is relatively immature. See :ref:`below <arch_overview_grpc_bridging>`
for an overview of filters that can help bring gRPC to more languages.
* gRPC-Web is supported by a :ref:`filter <config_http_filters_grpc_web>` that allows a gRPC-Web
client to send requests to Envoy over HTTP/1.1 and get proxied to a gRPC server. It's under
active development and is expected to be the successor to the gRPC :ref:`bridge filter
Expand All @@ -25,6 +21,20 @@ application layer:
that allows a RESTful JSON API client to send requests to Envoy over HTTP and get proxied to a
gRPC service.

.. _arch_overview_grpc_bridging:

gRPC bridging
-------------

Envoy supports two gRPC bridges:

* :ref:`grpc_http1_bridge filter <config_http_filters_grpc_bridge>` which allows gRPC requests to be sent to Envoy over
HTTP/1.1. Envoy then translates the requests to HTTP/2 for transport to the target server. The response is translated back to HTTP/1.1.
When installed, the bridge filter gathers per RPC statistics in addition to the standard array of global HTTP statistics.
* :ref:`grpc_http1_reverse_bridge filter <config_http_filters_grpc_reverse_bridge>` which allows gRPC requests to be sent to Envoy and
then translated to HTTP/1.1 when sent to the upstream. The response is then converted back into gRPC when sent to the downstream.
This filter can also optionally manage the gRPC frame header, allowing the upstream to not have to be gRPC aware at all.

.. _arch_overview_grpc_services:

gRPC services
Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Version history
* access log: added a new flag for upstream retry count exceeded.
* buffer: fix vulnerabilities when allocation fails
* config: removed deprecated_v1 sds_config from :ref:`Bootstrap config <config_overview_v2_bootstrap>`.
* http: added new grpc_http1_reverse_bridge filter for converting gRPC requests into HTTP/1.1 requests.
* tls: enabled TLS 1.3 on the server-side (non-FIPS builds).

1.9.0
Expand Down
2 changes: 2 additions & 0 deletions source/common/grpc/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const uint8_t GRPC_FH_DEFAULT = 0b0u;
// Last bit for a compressed message.
const uint8_t GRPC_FH_COMPRESSED = 0b1u;

constexpr uint64_t GRPC_FRAME_HEADER_SIZE = sizeof(uint8_t) + sizeof(uint32_t);

enum class CompressionAlgorithm { None, Gzip };

struct Frame {
Expand Down
1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ EXTENSIONS = {
"envoy.filters.http.grpc_http1_bridge": "//source/extensions/filters/http/grpc_http1_bridge:config",
"envoy.filters.http.grpc_json_transcoder": "//source/extensions/filters/http/grpc_json_transcoder:config",
"envoy.filters.http.grpc_web": "//source/extensions/filters/http/grpc_web:config",
"envoy.filters.http.grpc_http1_reverse_bridge": "//source/extensions/filters/http/grpc_http1_reverse_bridge:config",
"envoy.filters.http.gzip": "//source/extensions/filters/http/gzip:config",
"envoy.filters.http.header_to_metadata": "//source/extensions/filters/http/header_to_metadata:config",
"envoy.filters.http.health_check": "//source/extensions/filters/http/health_check:config",
Expand Down
40 changes: 40 additions & 0 deletions source/extensions/filters/http/grpc_http1_reverse_bridge/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_library",
"envoy_package",
)

envoy_package()

envoy_cc_library(
name = "filter_lib",
srcs = ["filter.cc"],
hdrs = ["filter.h"],
deps = [
"//include/envoy/http:filter_interface",
"//source/common/common:enum_to_int",
"//source/common/grpc:codec_lib",
"//source/common/grpc:common_lib",
"//source/common/grpc:status_lib",
"//source/common/http:header_map_lib",
"//source/extensions/filters/http:well_known_names",
"//source/extensions/filters/http/common:pass_through_filter_lib",
],
)

envoy_cc_library(
name = "config",
srcs = ["config.cc"],
hdrs = ["config.h"],
deps = [
":filter_lib",
"//include/envoy/http:filter_interface",
"//include/envoy/registry",
"//include/envoy/server:filter_config_interface",
"//source/extensions/filters/http:well_known_names",
"//source/extensions/filters/http/common:factory_base_lib",
"@envoy_api//envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1:config_cc",
],
)
30 changes: 30 additions & 0 deletions source/extensions/filters/http/grpc_http1_reverse_bridge/config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include "extensions/filters/http/grpc_http1_reverse_bridge/config.h"

#include "envoy/registry/registry.h"

#include "extensions/filters/http/grpc_http1_reverse_bridge/filter.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace GrpcHttp1ReverseBridge {

Http::FilterFactoryCb Config::createFilterFactoryFromProtoTyped(
const envoy::extensions::filter::http::grpc_http1_reverse_bridge::v2alpha1::FilterConfig&
config,
const std::string&, Server::Configuration::FactoryContext&) {
return [config](Envoy::Http::FilterChainFactoryCallbacks& callbacks) -> void {
callbacks.addStreamFilter(
std::make_unique<Filter>(config.content_type(), config.withhold_grpc_frames()));
};
}

/**
* Static registration for the grpc http1 reverse bridge filter. @see RegisterFactory.
*/
static Envoy::Registry::RegisterFactory<Config, Server::Configuration::NamedHttpFilterConfigFactory>
register_;
} // namespace GrpcHttp1ReverseBridge
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
29 changes: 29 additions & 0 deletions source/extensions/filters/http/grpc_http1_reverse_bridge/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include "envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/config.pb.validate.h"
#include "envoy/server/filter_config.h"

#include "extensions/filters/http/common/factory_base.h"
#include "extensions/filters/http/well_known_names.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace GrpcHttp1ReverseBridge {

class Config
: public Common::FactoryBase<
envoy::extensions::filter::http::grpc_http1_reverse_bridge::v2alpha1::FilterConfig> {
public:
Config() : FactoryBase(HttpFilterNames::get().GrpcHttp1ReverseBridge) {}

Http::FilterFactoryCb createFilterFactoryFromProtoTyped(
const envoy::extensions::filter::http::grpc_http1_reverse_bridge::v2alpha1::FilterConfig&
config,
const std::string& stat_prefix,
Envoy::Server::Configuration::FactoryContext& context) override;
};
} // namespace GrpcHttp1ReverseBridge
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
Loading

0 comments on commit e3ef155

Please sign in to comment.