Skip to content

Commit 71152b7

Browse files
surkimattklein123
authored andcommitted
ratelimit: Add ratelimit custom response headers (#4015)
- Ability to add custom response headers from ratelimit service/filter - For both (LimitStatus::OK and LimitStatus::OverLimit) custom headers are added if RLS service sends headers - For LimitStatus:OK, we temporarily store the headers and add them to the response (via Filter::encodeHeaders()) *Risk Level*: Low *Testing*: unit and integration tests added. Verified with modified github.com/lyft/ratelimit service. Passes "bazel test //test/..." in Linux Signed-off-by: Suresh Kumar <[email protected]>
1 parent 3062874 commit 71152b7

File tree

19 files changed

+370
-38
lines changed

19 files changed

+370
-38
lines changed

api/envoy/service/ratelimit/v2/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ api_proto_library_internal(
77
srcs = ["rls.proto"],
88
has_services = 1,
99
deps = [
10+
"//envoy/api/v2/core:base",
1011
"//envoy/api/v2/core:grpc_service",
1112
"//envoy/api/v2/ratelimit",
1213
],
@@ -16,6 +17,7 @@ api_go_grpc_library(
1617
name = "rls",
1718
proto = ":rls",
1819
deps = [
20+
"//envoy/api/v2/core:base_go_proto",
1921
"//envoy/api/v2/core:grpc_service_go_proto",
2022
"//envoy/api/v2/ratelimit:ratelimit_go_proto",
2123
],

api/envoy/service/ratelimit/v2/rls.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ syntax = "proto3";
33
package envoy.service.ratelimit.v2;
44
option go_package = "v2";
55

6+
import "envoy/api/v2/core/base.proto";
67
import "envoy/api/v2/ratelimit/ratelimit.proto";
78

89
import "validate/validate.proto";
@@ -75,4 +76,6 @@ message RateLimitResponse {
7576
// in the RateLimitRequest. This can be used by the caller to determine which individual
7677
// descriptors failed and/or what the currently configured limits are for all of them.
7778
repeated DescriptorStatus statuses = 2;
79+
// A list of headers to add to the response
80+
repeated envoy.api.v2.core.HeaderValue headers = 3;
7881
}

include/envoy/ratelimit/ratelimit.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ class RequestCallbacks {
3333
virtual ~RequestCallbacks() {}
3434

3535
/**
36-
* Called when a limit request is complete. The resulting status is supplied.
36+
* Called when a limit request is complete. The resulting status and
37+
* response headers are supplied.
3738
*/
38-
virtual void complete(LimitStatus status) PURE;
39+
virtual void complete(LimitStatus status, Http::HeaderMapPtr&& headers) PURE;
3940
};
4041

4142
/**

source/common/http/header_utility.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "common/common/utility.h"
44
#include "common/config/rds_json.h"
5+
#include "common/http/header_map_impl.h"
56
#include "common/protobuf/utility.h"
67

78
#include "absl/strings/match.h"
@@ -115,5 +116,18 @@ bool HeaderUtility::matchHeaders(const Http::HeaderMap& request_headers,
115116
return match != header_data.invert_match_;
116117
}
117118

119+
void HeaderUtility::addHeaders(Http::HeaderMap& headers, const Http::HeaderMap& headers_to_add) {
120+
headers_to_add.iterate(
121+
[](const Http::HeaderEntry& header, void* context) -> Http::HeaderMap::Iterate {
122+
Http::HeaderString k;
123+
k.setCopy(header.key().c_str(), header.key().size());
124+
Http::HeaderString v;
125+
v.setCopy(header.value().c_str(), header.value().size());
126+
static_cast<Http::HeaderMapImpl*>(context)->addViaMove(std::move(k), std::move(v));
127+
return Http::HeaderMap::Iterate::Continue;
128+
},
129+
&headers);
130+
}
131+
118132
} // namespace Http
119133
} // namespace Envoy

source/common/http/header_utility.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ class HeaderUtility {
4444
const std::vector<HeaderData>& config_headers);
4545

4646
static bool matchHeaders(const Http::HeaderMap& request_headers, const HeaderData& config_header);
47+
48+
/**
49+
* Add headers from one HeaderMap to another
50+
* @param headers target where headers will be added
51+
* @param headers_to_add supplies the headers to be added
52+
*/
53+
static void addHeaders(Http::HeaderMap& headers, const Http::HeaderMap& headers_to_add);
4754
};
4855
} // namespace Http
4956
} // namespace Envoy

