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

Request Header rate limiting Support #94

Merged
merged 10 commits into from
Sep 23, 2016
Merged
Show file tree
Hide file tree
Changes from 7 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
8 changes: 7 additions & 1 deletion docs/configuration/http_conn_man/route_config/route.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,20 @@ Global rate limit :ref:`architecture overview <arch_overview_rate_limit>`.
.. code-block:: json

{
"global": "..."
"global": "...",
"route_key": "..."
}

global
*(optional, boolean)* Specifies whether the global rate limit service should be called for a
request that matches this route. This information is used by the :ref:`rate limit filter
<config_http_filters_rate_limit>` if it is installed. Defaults to false if not specified.

route_key
*(optional, string)* Specifies a descriptor value to be used when rate limiting for a route.
This information is used by :ref:`rate limit filter
<config_http_filters_rate_limit>` if it is installed.

.. _config_http_conn_man_route_table_route_shadow:

Shadow
Expand Down
31 changes: 29 additions & 2 deletions docs/configuration/http_filters/rate_limit_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ Actions
}

type
*(required, string) The type of rate limit action to perform. The currently supported action
type is *service_to_service*.
*(required, string)* The type of rate limit action to perform. The currently supported action
types are *service_to_service* and *request_headers*.

Service to service
^^^^^^^^^^^^^^^^^^
Expand All @@ -60,6 +60,33 @@ The following descriptors are sent:

<local service cluster> is derived from the :option:`--service-cluster` option.

Request Headers
^^^^^^^^^^^^^^^

.. code-block:: json

{
"type": "request_headers",
"header_name": "...",
"descriptor_key" : "..."
}

header_name
*(required, string)* The header name to be queried from the request headers and used to
populate the descriptor value for the *descriptor_key*.

descriptor_key
*(required, string)* The key to use in the descriptor.

The following descriptor is sent when a header contains a key that matches the *header_name*:

* ("<descriptor_key>", "<header_value_queried_from_header>")

If *route_key* is set in the :ref:`route <config_http_conn_man_route_table_route_rate_limit>`, the following
descriptor is sent as well:

* ("route_key", "<route_key>"), ("<descriptor_key>", "<header_value_queried_from_header>")

Statistics
----------

Expand Down
5 changes: 5 additions & 0 deletions include/envoy/router/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ class RateLimitPolicy {
* @return whether the global rate limiting service should be called for the owning route.
*/
virtual bool doGlobalLimiting() const PURE;

/**
* @return the rate limit key for a service, if it exists.
Copy link
Member

Choose a reason for hiding this comment

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

nit: return the route key, if it exists.

*/
virtual const std::string& routeKey() const PURE;
};

