-
Notifications
You must be signed in to change notification settings - Fork 5.5k
ratelimit: add support for x-ratelimit-* headers in local rate limiting #18157
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
Changes from 10 commits
1940c6c
5482d0d
e5230d9
a71c1ca
b356032
36c31d6
98101be
0fe03e8
612e6f1
47c9f6d
92d18f2
5f26554
20907cc
1eafb85
ba6f194
266e72a
10ba430
39e98c2
bee36ef
5a9b778
e44d0ed
0e2daa5
a458994
56787c8
e3bd3be
8f6d427
a4e76c0
c2216e7
ff59453
e897ad6
d324244
f52ea50
5e3977d
1928a6f
c41acab
0be82da
f4e540b
bda1a88
afe2035
d485bf0
3993927
978ed0a
2bd01c1
a52570b
85c5c3e
d23c9a3
a78997b
67ea8ae
9afce6f
8b043ef
c601b82
6a57280
31c7daa
cdb1471
124f0ef
4b74159
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; | |
| // Local Rate limit :ref:`configuration overview <config_http_filters_local_rate_limit>`. | ||
| // [#extension: envoy.filters.http.local_ratelimit] | ||
|
|
||
| // [#next-free-field: 12] | ||
| // [#next-free-field: 13] | ||
| message LocalRateLimit { | ||
| // The human readable prefix to use when emitting stats. | ||
| string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; | ||
|
|
@@ -106,4 +106,21 @@ message LocalRateLimit { | |
| // one to rate limit requests on a per connection basis. | ||
| // If unspecified, the default value is false. | ||
| bool local_rate_limit_per_downstream_connection = 11; | ||
|
|
||
| // Defines the standard version to use for X-RateLimit headers emitted by the filter: | ||
| // | ||
| // * ``X-RateLimit-Limit`` - indicates the request-quota associated to the | ||
| // client in the current time-window followed by the description of the | ||
| // quota policy. The value is returned by the maximum tokens of the token bucket. | ||
| // * ``X-RateLimit-Remaining`` - indicates the remaining requests in the | ||
| // current time-window. The value is returned by the remaining tokens in the token bucket. | ||
| // * ``X-RateLimit-Reset`` - indicates the number of seconds until reset of | ||
| // the current time-window. The value is returned by the remaining fill interval of the token bucket. | ||
| // | ||
| // For more information about the headers specification see selected version of | ||
| // the `draft RFC <https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html>`_. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this explanation of the v3 draft go on the enum, not where the enum is used? If we end up adding more values to the enum then we'd need to change this comment I imagine?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds reasonable, I will move these comments to the enum. |
||
| // | ||
| // Disabled by default. | ||
| common.ratelimit.v3.XRateLimitHeadersRFCVersion enable_x_ratelimit_headers = 12 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe just
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I will changed to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some thoughts, I think it is worth considering since I want to align with |
||
| [(validate.rules).enum = {defined_only: true}]; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ syntax = "proto3"; | |
| package envoy.extensions.filters.http.ratelimit.v3; | ||
|
|
||
| import "envoy/config/ratelimit/v3/rls.proto"; | ||
| import "envoy/extensions/common/ratelimit/v3/ratelimit.proto"; | ||
|
|
||
| import "google/protobuf/duration.proto"; | ||
|
|
||
|
|
@@ -24,15 +25,6 @@ message RateLimit { | |
| option (udpa.annotations.versioning).previous_message_type = | ||
| "envoy.config.filter.http.rate_limit.v2.RateLimit"; | ||
|
|
||
| // Defines the version of the standard to use for X-RateLimit headers. | ||
| enum XRateLimitHeadersRFCVersion { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be deprecated. |
||
| // X-RateLimit headers disabled. | ||
| OFF = 0; | ||
|
|
||
| // Use `draft RFC Version 03 <https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html>`_. | ||
| DRAFT_VERSION_03 = 1; | ||
| } | ||
|
|
||
| // The rate limit domain to use when calling the rate limit service. | ||
| string domain = 1 [(validate.rules).string = {min_len: 1}]; | ||
|
|
||
|
|
@@ -96,7 +88,7 @@ message RateLimit { | |
| // the `draft RFC <https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html>`_. | ||
| // | ||
| // Disabled by default. | ||
| XRateLimitHeadersRFCVersion enable_x_ratelimit_headers = 8 | ||
| common.ratelimit.v3.XRateLimitHeadersRFCVersion enable_x_ratelimit_headers = 8 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately this breaks backwards compatibility, and therefore the field must be deprecated and a new field with the new type should be introduced. @htuch extracting the enum to a common place (see:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for the local rate limiting, put it in the common location and use it there. Either deprecate or eave a TODO to cleanup for global. I'm not super keen on API dependencies between extensions, since we're creating an unnecessary dependency in build/protos.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion, if we treat local rate limit and rate limit as two independent extensions (like if we can decouple envoy and extensions someday in the future), both local rate limit and rate limit should maintain their own enum values. But if we consider the local rate limit and rate limit are inseparable, extract common enum is a good choice.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My take on this is that similar to the original issue (#15293) that requested a local rate-limiting feature support similar to what the global rate-limiting does, then in the future they should be in-sync. |
||
| [(validate.rules).enum = {defined_only: true}]; | ||
|
|
||
| // Disables emitting the :ref:`x-envoy-ratelimited<config_http_filters_router_x-envoy-ratelimited>` header | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| #include "source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h" | ||
|
|
||
| #include <chrono> | ||
|
|
||
| #include "source/common/protobuf/utility.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
@@ -12,7 +14,8 @@ LocalRateLimiterImpl::LocalRateLimiterImpl( | |
| const std::chrono::milliseconds fill_interval, const uint32_t max_tokens, | ||
| const uint32_t tokens_per_fill, Event::Dispatcher& dispatcher, | ||
| const Protobuf::RepeatedPtrField< | ||
| envoy::extensions::common::ratelimit::v3::LocalRateLimitDescriptor>& descriptors) | ||
| envoy::extensions::common::ratelimit::v3::LocalRateLimitDescriptor>& descriptors, | ||
| bool init_fill_time) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like you always give
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, in local rate limit network filter,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we know why this is the case? What is the implication of not initializing the fill time at ctor time?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even though this is used for calculating the default per-route
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, it is a mistake, and I suppose it is because I was not not familiar about the time system in the test before. There is a assert failure threw by the test shows there is an inconsistent time system. And today I find the problem is only in the test code, not in the source code. But I cannot fix the test. There is a conflict between the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is already a TODO(#4160) in If that is a lot of work to do or if that doesn't work, a quick workaround off top of my head (just for ideas not necessary to be the final solution): wrapping this line under if condition like below: This implicitly limits this code path to local descriptor override case which is only available in HTTP filter.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, I do not think it might work. In fact, a HTTP filter should load time to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. |
||
| : fill_timer_(fill_interval > std::chrono::milliseconds(0) | ||
| ? dispatcher.createTimer([this] { onFillTimer(); }) | ||
| : nullptr), | ||
|
|
@@ -25,6 +28,9 @@ LocalRateLimiterImpl::LocalRateLimiterImpl( | |
| token_bucket_.tokens_per_fill_ = tokens_per_fill; | ||
| token_bucket_.fill_interval_ = absl::FromChrono(fill_interval); | ||
| tokens_.tokens_ = max_tokens; | ||
| if (init_fill_time) { | ||
| tokens_.fill_time_ = time_source_.monotonicTime(); | ||
| } | ||
|
|
||
| if (fill_timer_) { | ||
| fill_timer_->enableTimer(fill_interval); | ||
|
|
@@ -68,6 +74,7 @@ LocalRateLimiterImpl::~LocalRateLimiterImpl() { | |
|
|
||
| void LocalRateLimiterImpl::onFillTimer() { | ||
| onFillTimerHelper(tokens_, token_bucket_); | ||
| tokens_.fill_time_ = time_source_.monotonicTime(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is better to move this line into function Then, we can remove
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is reasonable, I will change it. |
||
| onFillTimerDescriptorHelper(); | ||
| fill_timer_->enableTimer(absl::ToChronoMilliseconds(token_bucket_.fill_interval_)); | ||
| } | ||
|
|
@@ -136,6 +143,51 @@ bool LocalRateLimiterImpl::requestAllowed( | |
| return requestAllowedHelper(tokens_); | ||
| } | ||
|
|
||
| uint32_t LocalRateLimiterImpl::maxTokens( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per the previous discussion on this maybe we should have these functions be called
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure if we need this change because it would make the method names very long.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We tend to favor longer names if it helps readability, but it's probably ok as is |
||
| absl::Span<const RateLimit::LocalDescriptor> request_descriptors) const { | ||
| if (!descriptors_.empty() && !request_descriptors.empty()) { | ||
| for (const auto& request_descriptor : request_descriptors) { | ||
| auto it = descriptors_.find(request_descriptor); | ||
| if (it != descriptors_.end()) { | ||
| return it->token_bucket_.max_tokens_; | ||
| } | ||
| } | ||
| } | ||
| return token_bucket_.max_tokens_; | ||
| } | ||
|
Comment on lines
+146
to
+160
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be finding the max of all the descriptors? Seems like it's finding the max of the first descriptor we know about? Docs might be good here if this is the intended implementation, I'm not that familiar with this code and seeing the impl for a function called
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1, it seems that we should return the max token of all matched request descriptors?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, each request will correspond to a certain descriptor, and these descriptors have their own token bucket. So what we need to do here is to find the corresponding token bucket and return its
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the explanation. Just for my own learning purpose: My previous comment about max_token was referring to such case. But I could be just wrong as I am not that familiar with code here.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NVM. I found the answer:) It might be a good idea to add this comment to the code when you move this pattern into a helper method? in case other people are wondering this as well.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I will add the comment. |
||
|
|
||
| uint32_t LocalRateLimiterImpl::remainingTokens( | ||
| absl::Span<const RateLimit::LocalDescriptor> request_descriptors) const { | ||
| if (!descriptors_.empty() && !request_descriptors.empty()) { | ||
| for (const auto& request_descriptor : request_descriptors) { | ||
| auto it = descriptors_.find(request_descriptor); | ||
| if (it != descriptors_.end()) { | ||
| return it->token_state_->tokens_.load(std::memory_order_relaxed); | ||
| } | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This pattern of finding the first matching descriptor seems to be the same for these three functions, so maybe a helper to DRY this up would help readability?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I will add a general method for finding the descriptor. |
||
| return tokens_.tokens_.load(std::memory_order_relaxed); | ||
| } | ||
|
|
||
| uint32_t LocalRateLimiterImpl::remainingFillInterval( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am curious why you want to cast it to uint32_t here? It seems quite natural for duration(i.e., not negative) in seconds to be int64_t
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think you are right. I will fix it. |
||
| absl::Span<const RateLimit::LocalDescriptor> request_descriptors) const { | ||
| using namespace std::literals; | ||
|
|
||
| if (!descriptors_.empty() && !request_descriptors.empty()) { | ||
| for (const auto& request_descriptor : request_descriptors) { | ||
| auto it = descriptors_.find(request_descriptor); | ||
| if (it != descriptors_.end()) { | ||
| return static_cast<uint32_t>(absl::ToInt64Seconds( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there some expected bound to the result of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have changed the return type from
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: It might be a good idea to assert like I understand the correctness of this calculation is guaranteed by the check below in but it is seems safer to have assert in case something goes wrong in the future.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I will add a simple assert here. |
||
| it->token_bucket_.fill_interval_ - | ||
| absl::Seconds((time_source_.monotonicTime() - it->token_state_->fill_time_) / 1s))); | ||
| } | ||
| } | ||
| } | ||
| return static_cast<uint32_t>(absl::ToInt64Seconds( | ||
| token_bucket_.fill_interval_ - | ||
| absl::Seconds((time_source_.monotonicTime() - tokens_.fill_time_) / 1s))); | ||
| } | ||
|
|
||
| } // namespace LocalRateLimit | ||
| } // namespace Common | ||
| } // namespace Filters | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,11 @@ | ||
| #include "source/extensions/filters/http/local_ratelimit/local_ratelimit.h" | ||
|
|
||
| #include <memory> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "envoy/extensions/common/ratelimit/v3/ratelimit.pb.h" | ||
| #include "envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.pb.h" | ||
| #include "envoy/http/codes.h" | ||
|
|
||
| #include "source/common/http/utility.h" | ||
|
|
@@ -17,6 +20,26 @@ const std::string& PerConnectionRateLimiter::key() { | |
| CONSTRUCT_ON_FIRST_USE(std::string, "per_connection_local_rate_limiter"); | ||
| } | ||
|
|
||
| LocalRateLimitRequestDescriptorsQueue::LocalRateLimitRequestDescriptorsQueue() | ||
| : request_descriptors_queue_( | ||
| std::queue<absl::optional<std::vector<RateLimit::LocalDescriptor>>>()) {} | ||
|
|
||
| void LocalRateLimitRequestDescriptorsQueue::push( | ||
| absl::optional<std::vector<RateLimit::LocalDescriptor>> request_descriptors) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I Imagine we don't need to have optional here. Do you consider just avoiding
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still need an optional here. In any request the filter is not enabled, we will not attach x-ratelimit-* headers. |
||
| request_descriptors_queue_.push(request_descriptors); | ||
| } | ||
|
|
||
| absl::optional<std::vector<RateLimit::LocalDescriptor>> | ||
| LocalRateLimitRequestDescriptorsQueue::pop() { | ||
| auto request_descriptors = std::move(request_descriptors_queue_.front()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you are using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, I will add a assert in the corresponding method. |
||
| request_descriptors_queue_.pop(); | ||
| return request_descriptors; | ||
| } | ||
|
|
||
| const std::string& LocalRateLimitRequestDescriptorsQueue::key() { | ||
| CONSTRUCT_ON_FIRST_USE(std::string, "local_rate_limit_requestdescriptors_queue"); | ||
| } | ||
|
|
||
| FilterConfig::FilterConfig( | ||
| const envoy::extensions::filters::http::local_ratelimit::v3::LocalRateLimit& config, | ||
| const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher, Stats::Scope& scope, | ||
|
|
@@ -30,7 +53,7 @@ FilterConfig::FilterConfig( | |
| descriptors_(config.descriptors()), | ||
| rate_limit_per_connection_(config.local_rate_limit_per_downstream_connection()), | ||
| rate_limiter_(Filters::Common::LocalRateLimit::LocalRateLimiterImpl( | ||
| fill_interval_, max_tokens_, tokens_per_fill_, dispatcher, descriptors_)), | ||
| fill_interval_, max_tokens_, tokens_per_fill_, dispatcher, descriptors_, true)), | ||
| local_info_(local_info), runtime_(runtime), | ||
| filter_enabled_( | ||
| config.has_filter_enabled() | ||
|
|
@@ -47,7 +70,9 @@ FilterConfig::FilterConfig( | |
| request_headers_parser_(Envoy::Router::HeaderParser::configure( | ||
| config.request_headers_to_add_when_not_enforced())), | ||
| stage_(static_cast<uint64_t>(config.stage())), | ||
| has_descriptors_(!config.descriptors().empty()) { | ||
| has_descriptors_(!config.descriptors().empty()), | ||
| enable_x_rate_limit_headers_(config.enable_x_ratelimit_headers() == | ||
| envoy::extensions::common::ratelimit::v3::DRAFT_VERSION_03) { | ||
| // Note: no token bucket is fine for the global config, which would be the case for enabling | ||
| // the filter globally but disabled and then applying limits at the virtual host or | ||
| // route level. At the virtual or route level, it makes no sense to have an no token | ||
|
|
@@ -63,6 +88,21 @@ bool FilterConfig::requestAllowed( | |
| return rate_limiter_.requestAllowed(request_descriptors); | ||
| } | ||
|
|
||
| uint32_t | ||
| FilterConfig::maxTokens(absl::Span<const RateLimit::LocalDescriptor> request_descriptors) const { | ||
| return rate_limiter_.maxTokens(request_descriptors); | ||
| } | ||
|
|
||
| uint32_t FilterConfig::remainingTokens( | ||
| absl::Span<const RateLimit::LocalDescriptor> request_descriptors) const { | ||
| return rate_limiter_.remainingTokens(request_descriptors); | ||
| } | ||
|
|
||
| uint32_t FilterConfig::remainingFillInterval( | ||
| absl::Span<const RateLimit::LocalDescriptor> request_descriptors) const { | ||
| return rate_limiter_.remainingFillInterval(request_descriptors); | ||
| } | ||
|
|
||
| LocalRateLimitStats FilterConfig::generateStats(const std::string& prefix, Stats::Scope& scope) { | ||
| const std::string final_prefix = prefix + ".http_local_rate_limit"; | ||
| return {ALL_LOCAL_RATE_LIMIT_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))}; | ||
|
|
@@ -80,6 +120,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, | |
| const auto* config = getConfig(); | ||
|
|
||
| if (!config->enabled()) { | ||
| pushRequestDescriptors(absl::nullopt); | ||
| return Http::FilterHeadersStatus::Continue; | ||
| } | ||
|
|
||
|
|
@@ -89,6 +130,7 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, | |
| if (config->hasDescriptors()) { | ||
| populateDescriptors(descriptors, headers); | ||
| } | ||
| pushRequestDescriptors(descriptors); | ||
|
|
||
| if (requestAllowed(descriptors)) { | ||
| config->stats().ok_.inc(); | ||
|
|
@@ -115,13 +157,77 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, | |
| return Http::FilterHeadersStatus::StopIteration; | ||
| } | ||
|
|
||
| Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) { | ||
| const auto* config = getConfig(); | ||
|
|
||
| if (config->enableXRateLimitHeaders()) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to also check Also, can we avoid
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, and it is changed. |
||
| auto descriptors = decoder_callbacks_->streamInfo() | ||
| .filterState() | ||
| ->getDataMutable<LocalRateLimitRequestDescriptorsQueue>( | ||
| LocalRateLimitRequestDescriptorsQueue::key()) | ||
| .pop(); | ||
| if (descriptors.has_value()) { | ||
| auto limit = maxTokens(descriptors.value()); | ||
| auto remaining = remainingTokens(descriptors.value()); | ||
| auto reset = remainingFillInterval(descriptors.value()); | ||
|
|
||
| headers.addCopy(Http::LowerCaseString{"ratelimit-limit"}, limit); | ||
| headers.addCopy(Http::LowerCaseString{"ratelimit-remaining"}, remaining); | ||
| headers.addCopy(Http::LowerCaseString{"ratelimit-reset"}, reset); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The remote ratelimit filter has
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is reasonable. Shall we move source/extensions/filters/http/ratelimit/ratelimit_headers to someplace for code sharing? @adisuissa
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, shared code should be in a common location (refactoring the code, not the API, shouldn't cause an issue).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And there is another question, where shall we move the ratelimit_headers? I prefer to move to a new file source/extensions/common/ratelimit/ratelimit_headers.h. Do you have any opinion? @adisuissa
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @adisuissa Any thoughts?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes a common file SGTM. |
||
| } | ||
|
|
||
| return Http::FilterHeadersStatus::Continue; | ||
| } | ||
|
|
||
| bool Filter::requestAllowed(absl::Span<const RateLimit::LocalDescriptor> request_descriptors) { | ||
| const auto* config = getConfig(); | ||
| return config->rateLimitPerConnection() | ||
| ? getPerConnectionRateLimiter().requestAllowed(request_descriptors) | ||
| : config->requestAllowed(request_descriptors); | ||
| } | ||
|
|
||
| uint32_t Filter::maxTokens(absl::Span<const RateLimit::LocalDescriptor> request_descriptors) { | ||
| const auto* config = getConfig(); | ||
| return config->rateLimitPerConnection() | ||
| ? getPerConnectionRateLimiter().maxTokens(request_descriptors) | ||
| : config->maxTokens(request_descriptors); | ||
| } | ||
|
|
||
| uint32_t Filter::remainingTokens(absl::Span<const RateLimit::LocalDescriptor> request_descriptors) { | ||
| const auto* config = getConfig(); | ||
| return config->rateLimitPerConnection() | ||
| ? getPerConnectionRateLimiter().remainingTokens(request_descriptors) | ||
| : config->remainingTokens(request_descriptors); | ||
| } | ||
|
|
||
| uint32_t | ||
| Filter::remainingFillInterval(absl::Span<const RateLimit::LocalDescriptor> request_descriptors) { | ||
| const auto* config = getConfig(); | ||
| return config->rateLimitPerConnection() | ||
| ? getPerConnectionRateLimiter().remainingFillInterval(request_descriptors) | ||
| : config->remainingFillInterval(request_descriptors); | ||
| } | ||
|
|
||
| void Filter::pushRequestDescriptors( | ||
| absl::optional<std::vector<RateLimit::LocalDescriptor>> request_descriptors) { | ||
| if (!decoder_callbacks_->streamInfo() | ||
| .filterState() | ||
| ->hasData<LocalRateLimitRequestDescriptorsQueue>( | ||
| LocalRateLimitRequestDescriptorsQueue::key())) { | ||
| decoder_callbacks_->streamInfo().filterState()->setData( | ||
| LocalRateLimitRequestDescriptorsQueue::key(), | ||
| std::make_unique<LocalRateLimitRequestDescriptorsQueue>(), | ||
| StreamInfo::FilterState::StateType::Mutable, StreamInfo::FilterState::LifeSpan::Connection); | ||
| } | ||
|
|
||
| decoder_callbacks_->streamInfo() | ||
| .filterState() | ||
| ->getDataMutable<LocalRateLimitRequestDescriptorsQueue>( | ||
| LocalRateLimitRequestDescriptorsQueue::key()) | ||
| .push(request_descriptors); | ||
| } | ||
|
|
||
| const Filters::Common::LocalRateLimit::LocalRateLimiterImpl& Filter::getPerConnectionRateLimiter() { | ||
| const auto* config = getConfig(); | ||
| ASSERT(config->rateLimitPerConnection()); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the code, this header is
ratelimit-limit(without the X). Should we update the doc here to remove theX-?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This patch is in parallel with #12410, so I think the prefix
X-is reasonable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And, yes, I found I missed the
X-in implementations, thanks for your review.