Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b137f32
Fix heap-use-after-free error when shutting down envoy.
fengli79 Apr 12, 2017
312dd58
Merge remote-tracking branch 'upstream/master'
fengli79 Apr 13, 2017
d0cec57
Change HotRestart::shutdownAdmin() to HotRestart::shutdown().
fengli79 Apr 13, 2017
ab3855f
Local change for thirdparty.cmake, DO NOT commit to upstream!
fengli79 Apr 25, 2017
489470a
Merge remote-tracking branch 'upstream/master'
fengli79 Apr 25, 2017
8ecec66
Add grpc-web support for envoy.
fengli79 May 10, 2017
8ef4b1c
Merge remote-tracking branch 'upstream/master'
fengli79 May 10, 2017
2362192
Merge remote-tracking branch 'upstream/master'
fengli79 May 10, 2017
4c9a291
Sync with upstream.
fengli79 May 10, 2017
43297c3
Fix the trailer encoding of grpc-web.
fengli79 May 10, 2017
2f20d0d
Modify bazel BUILD files to add grpc-web module.
fengli79 May 11, 2017
b4f942e
Merge remote-tracking branch 'upstream/master'
fengli79 May 11, 2017
7a205c9
Change to use the Http::Headers singleton for gRPC-Web relative headers.
fengli79 May 11, 2017
9d6b6d8
Merge remote-tracking branch 'upstream/master'
fengli79 May 16, 2017
8c958b4
Merge remote-tracking branch 'upstream/master'
fengli79 May 16, 2017
6acc12f
Merge remote-tracking branch 'upstream/master'
fengli79 May 17, 2017
1394d32
Revert source/common/http/conn_manager_impl.cc.
fengli79 May 17, 2017
ffee1d4
Change to use addDecodedData to encode trailers to response body.
fengli79 May 17, 2017
7db18bb
Merge remote-tracking branch 'upstream/master'
fengli79 May 17, 2017
2d195c3
Symbolize stack trace for tests.
fengli79 May 18, 2017
539dcf5
Add unit tests for grpc-web filter.
fengli79 May 18, 2017
7471364
Merge remote-tracking branch 'origin/symbolize_stacktrace'
fengli79 May 18, 2017
6bf1e12
Add unit tests for grpc-web filter.
fengli79 May 18, 2017
e2a5d4e
Merge remote-tracking branch 'upstream/master'
fengli79 May 18, 2017
25b9c37
fix format.
fengli79 May 18, 2017
f6bca59
Update unit tests.
fengli79 May 18, 2017
475a35c
Update unit tests.
fengli79 May 19, 2017
8e848f6
Address review comments.
fengli79 May 19, 2017
4950429
Address review comments.
fengli79 May 19, 2017
92b511d
Add document for grpc-web.
fengli79 May 19, 2017
69b34b0
Add gRPC-Web in Envoy intro document.
fengli79 May 19, 2017
02fb75a
Update grpc.rst
mattklein123 May 19, 2017
7251c10
Address comments.
fengli79 May 22, 2017
a1b6643
Fix format.
fengli79 May 22, 2017
15244b4
Address comments.
fengli79 May 23, 2017
88e5c63
Address comments.
fengli79 May 23, 2017
0f93298
Address comments.
fengli79 May 23, 2017
275cb7e
Merge branch 'master' into master
fengli79 May 24, 2017
6dc3008
Change to use NamedHttpFilterConfigFactory to register gRPC-Web filter.
fengli79 May 24, 2017
ddb90fd
Remove the unused capture of server variable.
fengli79 May 24, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/configuration/http_filters/grpc_web_filter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.. _config_http_filters_grpc_web:

gRPC-Web filter
====================

gRPC :ref:`architecture overview <arch_overview_grpc>`.

This is a filter which enables the bridging of a gRPC-Web client to a compliant gRPC server by
following https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md.

.. code-block:: json

