Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: add ability to add/remove query parameters #33977

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
57d85ad
functional implementation of add/remove query params
derekargueta Jun 13, 2021
15933e0
fix tests - dont parse query params when nothing is being added/removed
derekargueta May 4, 2024
60517b8
add changelog entry
derekargueta May 4, 2024
2db1cab
add basic initial config impl tests
derekargueta May 4, 2024
abc2de0
apply clang-format
derekargueta May 4, 2024
0f9782e
switch query_params_to_add to use QueryParameter protobuf type
derekargueta May 6, 2024
95b7237
add tests
derekargueta May 6, 2024
9c06fcd
query_params -> query_parameters in config API
derekargueta May 6, 2024
e636741
doc clarification on query_parameters_to_add
derekargueta May 6, 2024
7ec2cd9
fix format
derekargueta May 7, 2024
8f34e51
make query param mutation filter
derekargueta May 19, 2024
104fba4
Merge branch 'main' into derek-add-remove-query-params
derekargueta May 20, 2024
e31911f
remove query add/remove from router config_impl
derekargueta May 20, 2024
4eabeac
remove query params add/remove from router api, fix extension api
derekargueta May 20, 2024
c154196
clang-format
derekargueta May 20, 2024
e804f8f
move query param evaluator code to extension
derekargueta May 20, 2024
df82c4c
clang-format, remove no-longer-needed defaultEvaluator
derekargueta May 20, 2024
96e2ca9
initial review feedback
derekargueta May 22, 2024
5713609
give QueryParamsEvaluator normal constructor rather than singleton co…
derekargueta May 23, 2024
dd7cdac
code review
derekargueta May 23, 2024
733fdc0
add config test
derekargueta May 23, 2024
d07288f
Merge branch 'main' into derek-add-remove-query-params
derekargueta May 23, 2024
a139bb0
remove unused mock changes
derekargueta May 23, 2024
9f3eaa8
clang-format
derekargueta May 23, 2024
57a5f2f
one more format nit
derekargueta May 23, 2024
ded4304
refactor tests, add command substitution
derekargueta May 23, 2024
23b5558
more formatting...
derekargueta May 24, 2024
13ca58d
trim trailing whitespace...
derekargueta May 24, 2024
e88eeeb
proto formatting
derekargueta May 24, 2024
7eb433a
fix use of single backticks in docs
derekargueta May 24, 2024
130509d
add filter metadata
derekargueta May 24, 2024
7bb39c8
add filter to build registration
derekargueta May 24, 2024
9c85c52
fix rst yaml snippet
derekargueta May 25, 2024
cf14049
rst fix #2
derekargueta May 25, 2024
683e592
try to fix changelog
derekargueta May 25, 2024
5e59c68
try to address test TSAN error
derekargueta May 25, 2024
e3f0758
ability to specify query parameter append behavior
derekargueta May 31, 2024
6ce0d92
add type aliases to clean up code
derekargueta May 31, 2024
5475a3f
move query params to header mutation filter
derekargueta Jun 3, 2024
1aca2ba
clean out query parameter filter
derekargueta Jun 3, 2024
0c0bc51
formatting
derekargueta Jun 3, 2024
a2a76e5
Merge branch 'main' into derek-add-remove-query-params
derekargueta Jun 3, 2024
d88f04d
fix proto import order
derekargueta Jun 3, 2024
df4ea81
address line-too-long in changelog
derekargueta Jun 3, 2024
09b1dac
Merge branch 'main' into derek-add-remove-query-params
derekargueta Jun 4, 2024
f2b1ce7
restructure query parameters as list of mutations
derekargueta Jun 14, 2024
02fb669
alias protobuf types, clang-format
derekargueta Jun 14, 2024
07a795f
Merge branch 'main' into derek-add-remove-query-params
derekargueta Jun 14, 2024
7dcc321
fix typo in comments, caught by spelling CI
derekargueta Jun 14, 2024
aa96e59
remove proto dependency on v3 core - no longer needed, also caught by CI
derekargueta Jun 14, 2024
4939a35
Merge branch 'main' into derek-add-remove-query-params
derekargueta Jun 14, 2024
642c2a1
Merge branch 'main' into derek-add-remove-query-params
derekargueta Jul 10, 2024
fd155b7
update API
derekargueta Jul 10, 2024
ff55245
formatting
derekargueta Jul 10, 2024
0c00cc7
remove unused protos
derekargueta Jul 11, 2024
c276942
document query_parameter_mutations
derekargueta Jul 11, 2024
2c38641
use bytes
derekargueta Jul 16, 2024
466d1cd
Merge branch 'main' into derek-add-remove-query-params
derekargueta Jul 29, 2024
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
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123
# String matching extensions
/*/extensions/string_matcher/ @ggreenway @cpakulski
# Header mutation
/*/extensions/filters/http/header_mutation @wbpcode @htuch @soulxu
/*/extensions/filters/http/header_mutation @wbpcode @htuch @soulxu @derekargueta
# Health checkers
/*/extensions/health_checkers/grpc @zuercher @botengyao
/*/extensions/health_checkers/http @zuercher @botengyao
Expand Down
1 change: 1 addition & 0 deletions api/envoy/extensions/filters/http/header_mutation/v3/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ licenses(["notice"]) # Apache 2
api_proto_package(
deps = [
"//envoy/config/common/mutation_rules/v3:pkg",
"//envoy/config/core/v3:pkg",
"@com_github_cncf_xds//udpa/annotations:pkg",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ syntax = "proto3";
package envoy.extensions.filters.http.header_mutation.v3;

import "envoy/config/common/mutation_rules/v3/mutation_rules.proto";
import "envoy/config/core/v3/base.proto";

import "udpa/annotations/status.proto";

Expand All @@ -19,6 +20,9 @@ message Mutations {
// The request mutations are applied before the request is forwarded to the upstream cluster.
repeated config.common.mutation_rules.v3.HeaderMutation request_mutations = 1;

// The query parameter mutations are applied after request mutations before the request is forwarded to the upstream cluster.
repeated config.core.v3.KeyValueMutation query_parameter_mutations = 3;

// The response mutations are applied before the response is sent to the downstream client.
repeated config.common.mutation_rules.v3.HeaderMutation response_mutations = 2;
}
Expand Down
4 changes: 4 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,9 @@ new_features:
<envoy_v3_api_field_extensions.transport_sockets.tls.v3.CommonTlsContext.custom_tls_certificate_selector>`
to allow overriding TLS certificate selection behavior.
An extension can select certificate base on the incoming SNI, in both sync and async mode.
- area: http
change: |
Add query parameter capabilities to :ref:` Header Mutation Filter
<envoy_v3_api_msg_extensions.filters.http.header_mutation.v3.HeaderMutation>` for adding/removing query parameters on a request.

deprecated:
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Header Mutation
* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.header_mutation.v3.HeaderMutation``.
* :ref:`v3 API reference <envoy_v3_api_msg_extensions.filters.http.header_mutation.v3.HeaderMutation>`

This is a filter that can be used to add, remove, append, or update HTTP headers. It can be added in any position in the filter chain
This is a filter that can be used to add, remove, append, or update HTTP headers and query parameters. It can be added in any position in the filter chain
and used as downstream or upstream HTTP filter. The filter can be configured to apply the header mutations to the request, response, or both.


Expand All @@ -23,3 +23,52 @@ In addition, this filter can be used as upstream HTTP filter and mutate the requ

Please note that as an encoder filter, this filter follows the standard rules of when it will execute in situations such as local replies - response
headers will not be unconditionally added in cases where the filter would be bypassed.


Example configurations
----------------------

The following configuration will transform requests from `/some/path?remove-me=value` into `/some/path?param=new-value`.

.. code-block:: yaml

http_filters:
- name: envoy.filters.http.header_mutation
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.header_mutation.v3.HeaderMutation
query_parameters_to_add:
- append_action: APPEND_IF_EXISTS_OR_ADD
query_parameter:
key: param
value: new-value
query_parameters_to_remove:
- remove-me

- name: envoy.filters.http.router

The following configuration will add a query parameter only on requests that match `/foobar`.

.. code-block:: yaml

route_config:
virtual_hosts:
- name: service
domains: ["*"]
routes:
- match: { path: /foobar }
route: { cluster: service1 }
typed_per_filter_config:
envoy.filters.http.header_mutation:
"@type": type.googleapis.com/envoy.extensions.filters.http.header_mutation.v3.HeaderMutation
query_parameters_to_add:
- append_action: APPEND_IF_EXISTS_OR_ADD
query_parameter:
key: param
value: new-value
- match: { path: / }
route: { cluster: service2 }

http_filters:
- name: envoy.filters.http.header_mutation
- name: envoy.filters.http.router

2 changes: 1 addition & 1 deletion source/common/router/config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ class InternalRedirectPolicyImpl : public InternalRedirectPolicy {
using DefaultInternalRedirectPolicy = ConstSingleton<InternalRedirectPolicyImpl>;

/**
* Base implementation for all route entries.q
* Base implementation for all route entries.
*/
class RouteEntryImplBase : public RouteEntryAndRoute,
public Matchable,
Expand Down
2 changes: 1 addition & 1 deletion source/common/router/header_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class HeaderParser : public Http::HeaderEvaluator {

/**
* Helper methods to evaluate methods without explicitly passing request and response headers.
* The method will try to fetch request headers from steam_info. Response headers will always be
* The method will try to fetch request headers from stream_info. Response headers will always be
* empty.
*/
void evaluateHeaders(Http::HeaderMap& headers, const StreamInfo::StreamInfo& stream_info) const {
Expand Down
17 changes: 17 additions & 0 deletions source/extensions/filters/http/header_mutation/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ envoy_cc_library(
srcs = ["header_mutation.cc"],
hdrs = ["header_mutation.h"],
deps = [
":query_params_evaluator_lib",
"//envoy/server:filter_config_interface",
"//source/common/config:utility_lib",
"//source/common/http:header_map_lib",
Expand All @@ -36,3 +37,19 @@ envoy_cc_extension(
"@envoy_api//envoy/extensions/filters/http/header_mutation/v3:pkg_cc_proto",
],
)

envoy_cc_library(
name = "query_params_evaluator_lib",
srcs = ["query_params_evaluator.cc"],
hdrs = ["query_params_evaluator.h"],
deps = [
"//envoy/formatter:substitution_formatter_interface",
"//envoy/http:header_map_interface",
"//envoy/http:query_params_interface",
"//source/common/formatter:substitution_formatter_lib",
"//source/common/http:utility_lib",
"//source/common/protobuf:utility_lib",
"@envoy_api//envoy/config/core/v3:pkg_cc_proto",
"@envoy_api//envoy/extensions/filters/http/header_mutation/v3:pkg_cc_proto",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ namespace Extensions {
namespace HttpFilters {
namespace HeaderMutation {

void Mutations::mutateRequestHeaders(Http::HeaderMap& headers,
void Mutations::mutateRequestHeaders(Http::RequestHeaderMap& headers,
const Formatter::HttpFormatterContext& ctx,
const StreamInfo::StreamInfo& stream_info) const {
request_mutations_->evaluateHeaders(headers, ctx, stream_info);
query_params_evaluator_->evaluateQueryParams(headers, stream_info);
}

void Mutations::mutateResponseHeaders(Http::HeaderMap& headers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "source/common/common/logger.h"
#include "source/common/http/header_mutation.h"
#include "source/extensions/filters/http/common/pass_through_filter.h"
#include "source/extensions/filters/http/header_mutation/query_params_evaluator.h"

#include "absl/strings/string_view.h"

Expand All @@ -32,16 +33,20 @@ class Mutations {
HeaderMutations::create(config.request_mutations()), std::unique_ptr<HeaderMutations>)),
response_mutations_(
THROW_OR_RETURN_VALUE(HeaderMutations::create(config.response_mutations()),
std::unique_ptr<HeaderMutations>)) {}
std::unique_ptr<HeaderMutations>)),
query_params_evaluator_(
std::make_unique<QueryParamsEvaluator>(config.query_parameter_mutations())) {}

void mutateRequestHeaders(Http::HeaderMap& headers, const Formatter::HttpFormatterContext& ctx,
void mutateRequestHeaders(Http::RequestHeaderMap& headers,
const Formatter::HttpFormatterContext& ctx,
const StreamInfo::StreamInfo& stream_info) const;
void mutateResponseHeaders(Http::HeaderMap& headers, const Formatter::HttpFormatterContext& ctx,
const StreamInfo::StreamInfo& stream_info) const;

private:
const std::unique_ptr<HeaderMutations> request_mutations_;
const std::unique_ptr<HeaderMutations> response_mutations_;
QueryParamsEvaluatorPtr query_params_evaluator_;
};

class PerRouteHeaderMutation : public Router::RouteSpecificFilterConfig {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include "source/extensions/filters/http/header_mutation/query_params_evaluator.h"

#include <memory>
#include <string>
#include <utility>

#include "envoy/http/query_params.h"
#include "envoy/stream_info/stream_info.h"

#include "source/common/formatter/substitution_formatter.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace HeaderMutation {

using Http::Utility::QueryParamsMulti;

QueryParamsEvaluator::QueryParamsEvaluator(
const Protobuf::RepeatedPtrField<KeyValueMutationProto>& query_param_mutations)
: formatter_(std::make_unique<Formatter::FormatterImpl>("", true)) {

for (const auto& query_param : query_param_mutations) {
mutations_.emplace_back(query_param);
}
}

void QueryParamsEvaluator::evaluateQueryParams(Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& stream_info) const {
if (mutations_.empty()) {
return;
}

absl::string_view path = headers.getPathValue();
QueryParamsMulti query_params = QueryParamsMulti::parseAndDecodeQueryString(path);

Formatter::HttpFormatterContext ctx{&headers};
for (const auto& mutation : mutations_) {
if (!mutation.remove().empty()) {
query_params.remove(mutation.remove());
} else {
const auto value_option = mutation.append();
const auto key = value_option.entry().key();
const auto value = value_option.entry().value();
const auto formatter = std::make_unique<Formatter::FormatterImpl>(value, true);
Comment on lines +42 to +45
Copy link
Member

Choose a reason for hiding this comment

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

Please initialize the formatter when loading configuration to avoid do this at runtime.

Copy link
Member Author

Choose a reason for hiding this comment

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

The formatter needs the value?

Copy link
Member

@wbpcode wbpcode Jul 16, 2024

Choose a reason for hiding this comment

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

Yeah, you need to parse the value when you constructing QueryParamsEvaluator. Creating the formatter will bring big overhead and may throw exception. It's not allowed to do that at key data path.

Copy link
Member

Choose a reason for hiding this comment

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

I think this is problem is still not be resolved.

We should load/create the formatter when loading configuration.

You can take the header mutations implemention as an example.

/lgtm api

/wait

switch (AppendAction(value_option.action())) {
case AppendAction::AppendIfExistsOrAdd:
query_params.add(key, formatter->formatWithContext(ctx, stream_info));
break;
case AppendAction::AddIfAbsent:
if (!query_params.getFirstValue(key).has_value()) {
query_params.add(key, formatter->formatWithContext(ctx, stream_info));
}
break;
case AppendAction::OverwriteIfExistsOrAdd:
query_params.overwrite(key, value);
break;
case AppendAction::OverwriteIfExists:
if (query_params.getFirstValue(key).has_value()) {
query_params.overwrite(key, value);
}
break;
default:
PANIC("unreachable");
}
}
}

const auto new_path = query_params.replaceQueryString(headers.Path()->value());
headers.setPath(new_path);
}

} // namespace HeaderMutation
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once

#include <string>
#include <tuple>
#include <vector>

#include "envoy/config/core/v3/base.pb.h"
#include "envoy/extensions/filters/http/header_mutation/v3/header_mutation.pb.h"
#include "envoy/formatter/substitution_formatter.h"
#include "envoy/http/header_map.h"
#include "envoy/http/query_params.h"
#include "envoy/stream_info/stream_info.h"

#include "source/common/protobuf/protobuf.h"

namespace Envoy {
namespace Extensions {
namespace HttpFilters {
namespace HeaderMutation {

using KeyValueAppendProto = envoy::config::core::v3::KeyValueAppend;
using KeyValueAppendActionProto = envoy::config::core::v3::KeyValueAppend_KeyValueAppendAction;
using KeyValueMutationProto = envoy::config::core::v3::KeyValueMutation;

enum class AppendAction {
AppendIfExistsOrAdd = envoy::config::core::v3::KeyValueAppend::APPEND_IF_EXISTS_OR_ADD,
AddIfAbsent = envoy::config::core::v3::KeyValueAppend::ADD_IF_ABSENT,
OverwriteIfExistsOrAdd = envoy::config::core::v3::KeyValueAppend::OVERWRITE_IF_EXISTS_OR_ADD,
OverwriteIfExists = envoy::config::core::v3::KeyValueAppend::OVERWRITE_IF_EXISTS,
};

class QueryParamsEvaluator;
using QueryParamsEvaluatorPtr = std::unique_ptr<QueryParamsEvaluator>;

class QueryParamsEvaluator {
public:
QueryParamsEvaluator(
const Protobuf::RepeatedPtrField<KeyValueMutationProto>& query_param_mutations);

/**
* Processes headers first through query parameter removals then through query parameter
* additions. Header is modified in-place.
* @param headers supplies the request headers.
* @param stream_info used by the substitution formatter. Can be retrieved via
* decoder_callbacks_.streamInfo();
*/
void evaluateQueryParams(Http::RequestHeaderMap& headers,
const StreamInfo::StreamInfo& stream_info) const;

protected:
QueryParamsEvaluator() = default;

private:
std::vector<KeyValueMutationProto> mutations_;
Formatter::FormatterPtr formatter_;
};

} // namespace HeaderMutation
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
11 changes: 11 additions & 0 deletions test/extensions/filters/http/header_mutation/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,14 @@ envoy_extension_cc_test(
"@com_google_absl//absl/strings:str_format",
],
)

envoy_extension_cc_test(
name = "query_params_evaluator_test",
srcs = ["query_params_evaluator_test.cc"],
extension_names = ["envoy.filters.http.header_mutation"],
deps = [
"//source/common/router:string_accessor_lib",
"//source/extensions/filters/http/header_mutation:query_params_evaluator_lib",
"//test/mocks/stream_info:stream_info_mocks",
],
)
7 changes: 7 additions & 0 deletions test/extensions/filters/http/header_mutation/config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ TEST(FactoryTest, FactoryTest) {
key: "flag-header"
value: "%REQ(ANOTHER-FLAG-HEADER)%"
append_action: APPEND_IF_EXISTS_OR_ADD
query_parameter_mutations:
- remove: remove-me
- append:
action: APPEND_IF_EXISTS_OR_ADD
entry:
key: foo
value: bar
)EOF";

PerRouteProtoConfig per_route_proto_config;
Expand Down
Loading