Skip to content

Commit ceac456

Browse files
authored
composite_filter: Demonstrate the usage of route-level config (envoyproxy#30442)
* valid per route config Signed-off-by: tyxia <[email protected]> * tweak names Signed-off-by: tyxia <[email protected]> * add new line Signed-off-by: tyxia <[email protected]> * update test Signed-off-by: tyxia <[email protected]> --------- Signed-off-by: tyxia <[email protected]>
1 parent 4eedea6 commit ceac456

File tree

6 files changed

+99
-13
lines changed

6 files changed

+99
-13
lines changed

test/extensions/filters/http/composite/composite_filter_integration_test.cc

+37
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ using envoy::extensions::common::matching::v3::ExtensionWithMatcherPerRoute;
3333
using envoy::extensions::filters::http::composite::v3::ExecuteFilterAction;
3434
using envoy::type::matcher::v3::HttpRequestHeaderMatchInput;
3535
using test::integration::filters::SetResponseCodeFilterConfig;
36+
using test::integration::filters::SetResponseCodePerRouteFilterConfig;
3637
using xds::type::matcher::v3::Matcher_OnMatch;
3738

3839
class CompositeFilterIntegrationTest : public testing::TestWithParam<Network::Address::IpVersion>,
@@ -87,6 +88,25 @@ class CompositeFilterIntegrationTest : public testing::TestWithParam<Network::Ad
8788
});
8889
}
8990