source/common/ratelimit/ratelimit_impl.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "envoy/stats/scope.h"
1010

1111
#include "common/common/assert.h"
12+
#include "common/http/header_map_impl.h"
1213
#include "common/http/headers.h"
1314

1415
namespace Envoy {
@@ -67,14 +68,23 @@ void GrpcClientImpl::onSuccess(
6768
span.setTag(Constants::get().TraceStatus, Constants::get().TraceOk);
6869
}
6970

70-
callbacks_->complete(status);
71+
if (response->headers_size()) {
72+
Http::HeaderMapPtr headers = std::make_unique<Http::HeaderMapImpl>();
73+
for (const auto& h : response->headers()) {
74+
headers->addCopy(Http::LowerCaseString(h.key()), h.value());
75+
}
76+
callbacks_->complete(status, std::move(headers));
77+
} else {
78+
callbacks_->complete(status, nullptr);
79+
}
80+
7181
callbacks_ = nullptr;
7282
}
7383

7484
void GrpcClientImpl::onFailure(Grpc::Status::GrpcStatus status, const std::string&,
7585
Tracing::Span&) {
7686
ASSERT(status != Grpc::Status::GrpcStatus::Ok);
77-
callbacks_->complete(LimitStatus::Error);
87+
callbacks_->complete(LimitStatus::Error, nullptr);
7888
callbacks_ = nullptr;
7989
}
8090

source/common/ratelimit/ratelimit_impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class NullClientImpl : public Client {
8787
void cancel() override {}
8888
void limit(RequestCallbacks& callbacks, const std::string&, const std::vector<Descriptor>&,
8989
Tracing::Span&) override {
90-
callbacks.complete(LimitStatus::OK);
90+
callbacks.complete(LimitStatus::OK, nullptr);
9191
}
9292
};
9393

source/extensions/filters/http/ratelimit/config.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Http::FilterFactoryCb RateLimitFilterConfig::createFilterFactoryFromProtoTyped(
2626
const uint32_t timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config, timeout, 20);
2727
return
2828
[filter_config, timeout_ms, &context](Http::FilterChainFactoryCallbacks& callbacks) -> void {
29-
callbacks.addStreamDecoderFilter(std::make_shared<Filter>(
29+
callbacks.addStreamFilter(std::make_shared<Filter>(
3030
filter_config, context.rateLimitClient(std::chrono::milliseconds(timeout_ms))));
3131
};
3232
}

source/extensions/filters/http/ratelimit/ratelimit.cc

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "common/common/enum_to_int.h"
1010
#include "common/common/fmt.h"
1111
#include "common/http/codes.h"
12+
#include "common/http/header_utility.h"
1213
#include "common/router/config_impl.h"
1314

1415
namespace Envoy {
@@ -87,15 +88,35 @@ void Filter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callb
8788
callbacks_ = &callbacks;
8889
}
8990

91+
Http::FilterHeadersStatus Filter::encode100ContinueHeaders(Http::HeaderMap&) {
92+
return Http::FilterHeadersStatus::Continue;
93+
}
94+
95+
Http::FilterHeadersStatus Filter::encodeHeaders(Http::HeaderMap& headers, bool) {
96+
addHeaders(headers);
97+
return Http::FilterHeadersStatus::Continue;
98+
}
99+
100+
Http::FilterDataStatus Filter::encodeData(Buffer::Instance&, bool) {
101+
return Http::FilterDataStatus::Continue;
102+
}
103+
104+
Http::FilterTrailersStatus Filter::encodeTrailers(Http::HeaderMap&) {
105+
return Http::FilterTrailersStatus::Continue;
106+
}
107+
108+
void Filter::setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks&) {}
109+
90110
void Filter::onDestroy() {
91111
if (state_ == State::Calling) {
92112
state_ = State::Complete;
93113
client_->cancel();
94114
}
95115
}
96116

