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
7 changes: 6 additions & 1 deletion docs/configuration/http_conn_man/route_config/route.rst
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ The router can match a request to a route based on headers specified in the rout
.. code-block:: json

[
{"name": "...", "value": "..."}
{"name": "...", "value": "...", "regex": "..."}
]

name
Expand All @@ -208,6 +208,11 @@ value
*(optional, string)* Specifies the value of the header. If the value is absent a request that has
the *name* header will match, regardless of the header's value.

regex
*(optional, boolean)* Specifies whether the header value is a regular
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.

Can you link out to the grammar: http://en.cppreference.com/w/cpp/regex/ecmascript

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 below

expression or not. Defaults to false. The regex grammar used in the value field
is defined `here <http://en.cppreference.com/w/cpp/regex/ecmascript>`_.

The router will check the request's headers against all the specified
headers in the route config. A match will happen if all the headers in the route are present in
the request with the same values (or based on presence if the ``value`` field is not in the config).
7 changes: 6 additions & 1 deletion docs/configuration/http_filters/fault_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ actual fault injection further depend on the values of *abort_percent* and
.. code-block:: json

[
{"name": "...", "value": "..."}
{"name": "...", "value": "...", "regex": "..."}
]

name
Expand All @@ -130,6 +130,11 @@ value
absent a request that has the *name* header will match, regardless of the
header's value.

regex
*(optional, boolean)* Specifies whether the header value is a regular expression
or not. Defaults to false. The regex grammar used in the value field
is defined `here <http://en.cppreference.com/w/cpp/regex/ecmascript>`_.

The filter will check the request's headers against all the specified
headers in the filter config. A match will happen if all the headers in the
config are present in the request with the same values (or based on
Expand Down
3 changes: 2 additions & 1 deletion source/common/http/filter/fault_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ FaultFilterConfig::FaultFilterConfig(const Json::Object& json_config, Runtime::L
for (const Json::ObjectPtr& header_map : config_headers) {
// allow header value to be empty, allows matching to be only based on header presence.
fault_filter_headers_.emplace_back(Http::LowerCaseString(header_map->getString("name")),
header_map->getString("value", EMPTY_STRING));
header_map->getString("value", EMPTY_STRING),
header_map->getBoolean("regex", false));
}
}
}
Expand Down
22 changes: 14 additions & 8 deletions source/common/router/config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,20 @@ Upstream::ResourcePriority ConfigUtility::parsePriority(const Json::Object& conf
}
}

bool ConfigUtility::matchHeaders(const Http::HeaderMap& headers,
const std::vector<HeaderData> request_headers) {
bool ConfigUtility::matchHeaders(const Http::HeaderMap& request_headers,
const std::vector<HeaderData> config_headers) {
bool matches = true;

if (!request_headers.empty()) {
for (const HeaderData& header_data : request_headers) {
const Http::HeaderEntry* header = headers.get(header_data.name_);
if (header_data.value_ == EMPTY_STRING) {
if (!config_headers.empty()) {
for (const HeaderData& cfg_header_data : config_headers) {
const Http::HeaderEntry* header = request_headers.get(cfg_header_data.name_);
if (cfg_header_data.value_.empty()) {
matches &= (header != nullptr);
} else if (!cfg_header_data.is_regex_) {
matches &= (header != nullptr) && (header->value() == cfg_header_data.value_.c_str());
} else {
matches &= (header != nullptr) && (header->value() == header_data.value_.c_str());
matches &= (header != nullptr) &&
std::regex_match(header->value().c_str(), cfg_header_data.regex_pattern_);
}
if (!matches) {
break;
Expand Down Expand Up @@ -91,8 +94,11 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, const Json:
std::vector<Json::ObjectPtr> config_headers = route.getObjectArray("headers");
for (const Json::ObjectPtr& header_map : config_headers) {
// allow header value to be empty, allows matching to be only based on header presence.
// Regex is an opt-in. Unless explicitly mentioned, we will use header values for exact string
// matches.
config_headers_.emplace_back(Http::LowerCaseString(header_map->getString("name")),
header_map->getString("value", EMPTY_STRING));
header_map->getString("value", EMPTY_STRING),
header_map->getBoolean("regex", false));
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions source/common/router/config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ class SslRedirector : public RedirectEntry {
class ConfigUtility {
public:
struct HeaderData {
HeaderData(const Http::LowerCaseString& name, const std::string& value)
: name_(name), value_(value) {}
HeaderData(const Http::LowerCaseString& name, const std::string& value, const bool is_regex)
: name_(name), value_(value), regex_pattern_(value_, std::regex::optimize),
is_regex_(is_regex) {}

const Http::LowerCaseString name_;
const std::string value_;
const std::regex regex_pattern_;
const bool is_regex_;
};

/**
Expand Down
29 changes: 28 additions & 1 deletion test/common/router/config_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#include "common/json/json_loader.h"
#include "common/router/config_impl.h"

#include "test/mocks/upstream/mocks.h"
#include "test/mocks/runtime/mocks.h"
#include "test/mocks/upstream/mocks.h"
#include "test/test_common/utility.h"

using testing::_;
Expand Down Expand Up @@ -436,6 +436,20 @@ TEST(RouteMatcherTest, HeaderMatchedRouting) {
{"name": "test_header_presence"}
]
},
{
"prefix": "/",
"cluster": "local_service_with_header_pattern_set_regex",
"headers" : [
{"name": "test_header_pattern", "value": "^user=test-\\d+$", "regex": true}
]
},
{
"prefix": "/",
"cluster": "local_service_with_header_pattern_unset_regex",
"headers" : [
{"name": "test_header_pattern", "value": "^customer=test-\\d+$"}
]
},
{
"prefix": "/",
"cluster": "local_service_without_headers"
Expand Down Expand Up @@ -484,6 +498,19 @@ TEST(RouteMatcherTest, HeaderMatchedRouting) {
EXPECT_EQ("local_service_with_empty_headers",
config.routeForRequest(headers, 0)->clusterName());
}

{
Http::TestHeaderMapImpl headers = genHeaders("www.lyft.com", "/", "GET");
headers.addViaCopy("test_header_pattern", "user=test-1223");
EXPECT_EQ("local_service_with_header_pattern_set_regex",
config.routeForRequest(headers, 0)->clusterName());
}

{
Http::TestHeaderMapImpl headers = genHeaders("www.lyft.com", "/", "GET");
headers.addViaCopy("test_header_pattern", "customer=test-1223");
EXPECT_EQ("local_service_without_headers", config.routeForRequest(headers, 0)->clusterName());
}
}

TEST(RouteMatcherTest, ContentType) {
Expand Down