91+
void addResponseCodeFilterPerRouteConfig(const std::string& filter_name,
92+
const std::string& route_prefix, const int& code,
93+
bool response_prefix = false) {
94+
SetResponseCodePerRouteFilterConfig set_response_code_per_route_config;
95+
set_response_code_per_route_config.set_code(code);
96+
if (response_prefix) {
97+
set_response_code_per_route_config.set_prefix("skipLocalReplyAndContinue");
98+
}
99+
config_helper_.addConfigModifier([set_response_code_per_route_config, filter_name,
100+
route_prefix](ConfigHelper::HttpConnectionManager& cm) {
101+
auto* vh = cm.mutable_route_config()->mutable_virtual_hosts(0);
102+
auto* route = vh->mutable_routes()->Mutable(0);
103+
route->mutable_match()->set_prefix(route_prefix);
104+
route->mutable_route()->set_cluster("cluster_0");
105+
(*route->mutable_typed_per_filter_config())[filter_name].PackFrom(
106+
set_response_code_per_route_config);
107+
});
108+
}
109+
90110
void prependCompositeFilter(const std::string& name = "composite") {
91111
config_helper_.prependFilter(absl::StrFormat(R"EOF(
92112
name: %s
@@ -166,6 +186,23 @@ TEST_P(CompositeFilterIntegrationTest, TestPerRoute) {
166186
EXPECT_THAT(response->headers(), Http::HttpStatusIs("401"));
167187
}
168188

189+
// Verifies set_response_code filter's per-route config overrides the filter config.
190+
TEST_P(CompositeFilterIntegrationTest, TestPerRouteResponseCodeConfig) {
191+
std::string top_level_filter_name = "match_delegate_filter";
192+
prependCompositeFilter(top_level_filter_name);
193+
194+
addResponseCodeFilterPerRouteConfig(/*filter_name=*/top_level_filter_name,
195+
/*route_prefix=*/"/somepath",
196+
/*code=*/406);
197+
initialize();
198+
codec_client_ = makeHttpConnection(lookupPort("http"));
199+
200+
auto response = codec_client_->makeRequestWithBody(match_request_headers_, 1024);
201+
ASSERT_TRUE(response->waitForEndStream());
202+
// Verifies that 406 from per route config is used, rather than 403 from filter config.
203+
EXPECT_THAT(response->headers(), Http::HttpStatusIs("406"));
204+
}
205+
169206
// Test an empty match tree resolving with a per route config.
170207
TEST_P(CompositeFilterIntegrationTest, TestPerRouteEmptyMatcher) {
171208
config_helper_.prependFilter(R"EOF(

test/integration/BUILD

+1-1
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ envoy_cc_test(
722722
]),
723723
deps = [
724724
":http_integration_lib",
725-
"//test/integration/filters:set_response_code_filter_lib",
725+
"//test/integration/filters:set_route_filter_lib",
726726
],
727727
)
728728

test/integration/filters/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ envoy_cc_test_library(
487487
":set_response_code_filter_config_proto_cc_proto",
488488
"//envoy/http:filter_interface",
489489
"//envoy/registry",
490+
"//source/common/http:utility_lib",
490491
"//source/extensions/filters/http/common:factory_base_lib",
491492
"//source/extensions/filters/http/common:pass_through_filter_lib",
492493
],

test/integration/filters/set_response_code_filter.cc

+47-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "envoy/http/filter.h"
44
#include "envoy/registry/registry.h"
55

6+
#include "source/common/http/utility.h"
67
#include "source/extensions/filters/http/common/factory_base.h"
78
#include "source/extensions/filters/http/common/pass_through_filter.h"
89

@@ -27,14 +28,45 @@ class SetResponseCodeFilterConfig {
2728
ThreadLocal::TypedSlot<> tls_slot_;
2829
};
2930

31+
class SetResponseCodeFilterRouteSpecificConfig : public Envoy::Router::RouteSpecificFilterConfig {
32+
public:
33+
SetResponseCodeFilterRouteSpecificConfig(const std::string& prefix, uint32_t code,
34+
const std::string& body,
35+
Server::Configuration::FactoryContextBase& context)
36+
: prefix_(prefix), code_(code), body_(body), tls_slot_(context.threadLocal()) {}
37+
38+
const std::string prefix_;
39+
const uint32_t code_;
40+
const std::string body_;
41+
// Allocate a slot to validate that it is destroyed on a main thread only.
42+
ThreadLocal::TypedSlot<> tls_slot_;
43+
};
44+
3045
class SetResponseCodeFilter : public Http::PassThroughFilter {
3146
public:
3247
SetResponseCodeFilter(std::shared_ptr<SetResponseCodeFilterConfig> config) : config_(config) {}
3348

3449
Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override {
35-
if (absl::StartsWith(headers.Path()->value().getStringView(), config_->prefix_)) {
36-
decoder_callbacks_->sendLocalReply(static_cast<Http::Code>(config_->code_), config_->body_,
37-
nullptr, absl::nullopt, "");
50+
const auto* per_route_config = Envoy::Http::Utility::resolveMostSpecificPerFilterConfig<
51+
SetResponseCodeFilterRouteSpecificConfig>(decoder_callbacks_);
52+
53+
std::string prefix;
54+
uint32_t code;
55+
std::string body;
56+
// Route level config takes precedence over filter level config, if present.
57+
if (per_route_config != nullptr) {
58+
prefix = per_route_config->prefix_;
59+
code = per_route_config->code_;
60+
body = per_route_config->body_;
61+
} else {
62+
prefix = config_->prefix_;
63+
code = config_->code_;
64+
body = config_->body_;
65+
}
66+
67+
if (absl::StartsWith(headers.Path()->value().getStringView(), prefix)) {
68+
decoder_callbacks_->sendLocalReply(static_cast<Http::Code>(code), body, nullptr,
69+
absl::nullopt, "");
3870
return Http::FilterHeadersStatus::StopIteration;
3971
}
4072
return Http::FilterHeadersStatus::Continue;
@@ -44,8 +76,10 @@ class SetResponseCodeFilter : public Http::PassThroughFilter {
4476
const std::shared_ptr<SetResponseCodeFilterConfig> config_;
4577
};
4678

47-
class SetResponseCodeFilterFactory : public Extensions::HttpFilters::Common::FactoryBase<
48-
test::integration::filters::SetResponseCodeFilterConfig> {
79+
class SetResponseCodeFilterFactory
80+
: public Extensions::HttpFilters::Common::FactoryBase<
81+
test::integration::filters::SetResponseCodeFilterConfig,
82+
test::integration::filters::SetResponseCodePerRouteFilterConfig> {
4983
public:
5084
SetResponseCodeFilterFactory() : FactoryBase("set-response-code-filter") {}
5185

@@ -68,6 +102,14 @@ class SetResponseCodeFilterFactory : public Extensions::HttpFilters::Common::Fac
68102
callbacks.addStreamFilter(std::make_shared<SetResponseCodeFilter>(filter_config));
69103
};
70104
}
105+
106+
Router::RouteSpecificFilterConfigConstSharedPtr createRouteSpecificFilterConfigTyped(
107+
const test::integration::filters::SetResponseCodePerRouteFilterConfig& proto_config,
108+
Server::Configuration::ServerFactoryContext& context,
109+
ProtobufMessage::ValidationVisitor&) override {
110+
return std::make_shared<const SetResponseCodeFilterRouteSpecificConfig>(
111+
proto_config.prefix(), proto_config.code(), proto_config.body(), context);
112+
}
71113
};
72114

73115
REGISTER_FACTORY(SetResponseCodeFilterFactory, Server::Configuration::NamedHttpFilterConfigFactory);

test/integration/filters/set_response_code_filter_config.proto

+7
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,10 @@ message SetResponseCodeFilterConfig {
99
uint32 code = 2 [(validate.rules).uint32 = {lt: 600 gte: 200}];
1010
string body = 3;
1111
}
12+
13+
// Per route config overrides filter config, if present.
14+
message SetResponseCodePerRouteFilterConfig {
15+
string prefix = 1;
16+
uint32 code = 2 [(validate.rules).uint32 = {lt: 600 gte: 200}];
17+
string body = 3;
18+
}

test/integration/http_typed_per_filter_config_test.cc

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "envoy/config/route/v3/route_components.pb.h"
22

3-
#include "test/integration/filters/set_response_code_filter_config.pb.h"
3+
#include "test/integration/filters/set_route_filter_config.pb.h"
44
#include "test/integration/http_integration.h"
55

66
#include "gtest/gtest.h"
@@ -18,21 +18,20 @@ TEST_F(HTTPTypedPerFilterConfigTest, RejectUnsupportedTypedPerFilterConfig) {
1818
config_helper_.addConfigModifier(
1919
[&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
2020
hcm) {
21-
test::integration::filters::SetResponseCodeFilterConfig response_code;
22-
response_code.set_code(403);
21+
test::integration::filters::SetRouteFilterConfig set_route_config;
2322

2423
auto* virtual_host = hcm.mutable_route_config()->mutable_virtual_hosts(0);
2524
auto* config = virtual_host->mutable_typed_per_filter_config();
26-
(*config)["set-response-code-filter"].PackFrom(response_code);
25+
(*config)["set-route-filter"].PackFrom(set_route_config);
2726

2827
auto* filter = hcm.mutable_http_filters()->Add();
29-
filter->set_name("set-response-code-filter");
30-
filter->mutable_typed_config()->PackFrom(response_code);
28+
filter->set_name("set-route-filter");
29+
filter->mutable_typed_config()->PackFrom(set_route_config);
3130
// keep router the last
3231
auto size = hcm.http_filters_size();
3332
hcm.mutable_http_filters()->SwapElements(size - 2, size - 1);
3433
});
35-
EXPECT_DEATH(initialize(), "The filter set-response-code-filter doesn't support virtual host or "
34+
EXPECT_DEATH(initialize(), "The filter set-route-filter doesn't support virtual host or "
3635
"route specific configurations");
3736
}
3837

0 commit comments

Comments
 (0)