Skip to content

Commit

Permalink
Mt feature/cherry pick http 1.1 case preservation 1.9.9 (#7)
Browse files Browse the repository at this point in the history
* feat: http: add HTTP/1.1 case preservation (envoyproxy#15619)

1) Add new stateful header formatter extension point; 2) Add preserve case formatter extension;
Fixes envoyproxy#14363

Signed-off-by: jiangshantao <[email protected]>

* fix: fix Error envoy_cc_library() got unexpected keyword argument: category

Signed-off-by: jiangshantao <[email protected]>

* fix: source/common/http/http1/settings.cc:39:39: error: unused parameter 'validate_scheme'

Signed-off-by: jiangshantao <[email protected]>

* feat: preserve case with proper case config

Signed-off-by: jiangshantao <[email protected]>

Co-authored-by: jiangshantao <[email protected]>
  • Loading branch information
jiangshantao-dbg and jiangshantao authored Mar 31, 2022
1 parent d53d856 commit 565d857
Show file tree
Hide file tree
Showing 48 changed files with 879 additions and 149 deletions.
1 change: 1 addition & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ proto_library(
"//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg",
"//envoy/extensions/filters/udp/dns_filter/v3alpha:pkg",
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
"//envoy/extensions/http/header_formatters/preserve_case/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
Expand Down
7 changes: 7 additions & 0 deletions api/envoy/config/core/v3/protocol.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";

package envoy.config.core.v3;

import "envoy/config/core/v3/extension.proto";
import "envoy/type/v3/percent.proto";

import "google/protobuf/duration.proto";
Expand Down Expand Up @@ -111,6 +112,7 @@ message Http1ProtocolOptions {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.core.Http1ProtocolOptions";

// [#next-free-field: 9]
message HeaderKeyFormat {
option (udpa.annotations.versioning).previous_message_type =
"envoy.api.v2.core.Http1ProtocolOptions.HeaderKeyFormat";
Expand All @@ -129,6 +131,11 @@ message Http1ProtocolOptions {
// Note that while this results in most headers following conventional casing, certain headers
// are not covered. For example, the "TE" header will be formatted as "Te".
ProperCaseWords proper_case_words = 1;

// Configuration for stateful formatter extensions that allow using received headers to
// affect the output of encoding headers. E.g., preserving case during proxying.
// [#extension-category: envoy.http.stateful_header_formatters]
TypedExtensionConfig stateful_formatter = 8;
}
}

Expand Down
7 changes: 7 additions & 0 deletions api/envoy/config/core/v4alpha/protocol.proto

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.

load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")

licenses(["notice"]) # Apache 2

api_proto_package(
deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
syntax = "proto3";

package envoy.extensions.http.header_formatters.preserve_case.v3;

import "udpa/annotations/status.proto";

option java_package = "io.envoyproxy.envoy.extensions.http.header_formatters.preserve_case.v3";
option java_outer_classname = "PreserveCaseProto";
option java_multiple_files = true;
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Preserve case header formatter]
// [#extension: envoy.http.stateful_header_formatters.preserve_case]

// Configuration for the preserve case header formatter.
// See the :ref:`header casing <config_http_conn_man_header_casing>` configuration guide for more
// information.
message PreserveCaseFormatterConfig {
}
1 change: 1 addition & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ proto_library(
"//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg",
"//envoy/extensions/filters/udp/dns_filter/v3alpha:pkg",
"//envoy/extensions/filters/udp/udp_proxy/v3:pkg",
"//envoy/extensions/http/header_formatters/preserve_case/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v3/config/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Extensions
wasm/wasm
watchdog/watchdog
descriptors/descriptors
http/header_formatters
8 changes: 8 additions & 0 deletions docs/root/api-v3/config/http/header_formatters.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
HTTP header formatters
======================

.. toctree::
:glob:
:maxdepth: 2

../../extensions/http/header_formatters/*/v3/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 443
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_protocol_options:
header_key_format:
stateful_formatter:
name: preserve_case
typed_config:
"@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig
http_filters:
- name: envoy.filters.http.router
route_config:
virtual_hosts:
- name: default
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: service_foo
clusters:
- name: service_foo
connect_timeout: 15s
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http_protocol_options:
header_key_format:
stateful_formatter:
name: preserve_case
typed_config:
"@type": type.googleapis.com/envoy.extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig
load_assignment:
cluster_name: some_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8080
45 changes: 39 additions & 6 deletions docs/root/configuration/http/http_conn_man/header_casing.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
.. _config_http_conn_man_header_casing:

HTTP/1.1 Header Casing
======================

When handling HTTP/1.1, Envoy will normalize the header keys to be all lowercase. While this is
compliant with the HTTP/1.1 spec, in practice this can result in issues when migrating
existing systems that might rely on specific header casing.

To support these use cases, Envoy allows configuring a formatting scheme for the headers, which
will have Envoy transform the header keys during serialization. To configure this formatting on
response headers, specify the format in the :ref:`http_protocol_options <envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.http_protocol_options>`.
To configure this for upstream request headers, specify the formatting in :ref:`http_protocol_options <envoy_v3_api_msg_extensions.upstreams.http.v3.HttpProtocolOptions>` in the Cluster's :ref:`extension_protocol_options<envoy_v3_api_field_config.cluster.v3.Cluster.typed_extension_protocol_options>`.
To support these use cases, Envoy allows :ref:`configuring a formatting scheme for the headers
<envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.header_key_format>`, which will have Envoy
transform the header keys during serialization.

To configure this formatting on response headers, specify the format in the
:ref:`http_protocol_options
<envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.http_protocol_options>`.
To configure this for upstream request headers, specify the formatting in
:ref:`http_protocol_options <envoy_v3_api_msg_extensions.upstreams.http.v3.HttpProtocolOptions>` in
the cluster's
:ref:`extension_protocol_options<envoy_v3_api_field_config.cluster.v3.Cluster.typed_extension_protocol_options>`.

Currently Envoy supports two mutually exclusive types of header key formatters:

Stateless formatters
--------------------

Stateless formatters are run on encoding and do not depend on any previous knowledge of the headers.
An example of this type of formatter is the :ref:`proper case words
<envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.HeaderKeyFormat.proper_case_words>`
formatter. These formatters are useful when converting from non-HTTP/1 to HTTP/1 (within a single
proxy or across multiple hops) or when stateful formatting is not desired due to increased memory
requirements.

Stateful formatters
-------------------

Stateful formatters are instantiated on decoding, called for every decoded header, attached to the
header map, and are then available during encoding to format the headers prior to writing. Thus, they
traverse the entire proxy stack. An example of this type of formatter is the :ref:`preserve case
formatter
<envoy_v3_api_msg_extensions.http.header_formatters.preserve_case.v3.PreserveCaseFormatterConfig>`
configured via the :ref:`stateful_formatter
<envoy_v3_api_field_config.core.v3.Http1ProtocolOptions.HeaderKeyFormat.stateful_formatter>` field.
The following is an example configuration which will preserve HTTP/1 header case across the proxy.

See :ref:`below <faq_configuration_timeouts_transport_socket>` for other connection timeouts.
on the :ref:`Cluster <envoy_v3_api_field_config.cluster.v3.Cluster.http_protocol_options>`. FIXME
.. literalinclude:: _include/preserve-case.yaml
:language: yaml
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Removed Config or Runtime

New Features
------------
* http: added the ability to preserve HTTP/1 header case across the proxy. See the :ref:`header casing <config_http_conn_man_header_casing>` documentation for more information.
* listener: added an option when balancing across active listeners and wildcard matching is used to return the listener that matches the IP family type associated with the listener's socket address. It is off by default, but is turned on by default in v1.19. To set change the runtime guard `envoy.reloadable_features.listener_wildcard_match_ip_family` to true.
* upstream: added support for :ref:`slow start mode <arch_overview_load_balancing_slow_start>`, which allows to progresively increase traffic for new endpoints.
* upstream: extended :ref:`Round Robin load balancer configuration <envoy_v3_api_field_config.cluster.v3.Cluster.round_robin_lb_config>` with :ref:`slow start <envoy_v3_api_field_config.cluster.v3.Cluster.RoundRobinLbConfig.slow_start_config>` support.
Expand Down
1 change: 1 addition & 0 deletions generated_api_shadow/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ proto_library(
"//envoy/extensions/filters/network/thrift_proxy/filters/ratelimit/v3:pkg",
"//envoy/extensions/filters/network/thrift_proxy/v3:pkg",
"//envoy/extensions/filters/network/zookeeper_proxy/v3:pkg",
"//envoy/extensions/http/header_formatters/preserve_case/v3:pkg",
"//envoy/extensions/retry/host/omit_host_metadata/v3:pkg",
"//envoy/extensions/retry/priority/previous_priorities/v3:pkg",
"//envoy/extensions/transport_sockets/alts/v3:pkg",
Expand Down
7 changes: 7 additions & 0 deletions generated_api_shadow/envoy/config/core/v3/protocol.proto

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

7 changes: 7 additions & 0 deletions generated_api_shadow/envoy/config/core/v4alpha/protocol.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.

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

20 changes: 20 additions & 0 deletions include/envoy/common/optref.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ template <class T> struct OptRef : public absl::optional<std::reference_wrapper<
OptRef(T& t) : absl::optional<std::reference_wrapper<T>>(t) {}
OptRef() = default;

/**
* Copy constructor that allows conversion.
*/
template <class From> explicit OptRef(OptRef<From> rhs) {
if (rhs.has_value()) {
*this = rhs.ref();
}
}

/**
* Assignment that allows conversion.
*/
template <class From> OptRef<T>& operator=(OptRef<From> rhs) {
this->reset();
if (rhs.has_value()) {
*this = rhs.ref();
}
return *this;
}

/**
* Helper to call a method on T. The caller is responsible for ensuring
* has_value() is true.
Expand Down
9 changes: 9 additions & 0 deletions include/envoy/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ envoy_cc_library(
"abseil_inlined_vector",
],
deps = [
":header_formatter_interface",
"//source/common/common:assert_lib",
"//source/common/common:hash_lib",
],
Expand Down Expand Up @@ -140,3 +141,11 @@ envoy_cc_library(
":header_map_interface",
],
)

envoy_cc_library(
name = "header_formatter_interface",
hdrs = ["header_formatter.h"],
deps = [
"//include/envoy/config:typed_config_interface",
],
)
6 changes: 6 additions & 0 deletions include/envoy/http/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "envoy/buffer/buffer.h"
#include "envoy/common/pure.h"
#include "envoy/grpc/status.h"
#include "envoy/http/header_formatter.h"
#include "envoy/http/header_map.h"
#include "envoy/http/metadata_interface.h"
#include "envoy/http/protocol.h"
Expand Down Expand Up @@ -396,11 +397,16 @@ struct Http1Settings {
// Performs proper casing of header keys: the first and all alpha characters following a
// non-alphanumeric character is capitalized.
ProperCase,
// A stateful formatter extension has been configured.
StatefulFormatter,
};

// How header keys should be formatted when serializing HTTP/1.1 headers.
HeaderKeyFormat header_key_format_{HeaderKeyFormat::Default};

// Non-null IFF header_key_format_ is configured to StatefulFormatter.
StatefulHeaderKeyFormatterFactorySharedPtr stateful_header_key_formatter_;

// Behaviour on invalid HTTP messaging:
// - if true, the HTTP/1.1 connection is left open (where possible)
// - if false, the HTTP/1.1 connection is terminated
Expand Down
Loading

0 comments on commit 565d857

Please sign in to comment.