97-
void Filter::complete(RateLimit::LimitStatus status) {
117+
void Filter::complete(RateLimit::LimitStatus status, Http::HeaderMapPtr&& headers) {
98118
state_ = State::Complete;
119+
headers_to_add_ = std::move(headers);
99120

100121
switch (status) {
101122
case RateLimit::LimitStatus::OK:
@@ -123,7 +144,8 @@ void Filter::complete(RateLimit::LimitStatus status) {
123144
if (status == RateLimit::LimitStatus::OverLimit &&
124145
config_->runtime().snapshot().featureEnabled("ratelimit.http_filter_enforcing", 100)) {
125146
state_ = State::Responded;
126-
callbacks_->sendLocalReply(Http::Code::TooManyRequests, "", nullptr);
147+
callbacks_->sendLocalReply(Http::Code::TooManyRequests, "",
148+
[this](Http::HeaderMap& headers) { addHeaders(headers); });
127149
callbacks_->requestInfo().setResponseFlag(RequestInfo::ResponseFlag::RateLimited);
128150
} else if (!initiating_call_) {
129151
callbacks_->continueDecoding();
@@ -147,6 +169,13 @@ void Filter::populateRateLimitDescriptors(const Router::RateLimitPolicy& rate_li
147169
}
148170
}
149171

172+
void Filter::addHeaders(Http::HeaderMap& headers) {
173+
if (headers_to_add_) {
174+
Http::HeaderUtility::addHeaders(headers, *headers_to_add_);
175+
headers_to_add_ = nullptr;
176+
}
177+
}
178+
150179
} // namespace RateLimitFilter
151180
} // namespace HttpFilters
152181
} // namespace Extensions

source/extensions/filters/http/ratelimit/ratelimit.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ typedef std::shared_ptr<FilterConfig> FilterConfigSharedPtr;
7474
* HTTP rate limit filter. Depending on the route configuration, this filter calls the global
7575
* rate limiting service before allowing further filter iteration.
7676
*/
77-
class Filter : public Http::StreamDecoderFilter, public RateLimit::RequestCallbacks {
77+
class Filter : public Http::StreamFilter, public RateLimit::RequestCallbacks {
7878
public:
7979
Filter(FilterConfigSharedPtr config, RateLimit::ClientPtr&& client)
8080
: config_(config), client_(std::move(client)) {}
@@ -88,15 +88,23 @@ class Filter : public Http::StreamDecoderFilter, public RateLimit::RequestCallba
8888
Http::FilterTrailersStatus decodeTrailers(Http::HeaderMap& trailers) override;
8989
void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override;
9090

91+
// Http::StreamEncoderFilter
92+
Http::FilterHeadersStatus encode100ContinueHeaders(Http::HeaderMap& headers) override;
93+
Http::FilterHeadersStatus encodeHeaders(Http::HeaderMap& headers, bool end_stream) override;
94+
Http::FilterDataStatus encodeData(Buffer::Instance& data, bool end_stream) override;
95+
Http::FilterTrailersStatus encodeTrailers(Http::HeaderMap& trailers) override;
96+
void setEncoderFilterCallbacks(Http::StreamEncoderFilterCallbacks& callbacks) override;
97+
9198
// RateLimit::RequestCallbacks
92-
void complete(RateLimit::LimitStatus status) override;
99+
void complete(RateLimit::LimitStatus status, Http::HeaderMapPtr&& headers) override;
93100

94101
private:
95102
void initiateCall(const Http::HeaderMap& headers);
96103
void populateRateLimitDescriptors(const Router::RateLimitPolicy& rate_limit_policy,
97104
std::vector<RateLimit::Descriptor>& descriptors,
98105
const Router::RouteEntry* route_entry,
99106
const Http::HeaderMap& headers) const;
107+
void addHeaders(Http::HeaderMap& headers);
100108

101109
enum class State { NotStarted, Calling, Complete, Responded };
102110

@@ -106,6 +114,7 @@ class Filter : public Http::StreamDecoderFilter, public RateLimit::RequestCallba
106114
State state_{State::NotStarted};
107115
Upstream::ClusterInfoConstSharedPtr cluster_;
108116
bool initiating_call_{};
117+
Http::HeaderMapPtr headers_to_add_;
109118
};
110119

111120
} // namespace RateLimitFilter

0 commit comments

Comments
 (0)