-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Common: Introduce StopAllIteration filter status for decoding and encoding filters #5954
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 3 commits
3835768
9cf21dd
4429d12
94c4d44
eabbeb6
0bd2315
b437b67
50b7e7c
5c9fc64
b3527f7
eaa2ceb
48a3833
ac8a553
f595c23
ee71a1f
6544a7c
3f2656b
6996475
7bf4912
119e166
82588d3
d2839d6
eb25b60
82e02e2
e0726f0
a43ba57
701493a
66ebe16
e9f387a
b7b8113
dd80759
eef5003
bcb917b
5f21a63
e691414
5dfb999
c60f2df
c645f9b
0ddb3af
beb19b8
d875ca4
e7cebab
d02d056
c2becd0
9e2e461
4ca783e
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 |
|---|---|---|
|
|
@@ -33,7 +33,15 @@ enum class FilterHeadersStatus { | |
| StopIteration, | ||
| // Continue iteration to remaining filters, but ignore any subsequent data or trailers. This | ||
| // results in creating a header only request/response. | ||
| ContinueAndEndStream | ||
| ContinueAndEndStream, | ||
| // Stop iterations for headers as well as data and trailers that follows the headers. Returning | ||
| // StopAllTypesIteration means the filter that returns the status and all the filters following | ||
| // that filter stop processing. The filters before are not impacted. continueDecoding() MUST be | ||
| // called if continued filter iteration is desired. To avoid buffering large amount of data, a | ||
| // rate-limiting filter can be added before the filter that can return StopAllTypesIteration. | ||
| // Only used in decoding path. | ||
| // TODO(soya3129): add tests for encoding path, and remove the decoding path condition. | ||
| StopAllTypesIteration | ||
|
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: I would call this Also, I think we likely need
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. Fixed. Thanks! |
||
| }; | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1498,6 +1498,7 @@ void ConnectionManagerImpl::ActiveStreamFilterBase::commonContinue() { | |
| static_cast<const void*>(this)); | ||
| ASSERT(stopped_); | ||
| stopped_ = false; | ||
| stopped_all_ = false; | ||
|
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: Instead 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. Fixed. Thanks! |
||
|
|
||
| // Only resume with do100ContinueHeaders() if we've actually seen a 100-Continue. | ||
| if (parent_.has_continue_headers_ && !continue_headers_continued_) { | ||
|
|
@@ -1553,6 +1554,10 @@ bool ConnectionManagerImpl::ActiveStreamFilterBase::commonHandleAfterHeadersCall | |
| if (status == FilterHeadersStatus::StopIteration) { | ||
| stopped_ = true; | ||
| return false; | ||
| } else if (status == FilterHeadersStatus::StopAllTypesIteration) { | ||
| stopped_ = true; | ||
| stopped_all_ = true; | ||
|
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. Where do we look at
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 checked stopped_all_ value in line 1597 and line 1623 in conn_manager_impl.cc. Let's say there are 3 filters: filter1 ----- filter2 ----- filter3 where filter2::decodeHeaders() returns StopAllIteration. We still allow data to call filter1::decodeData(), and filter2::decodeData(), but will not call filter3::decodeData(). This is achieved by the check filter2::stop_all_ value in line 1597. If filter2::stop_all_ is true, we will buffer data but not call commonContinue(), and because commonHandleAfterDataCallback returns false, filter3::decodeData() will not be called. The same logic applies to trailers, where stop_all_ value is checked in line 1623. But I can be completely wrong. Please let me know the feedback. Thanks!
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. OK I see. I think though we don't want to call any other filter2 methods, including data and trailers. This is how most filters would use this I think, including auth, rate limit, etc. where they make an outbound call as part of the decodeHeaders() path. Right? Assuming ^ is what we want, I think this means that you need to check stop all status before calling filter functions, and then also handle buffer/watermark for data?
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. Ah, I see! I mistakenly thought we didn't allow callbacks for filters after filter2, excluding filter2. Will make the change. Thanks! About stop-all options for data. I thought we want stop-all for headers only at least for now? (#5842 (comment)). But it makes sense to be consistent with data as well. Will make the change and address other comments as well. Thanks for the feedback!
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.
We do want it only for headers, but what I'm saying is that we need to tell the HCM how to handle subsequent data. Do we buffer up to the limit and then error? Or do we buffer and try to apply flow control? Those are the two options.
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. Sorry I misread the comment! My original thoughts was if users don't want to buffer large data or want the flow control feature, they may add a rate-limiting filter before the filter that can return StopAllIteration for headers: "// To avoid buffering large amount of data, a rate-limiting filter can be added before the filter that If users don't care, we can buffer up to the limit and then error. But I think I missed to set decoder_filters_streaming_ to be false in this case. Sorry, I will fix it. Do you think it's reasonable to let users add rate-limiting filter, otherwise, fails when the limit is reached?
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 don't think so. I think it would be more natural given the rest of the API to offer
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 see! Is the suggestion to introduce StopAllIterationAndBuffer and StopAllIterationAndWatermark as decodeHeaders()'s return status (not as data's status)? I think it totally makes sense! Sorry for being slow!! I will change accordingly.
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. Yup exactly! I think these are new headers return codes, and they guide what we do when data comes in. You aren't being slow, this is very tough stuff! :) |
||
| return false; | ||
| } else if (status == FilterHeadersStatus::ContinueAndEndStream) { | ||
| // Set headers_only to true so we know to end early if necessary, | ||
| // but continue filter iteration so we actually write the headers/run the cleanup code. | ||
|
|
@@ -1589,7 +1594,9 @@ bool ConnectionManagerImpl::ActiveStreamFilterBase::commonHandleAfterDataCallbac | |
| if (status == FilterDataStatus::Continue) { | ||
| if (stopped_) { | ||
| commonHandleBufferData(provided_data); | ||
| commonContinue(); | ||
| if (!stopped_all_) { | ||
| commonContinue(); | ||
| } | ||
| return false; | ||
| } else { | ||
| ASSERT(headers_continued_); | ||
|
|
@@ -1613,7 +1620,9 @@ bool ConnectionManagerImpl::ActiveStreamFilterBase::commonHandleAfterTrailersCal | |
|
|
||
| if (status == FilterTrailersStatus::Continue) { | ||
| if (stopped_) { | ||
| commonContinue(); | ||
| if (!stopped_all_) { | ||
| commonContinue(); | ||
| } | ||
| return false; | ||
| } else { | ||
| ASSERT(headers_continued_); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| #include "envoy/registry/registry.h" | ||
| #include "envoy/server/filter_config.h" | ||
|
|
||
| #include "extensions/filters/http/common/empty_http_filter_config.h" | ||
| #include "extensions/filters/http/common/pass_through_filter.h" | ||
|
|
||
| #include "test/integration/filters/common.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
||
| // A filter that only allows decodeData() to be called once. Multiple calls will result in assert | ||
| // failure. | ||
| class CallDecodeDataOnceFilter : public Http::PassThroughFilter { | ||
| public: | ||
| constexpr static char name[] = "call-decodedata-once-filter"; | ||
|
|
||
| Http::FilterHeadersStatus decodeHeaders(Http::HeaderMap&, bool) override { | ||
| return Http::FilterHeadersStatus::Continue; | ||
| } | ||
|
|
||
| Http::FilterDataStatus decodeData(Buffer::Instance&, bool) override { | ||
| ASSERT(call_count_ == 0); | ||
| call_count_++; | ||
| return Http::FilterDataStatus::Continue; | ||
| } | ||
|
|
||
| Http::FilterTrailersStatus decodeTrailers(Http::HeaderMap&) override { | ||
| return Http::FilterTrailersStatus::Continue; | ||
| } | ||
|
|
||
| private: | ||
| int call_count_ = 0; | ||
| }; | ||
|
|
||
| constexpr char CallDecodeDataOnceFilter::name[]; | ||
| static Registry::RegisterFactory<SimpleFilterConfig<CallDecodeDataOnceFilter>, | ||
| Server::Configuration::NamedHttpFilterConfigFactory> | ||
| register_; | ||
|
|
||
| } // namespace Envoy |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| #include <chrono> | ||
| #include <string> | ||
|
|
||
| #include "envoy/event/timer.h" | ||
| #include "envoy/http/filter.h" | ||
| #include "envoy/registry/registry.h" | ||
| #include "envoy/server/filter_config.h" | ||
|
|
||
| #include "extensions/filters/http/common/empty_http_filter_config.h" | ||
| #include "extensions/filters/http/common/pass_through_filter.h" | ||
|
|
||
| #include "test/integration/filters/common.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
||
| // A filter returns StopAllTypesIteration for headers. The iteration continues when end_stream is | ||
| // received. | ||
| class DecodeHeadersReturnStopAllFilter : public Http::PassThroughFilter { | ||
| public: | ||
| constexpr static char name[] = "decode-headers-return-stop-all-filter"; | ||
|
|
||
| // Returns Http::FilterHeadersStatus::StopAllTypesIteration for headers. | ||
| Http::FilterHeadersStatus decodeHeaders(Http::HeaderMap&, bool) override { | ||
| return Http::FilterHeadersStatus::StopAllTypesIteration; | ||
| } | ||
|
|
||
| // Continues the iteration 2s after end_stream is received. | ||
| Http::FilterDataStatus decodeData(Buffer::Instance&, bool end_stream) override { | ||
| call_count_++; | ||
| if (end_stream) { | ||
| // Verifies decodeData() is called more than once. | ||
| ASSERT(call_count_ > 1); | ||
| continued_ = true; | ||
| createTimerForContinue(); | ||
| } | ||
| return Http::FilterDataStatus::Continue; | ||
| } | ||
|
|
||
| // Continues the iteration 2s after trailers are received. | ||
| Http::FilterTrailersStatus decodeTrailers(Http::HeaderMap&) override { | ||
| if (!continued_) { | ||
| // Verifies decodeData() is called more than once. | ||
| ASSERT(call_count_ > 1); | ||
| createTimerForContinue(); | ||
| } | ||
| return Http::FilterTrailersStatus::Continue; | ||
| } | ||
|
|
||
| private: | ||
| // Creates a timer to continue iteration after 2s. | ||
| void createTimerForContinue() { | ||
| delay_timer_ = decoder_callbacks_->dispatcher().createTimer( | ||
| [this]() -> void { decoder_callbacks_->continueDecoding(); }); | ||
| delay_timer_->enableTimer(std::chrono::seconds(2)); | ||
| } | ||
|
|
||
| Event::TimerPtr delay_timer_; | ||
| int call_count_ = 0; | ||
| bool continued_ = false; | ||
| }; | ||
|
|
||
| constexpr char DecodeHeadersReturnStopAllFilter::name[]; | ||
| static Registry::RegisterFactory<SimpleFilterConfig<DecodeHeadersReturnStopAllFilter>, | ||
| Server::Configuration::NamedHttpFilterConfigFactory> | ||
| register_; | ||
|
|
||
| } // namespace Envoy |
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.
would be nice to add a line on the use case for this type of status
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.
Fixed. Thanks for the feedback.