{
"type": "both",
"name": "grpc_web",
"config": {}
}
1 change: 1 addition & 0 deletions docs/configuration/http_filters/http_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ HTTP filters
fault_filter
dynamodb_filter
grpc_http1_bridge_filter
grpc_web_filter
health_check_filter
rate_limit_filter
router_filter
4 changes: 4 additions & 0 deletions docs/intro/arch_overview/grpc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ application layer:
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.
* 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
<config_http_filters_grpc_bridge>`.
2 changes: 2 additions & 0 deletions include/envoy/http/header_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ class HeaderEntry {
HEADER_FUNC(Expect) \
HEADER_FUNC(ForwardedFor) \
HEADER_FUNC(ForwardedProto) \
HEADER_FUNC(GrpcAcceptEncoding) \
HEADER_FUNC(GrpcMessage) \
HEADER_FUNC(GrpcStatus) \
HEADER_FUNC(Host) \
Expand All @@ -226,6 +227,7 @@ class HeaderEntry {
HEADER_FUNC(Scheme) \
HEADER_FUNC(Server) \
HEADER_FUNC(Status) \
HEADER_FUNC(TE) \
HEADER_FUNC(TransferEncoding) \
HEADER_FUNC(Upgrade) \
HEADER_FUNC(UserAgent) \
Expand Down
13 changes: 13 additions & 0 deletions source/common/grpc/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "grpc_web_filter_lib",
srcs = ["grpc_web_filter.cc"],
hdrs = ["grpc_web_filter.h"],
deps = [
":codec_lib",
":common_lib",
"//include/envoy/http:filter_interface",
"//source/common/common:base64_lib",
"//source/common/http:headers_lib",
],
)

envoy_cc_library(
name = "rpc_channel_lib",
srcs = ["rpc_channel_impl.cc"],
Expand Down
127 changes: 127 additions & 0 deletions source/common/grpc/grpc_web_filter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include "common/grpc/grpc_web_filter.h"

#include <arpa/inet.h>

#include "common/common/base64.h"
#include "common/http/headers.h"

namespace Envoy {
namespace Grpc {

// Bit mask denotes a trailers frame of gRPC-Web.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: uncompressed trailer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to add another bit mask 0b00000001 for compression. So it's not necessary to mention the compression here once it gets in.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, works for me as long as it's WIP in here.

const uint8_t GrpcWebFilter::GRPC_WEB_TRAILER = 0b10000000;

// Implements StreamDecoderFilter.
// TODO(fengli): Implements the subtypes of gRPC-Web content-type, like +proto, etc.
Http::FilterHeadersStatus GrpcWebFilter::decodeHeaders(Http::HeaderMap& headers, bool) {
const Http::HeaderEntry* content_type = headers.ContentType();
if (content_type != nullptr &&
Http::Headers::get().ContentTypeValues.GrpcWebText == content_type->value().c_str()) {
// Checks whether gRPC-Web client is sending base64 encoded request.
is_text_request_ = true;
}
headers.insertContentType().value(Http::Headers::get().ContentTypeValues.Grpc);

const Http::HeaderEntry* accept = headers.get(Http::Headers::get().Accept);
if (accept != nullptr &&
Http::Headers::get().ContentTypeValues.GrpcWebText == accept->value().c_str()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment on grpc-web-text prefix.

// Checks whether gRPC-Web client is asking for base64 encoded response.
is_text_response_ = true;
}

// Adds te:trailers to upstream HTTP2 request. It's required for gRPC.
headers.addStatic(Http::Headers::get().TE, Http::Headers::get().TEValues.Trailers);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these addStatic() calls can use the O(1) insert varieties since you added the headers to the O(1) map. This would yield better performance. @fengli79 not urgent but I would do a follow up or add to a future grpc-web PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address in a follow up PR. Also, there are other stuffs need to be enhanced, like add some wrapper methods in Buffer::OwnedImpl to avoid the additional buffer allocation. Today I have to use a temp buffer when processing trailers, as the Buffer::OwnedImpl doesn't allow me to insert data to a particular position.

// Adds grpc-accept-encoding:identity,deflate,gzip. It's required for gRPC.
headers.addStatic(Http::Headers::get().GrpcAcceptEncoding,
Http::Headers::get().GrpcAcceptEncodingValues.Default);
return Http::FilterHeadersStatus::Continue;
}

Http::FilterDataStatus GrpcWebFilter::decodeData(Buffer::Instance& data, bool) {
if (!is_text_request_) {
// No additional transcoding required if gRPC client is sending binary request.
return Http::FilterDataStatus::Continue;
}

// Parse application/grpc-web-text format.
if (data.length() + decoding_buffer_.length() < 4) {
decoding_buffer_.move(data);
return Http::FilterDataStatus::StopIterationNoBuffer;
}

const uint64_t needed =
(data.length() + decoding_buffer_.length()) / 4 * 4 - decoding_buffer_.length();
decoding_buffer_.move(data, needed);
const std::string decoded = Base64::decode(
std::string(static_cast<const char*>(decoding_buffer_.linearize(decoding_buffer_.length())),
decoding_buffer_.length()));
decoding_buffer_.drain(decoding_buffer_.length());
decoding_buffer_.move(data);
data.add(decoded);
return Http::FilterDataStatus::Continue;
}

// Implements StreamEncoderFilter.
Http::FilterHeadersStatus GrpcWebFilter::encodeHeaders(Http::HeaderMap& headers, bool) {
if (is_text_response_) {
headers.insertContentType().value(Http::Headers::get().ContentTypeValues.GrpcWebText);
} else {
headers.insertContentType().value(Http::Headers::get().ContentTypeValues.GrpcWeb);
}
return Http::FilterHeadersStatus::Continue;
}

Http::FilterDataStatus GrpcWebFilter::encodeData(Buffer::Instance& data, bool) {
if (!is_text_response_) {
// No additional transcoding required if gRPC-Web client asked for binary response.
return Http::FilterDataStatus::Continue;
}

// The decoder always consumes and drains the given buffer. Incomplete data frame is buffered
// inside the decoder.
std::vector<Frame> frames;
decoder_.decode(data, frames);
if (frames.empty()) {
// We don't have enough data to decode for one single frame, stop iteration until more data
// comes in.
return Http::FilterDataStatus::StopIterationNoBuffer;
}

// Encodes the decoded gRPC frames with base64.
for (auto& frame : frames) {
Buffer::OwnedImpl temp;
temp.add(&frame.flags_, 1);
const uint32_t length = htonl(frame.length_);
temp.add(&length, 4);
if (frame.length_ > 0) {
temp.add(*frame.data_);
}
data.add(Base64::encode(temp, temp.length()));
}
return Http::FilterDataStatus::Continue;
}

Http::FilterTrailersStatus GrpcWebFilter::encodeTrailers(Http::HeaderMap& trailers) {
// Trailers are expected to come all in once, and will be encoded into one single trailers frame.
// Trailers in the trailers frame are separated by CRLFs.
Buffer::OwnedImpl temp;
trailers.iterate([](const Http::HeaderEntry& header, void* context) -> void {
Buffer::Instance* temp = static_cast<Buffer::Instance*>(context);
temp->add(header.key().c_str(), header.key().size());
temp->add(":");
temp->add(header.value().c_str(), header.value().size());
temp->add("\r\n");
}, &temp);
Buffer::OwnedImpl buffer;
// Adds the trailers frame head.
buffer.add(&GRPC_WEB_TRAILER, 1);
// Adds the trailers frame length.
const uint32_t length = htonl(temp.length());
buffer.add(&length, 4);
buffer.move(temp);
encoder_callbacks_->addEncodedData(buffer);
return Http::FilterTrailersStatus::Continue;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: new line after this line and before bracket closing the namespace Grpc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


} // namespace Grpc
} // namespace Envoy
48 changes: 48 additions & 0 deletions source/common/grpc/grpc_web_filter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once

#include "envoy/http/filter.h"

#include "common/buffer/buffer_impl.h"
#include "common/common/non_copyable.h"
#include "common/grpc/codec.h"

namespace Envoy {
namespace Grpc {

/**
* See docs/configuration/http_filters/grpc_web_filter.rst
*/
class GrpcWebFilter : public Http::StreamFilter, NonCopyable {
public:
GrpcWebFilter(){};
virtual ~GrpcWebFilter(){};

void onDestroy() override{};

// Implements StreamDecoderFilter.
Http::FilterHeadersStatus decodeHeaders(Http::HeaderMap&, bool) override;
Http::FilterDataStatus decodeData(Buffer::Instance&, bool) override;
Http::FilterTrailersStatus decodeTrailers(Http::HeaderMap&) override {
return Http::FilterTrailersStatus::Continue;
}
void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks&) override {}

// Implements StreamEncoderFilter.
Http::FilterHeadersStatus encodeHeaders(Http::HeaderMap&, bool) override;
Http::FilterDataStatus encodeData(Buffer::Instance&, bool) override;
Http::FilterTrailersStatus encodeTrailers(Http::HeaderMap& trailers) override;
void setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks& callbacks) override {
encoder_callbacks_ = &callbacks;
}

private:
static const uint8_t GRPC_WEB_TRAILER;
Http::StreamEncoderFilterCallbacks* encoder_callbacks_{};
bool is_text_request_{};
bool is_text_response_{};
Buffer::OwnedImpl decoding_buffer_;
Decoder decoder_;
};

} // namespace Grpc
} // namespace Envoy
13 changes: 13 additions & 0 deletions source/common/http/headers.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class HeaderValues {
const LowerCaseString ForwardedProto{"x-forwarded-proto"};
const LowerCaseString GrpcMessage{"grpc-message"};
const LowerCaseString GrpcStatus{"grpc-status"};
const LowerCaseString GrpcAcceptEncoding{"grpc-accept-encoding"};
const LowerCaseString Host{":authority"};
const LowerCaseString HostLegacy{"host"};
const LowerCaseString KeepAlive{"keep-alive"};
Expand All @@ -57,6 +58,7 @@ class HeaderValues {
const LowerCaseString Server{"server"};
const LowerCaseString Status{":status"};
const LowerCaseString TransferEncoding{"transfer-encoding"};
const LowerCaseString TE{"te"};
const LowerCaseString Upgrade{"upgrade"};
const LowerCaseString UserAgent{"user-agent"};
const LowerCaseString XB3TraceId{"x-b3-traceid"};
Expand All @@ -71,6 +73,9 @@ class HeaderValues {

struct {
const std::string Text{"text/plain"};
const std::string Grpc{"application/grpc"};
const std::string GrpcWeb{"application/grpc-web"};
const std::string GrpcWebText{"application/grpc-web-text"};
} ContentTypeValues;

struct {
Expand Down Expand Up @@ -106,6 +111,14 @@ class HeaderValues {
struct {
const std::string EnvoyHealthChecker{"Envoy/HC"};
} UserAgentValues;

struct {
const std::string Default{"identity,deflate,gzip"};
} GrpcAcceptEncodingValues;

struct {
const std::string Trailers{"trailers"};
} TEValues;
};

typedef ConstSingleton<HeaderValues> Headers;
Expand Down
1 change: 1 addition & 0 deletions source/exe/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ envoy_cc_library(
"//source/server/config/http:dynamo_lib",
"//source/server/config/http:fault_lib",
"//source/server/config/http:grpc_http1_bridge_lib",
"//source/server/config/http:grpc_web_lib",
"//source/server/config/http:lightstep_lib",
"//source/server/config/http:ratelimit_lib",
"//source/server/config/http:router_lib",
Expand Down
11 changes: 11 additions & 0 deletions source/server/config/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "grpc_web_lib",
srcs = ["grpc_web.cc"],
hdrs = ["grpc_web.h"],
deps = [
"//include/envoy/server:instance_interface",
"//source/common/grpc:grpc_web_filter_lib",
"//source/server/config/network:http_connection_manager_lib",
],
)

envoy_cc_library(
name = "lightstep_lib",
srcs = ["lightstep_http_tracer.cc"],
Expand Down
32 changes: 32 additions & 0 deletions source/server/config/http/grpc_web.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include "server/config/http/grpc_web.h"

#include "common/grpc/grpc_web_filter.h"

namespace Envoy {
namespace Server {
namespace Configuration {

HttpFilterFactoryCb GrpcWebFilterConfig::createFilterFactory(HttpFilterType type,
const Json::Object&,
const std::string&,
Server::Instance&) {
if (type != HttpFilterType::Both) {
throw EnvoyException(fmt::format(
"{} gRPC-Web filter must be configured as both a decoder and encoder filter.", name()));
}

return [](Http::FilterChainFactoryCallbacks& callbacks) -> void {
callbacks.addStreamFilter(Http::StreamFilterSharedPtr{new Grpc::GrpcWebFilter()});
};
}

std::string GrpcWebFilterConfig::name() { return "grpc_web"; }

/**
* Static registration for the gRPC-Web filter. @see RegisterNamedHttpFilterConfigFactory.
*/
static RegisterNamedHttpFilterConfigFactory<GrpcWebFilterConfig> register_;

} // namespace Configuration
} // namespace Server
} // namespace Envoy
19 changes: 19 additions & 0 deletions source/server/config/http/grpc_web.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "server/config/network/http_connection_manager.h"

namespace Envoy {
namespace Server {
namespace Configuration {

class GrpcWebFilterConfig : public NamedHttpFilterConfigFactory {
public:
HttpFilterFactoryCb createFilterFactory(HttpFilterType type, const Json::Object&,
const std::string&, Server::Instance&) override;

std::string name() override;
};

} // namespace Configuration
} // namespace Server
} // namespace Envoy
11 changes: 11 additions & 0 deletions test/common/grpc/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ envoy_cc_test(
],
)

envoy_cc_test(
name = "grpc_web_filter_test",
srcs = ["grpc_web_filter_test.cc"],
deps = [
"//source/common/grpc:grpc_web_filter_lib",
"//test/mocks/http:http_mocks",
"//test/mocks/upstream:upstream_mocks",
"//test/test_common:utility_lib",
],
)

envoy_cc_test(
name = "rpc_channel_impl_test",
srcs = ["rpc_channel_impl_test.cc"],
Expand Down
Loading