Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 48 additions & 1 deletion api/envoy/config/bootstrap/v3/bootstrap.proto
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// <config_overview_bootstrap>` for more detail.

// Bootstrap :ref:`configuration overview <config_overview_bootstrap>`.
// [#next-free-field: 32]
// [#next-free-field: 33]
message Bootstrap {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.bootstrap.v2.Bootstrap";
Expand Down Expand Up @@ -322,6 +322,13 @@ message Bootstrap {
// field.
// [#not-implemented-hide:]
map<string, core.v3.TypedExtensionConfig> certificate_provider_instances = 25;

// Specifies a set of headers that need to be registered as inline header. This configuration
// allows users to customize the inline headers on-demand at Envoy startup without modifying
// Envoy's source code.
//
// Note that the 'set-cookie' header cannot be registered as inline header.
repeated CustomInlineHeader inline_headers = 32;
}

// Administration interface :ref:`operations documentation
Expand Down Expand Up @@ -595,3 +602,43 @@ message LayeredRuntime {
// such that later layers in the list overlay earlier entries.
repeated RuntimeLayer layers = 1;
}

// Used to specify the header that needs to be registered as an inline header.
//
// If request or response contain multiple headers with the same name and the header
// name is registered as an inline header. Then multiple headers will be folded
// into one, and multiple header values will be concatenated by a suitable delimiter.
// The delimiter is generally a comma.
//
// For example, if 'foo' is registered as an inline header, and the headers contains
// the following two headers:
//
// .. code-block:: text
//
// foo: bar
// foo: eep
//
// Then they will eventually be folded into:
//
// .. code-block:: text
//
// foo: bar, eep
//
// Inline headers provide O(1) search performance, but each inline header imposes
// an additional memory overhead on all instances of the corresponding type of
// HeaderMap or TrailerMap.
message CustomInlineHeader {
enum InlineHeaderType {
REQUEST_HEADER = 0;
REQUEST_TRAILER = 1;
RESPONSE_HEADER = 2;
RESPONSE_TRAILER = 3;
}

// The name of the header that is expected to be set as the inline header.
string inline_header_name = 1
[(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_NAME strict: false}];

// The type of the header that is expected to be set as the inline header.
InlineHeaderType inline_header_type = 2 [(validate.rules).enum = {defined_only: true}];
Copy link
Contributor

Choose a reason for hiding this comment

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

vadd pgv for well_known_regex: HTTP_HEADER_NAME?

Copy link
Member Author

Choose a reason for hiding this comment

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

get it.

}
52 changes: 51 additions & 1 deletion api/envoy/config/bootstrap/v4alpha/bootstrap.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Removed Config or Runtime

New Features
------------
* bootstrap: added :ref:`inline_headers <envoy_v3_api_field_config.bootstrap.v3.Bootstrap.inline_headers>` in the bootstrap to make custom inline headers bootstrap configurable.
* http: added :ref:`string_match <envoy_v3_api_field_config.route.v3.HeaderMatcher.string_match>` in the header matcher.

Deprecated
Expand Down
49 changes: 48 additions & 1 deletion generated_api_shadow/envoy/config/bootstrap/v3/bootstrap.proto

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions source/server/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,7 @@ envoy_cc_library(
"//source/common/grpc:context_lib",
"//source/common/http:codes_lib",
"//source/common/http:context_lib",
"//source/common/http:headers_lib",
"//source/common/init:manager_lib",
"//source/common/local_info:local_info_lib",
"//source/common/memory:heap_shrinker_lib",
Expand Down
47 changes: 45 additions & 2 deletions source/server/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "source/common/config/version_converter.h"
#include "source/common/config/xds_resource.h"
#include "source/common/http/codes.h"
#include "source/common/http/headers.h"
#include "source/common/local_info/local_info_impl.h"
#include "source/common/memory/stats.h"
#include "source/common/network/address_impl.h"
Expand Down Expand Up @@ -292,6 +293,46 @@ void loadBootstrap(absl::optional<uint32_t> bootstrap_version,
throw EnvoyException(fmt::format("Unknown bootstrap version {}.", *bootstrap_version));
}
}

bool canBeRegisteredAsInlineHeader(const Http::LowerCaseString& header_name) {
Copy link
Contributor

Choose a reason for hiding this comment

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

call out in API?

Copy link
Member Author

Choose a reason for hiding this comment

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

get it.

// 'set-cookie' cannot currently be registered as an inline header.
if (header_name == Http::Headers::get().SetCookie) {
return false;
}
return true;
}

void registerCustomInlineHeadersFromBootstrap(
const envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
for (const auto& inline_header : bootstrap.inline_headers()) {
const Http::LowerCaseString lower_case_name(inline_header.inline_header_name());
if (!canBeRegisteredAsInlineHeader(lower_case_name)) {
throw EnvoyException(fmt::format("Header {} cannot be registered as an inline header.",
inline_header.inline_header_name()));
}
switch (inline_header.inline_header_type()) {
case envoy::config::bootstrap::v3::CustomInlineHeader::REQUEST_HEADER:
Copy link
Contributor

Choose a reason for hiding this comment

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

Can one add as a custom inline header a header which is already an inline header? I think it's worth a unit test for this.

Copy link
Member Author

@wbpcode wbpcode Jul 21, 2021

Choose a reason for hiding this comment

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

Can one add as a custom inline header a header which is already an inline header?

Yes.

I think it's worth a unit test for this.

I think this scenario has been verified in the test of CustomInlineHeaderRegistry::registerInlineHeader itself. We can register the header with the same name as the inline header repeatedly without any negative effects.

Http::RegisterCustomInlineHeader<Http::CustomInlineHeaderRegistry::Type::RequestHeaders>
custom_header_1(Http::LowerCaseString{"foo_custom_header"});
Http::RegisterCustomInlineHeader<Http::CustomInlineHeaderRegistry::Type::RequestHeaders>
custom_header_1_copy(Http::LowerCaseString{"foo_custom_header"});

// Make sure that the same header registered twice points to the same location.
TEST_P(HeaderMapImplTest, CustomRegisteredHeaders) {
TestRequestHeaderMapImpl headers;
EXPECT_EQ(custom_header_1.handle(), custom_header_1_copy.handle());
EXPECT_EQ(nullptr, headers.getInline(custom_header_1.handle()));
EXPECT_EQ(nullptr, headers.getInline(custom_header_1_copy.handle()));
headers.setInline(custom_header_1.handle(), 42);
EXPECT_EQ("42", headers.getInlineValue(custom_header_1_copy.handle()));
EXPECT_EQ("foo_custom_header",
headers.getInline(custom_header_1.handle())->key().getStringView());
}

Http::CustomInlineHeaderRegistry::registerInlineHeader<
Http::RequestHeaderMap::header_map_type>(lower_case_name);
break;
case envoy::config::bootstrap::v3::CustomInlineHeader::REQUEST_TRAILER:
Http::CustomInlineHeaderRegistry::registerInlineHeader<
Http::RequestTrailerMap::header_map_type>(lower_case_name);
break;
case envoy::config::bootstrap::v3::CustomInlineHeader::RESPONSE_HEADER:
Http::CustomInlineHeaderRegistry::registerInlineHeader<
Http::ResponseHeaderMap::header_map_type>(lower_case_name);
break;
case envoy::config::bootstrap::v3::CustomInlineHeader::RESPONSE_TRAILER:
Http::CustomInlineHeaderRegistry::registerInlineHeader<
Http::ResponseTrailerMap::header_map_type>(lower_case_name);
break;
default:
NOT_IMPLEMENTED_GCOVR_EXCL_LINE;
}
}
}

} // namespace

void InstanceUtil::loadBootstrapConfig(envoy::config::bootstrap::v3::Bootstrap& bootstrap,
Expand Down Expand Up @@ -353,8 +394,10 @@ void InstanceImpl::initialize(const Options& options,
// setPrefix has a release assert verifying that setPrefix() is not called after prefix()
ThreadSafeSingleton<Http::PrefixValue>::get().setPrefix(bootstrap_.header_prefix().c_str());
}
// TODO(mattklein123): Custom O(1) headers can be registered at this point for creating/finalizing
// any header maps.

// Register Custom O(1) headers from bootstrap.
registerCustomInlineHeadersFromBootstrap(bootstrap_);

ENVOY_LOG(info, "HTTP header map info:");
for (const auto& info : Http::HeaderMapImplUtility::getAllHeaderMapImplInfo()) {
ENVOY_LOG(info, " {}: {} bytes: {}", info.name_, info.size_,
Expand Down
Loading