/**
Expand Down
1 change: 1 addition & 0 deletions source/common/http/async_client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class AsyncRequestImpl final : public AsyncClient::Request,
struct NullRateLimitPolicy : public Router::RateLimitPolicy {
// Router::RateLimitPolicy
bool doGlobalLimiting() const override { return false; }
const std::string& routeKey() const override { return EMPTY_STRING; }
};

struct NullRetryPolicy : public Router::RetryPolicy {
Expand Down
24 changes: 22 additions & 2 deletions source/common/http/filter/ratelimit.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Http::HeaderMapImpl Filter::TOO_MANY_REQUESTS_HEADER{

void ServiceToServiceAction::populateDescriptors(const Router::RouteEntry& route,
std::vector<::RateLimit::Descriptor>& descriptors,
FilterConfig& config) {
FilterConfig& config, const HeaderMap&) {
// We limit on 2 dimensions.
// 1) All calls to the given cluster.
// 2) Calls to the given cluster and from this cluster.
Expand All @@ -26,6 +26,24 @@ void ServiceToServiceAction::populateDescriptors(const Router::RouteEntry& route
{{{"to_cluster", route.clusterName()}, {"from_cluster", config.localServiceCluster()}}});
}

void RequestHeadersAction::populateDescriptors(const Router::RouteEntry& route,
std::vector<::RateLimit::Descriptor>& descriptors,
FilterConfig&, const HeaderMap& headers) {
std::string header_value = headers.get(header_name_);
if (header_value.empty()) {
return;
}

descriptors.push_back({{{descriptor_key_, header_value}}});

std::string route_key = route.rateLimitPolicy().routeKey();
Copy link
Member

Choose a reason for hiding this comment

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

this does a copy, so do const std::string& or leave it the way you had it before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated.

if (route_key.empty()) {
return;
}

descriptors.push_back({{{"route_key", route_key}, {descriptor_key_, header_value}}});
}

FilterConfig::FilterConfig(const Json::Object& config, const std::string& local_service_cluster,
Stats::Store& stats_store, Runtime::Loader& runtime)
: domain_(config.getString("domain")), local_service_cluster_(local_service_cluster),
Expand All @@ -34,6 +52,8 @@ FilterConfig::FilterConfig(const Json::Object& config, const std::string& local_
std::string type = action.getString("type");
if (type == "service_to_service") {
actions_.emplace_back(new ServiceToServiceAction());
} else if (type == "request_headers") {
actions_.emplace_back(new RequestHeadersAction(action));
} else {
throw EnvoyException(fmt::format("unknown http rate limit filter action '{}'", type));
}
Expand All @@ -49,7 +69,7 @@ FilterHeadersStatus Filter::decodeHeaders(HeaderMap& headers, bool) {
if (route && route->rateLimitPolicy().doGlobalLimiting()) {
std::vector<::RateLimit::Descriptor> descriptors;
for (const ActionPtr& action : config_->actions()) {
action->populateDescriptors(*route, descriptors, *config_);
action->populateDescriptors(*route, descriptors, *config_, headers);
}

if (!descriptors.empty()) {
Expand Down
23 changes: 20 additions & 3 deletions source/common/http/filter/ratelimit.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Action {
*/
virtual void populateDescriptors(const Router::RouteEntry& route,
std::vector<::RateLimit::Descriptor>& descriptors,
FilterConfig& config) PURE;
FilterConfig& config, const HeaderMap& headers) PURE;
};

typedef std::unique_ptr<Action> ActionPtr;
Expand All @@ -39,10 +39,27 @@ class ServiceToServiceAction : public Action {
public:
// Action
void populateDescriptors(const Router::RouteEntry& route,
std::vector<::RateLimit::Descriptor>& descriptors,
FilterConfig& config) override;
std::vector<::RateLimit::Descriptor>& descriptors, FilterConfig& config,
const HeaderMap&) override;
};

/**
* Action for request headers rate limiting.
*/
class RequestHeadersAction : public Action {
public:
RequestHeadersAction(const Json::Object& action)
: header_name_(action.getString("header_name")),
descriptor_key_(action.getString("descriptor_key")) {}
// Action
void populateDescriptors(const Router::RouteEntry& route,
std::vector<::RateLimit::Descriptor>& descriptors, FilterConfig& config,
const HeaderMap& headers) override;

private:
const LowerCaseString header_name_;
const std::string descriptor_key_;
};
/**
* Global configuration for the HTTP rate limit filter.
*/
Expand Down
8 changes: 0 additions & 8 deletions source/common/router/config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ RetryPolicyImpl::RetryPolicyImpl(const Json::Object& config) {
retry_on_ = RetryStateImpl::parseRetryOn(config.getObject("retry_policy").getString("retry_on"));
}

RateLimitPolicyImpl::RateLimitPolicyImpl(const Json::Object& config) {
if (!config.hasObject("rate_limit")) {
return;
}

do_global_limiting_ = config.getObject("rate_limit").getBoolean("global", false);
}

ShadowPolicyImpl::ShadowPolicyImpl(const Json::Object& config) {
if (!config.hasObject("shadow")) {
return;
Expand Down
10 changes: 8 additions & 2 deletions source/common/router/config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,19 @@ class RetryPolicyImpl : public RetryPolicy {
*/
class RateLimitPolicyImpl : public RateLimitPolicy {
public:
RateLimitPolicyImpl(const Json::Object& config);
RateLimitPolicyImpl(const Json::Object& config)
: do_global_limiting_(config.getObject("rate_limit", true).getBoolean("global", false)),
route_key_(config.getObject("rate_limit", true).getString("route_key", "")) {}

// Router::RateLimitPolicy
bool doGlobalLimiting() const override { return do_global_limiting_; }

// Router::RateLimitPolicy
const std::string& routeKey() const override { return route_key_; }

private:
bool do_global_limiting_{};
const bool do_global_limiting_;
const std::string route_key_;
};

/**
Expand Down
Loading