Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
3835768
Enable StopAllTypesIteration for decoding path
Feb 13, 2019
9cf21dd
update comments
Feb 14, 2019
4429d12
fix clang_tidy
Feb 14, 2019
94c4d44
address comments
Feb 20, 2019
eabbeb6
add use case explanation
Feb 20, 2019
0bd2315
fix format
Feb 20, 2019
b437b67
merge from master
Feb 21, 2019
50b7e7c
fix format
Feb 21, 2019
5c9fc64
fix format and add new files
Feb 21, 2019
b3527f7
remove unused variable
Feb 21, 2019
eaa2ceb
cleanups
Feb 21, 2019
48a3833
emtpy commit
Feb 21, 2019
ac8a553
emtpy commit
Feb 21, 2019
f595c23
Merge stopped_ into iteration_state_
Mar 1, 2019
ee71a1f
remove iterate_from_current_filter_
Mar 1, 2019
6544a7c
fixed a test where two stop-all filters are back-to-back
Mar 1, 2019
3f2656b
enable stop all iteration tests on http2
Mar 4, 2019
6996475
address review comments
Mar 5, 2019
7bf4912
merge master
Mar 6, 2019
119e166
add unit test and fixed a bug
Mar 8, 2019
82588d3
merge from master
Mar 10, 2019
d2839d6
fix format
Mar 11, 2019
eb25b60
fix a bug in conn_manager_impl_test.cc
Mar 11, 2019
82e02e2
Enable stop-all for encoding path
Mar 11, 2019
e0726f0
add trailers to unit test
Mar 11, 2019
a43ba57
address review comments except filters combining.
Mar 12, 2019
701493a
move tests to protocol_integration_test.cc and set parameters in test…
Mar 12, 2019
66ebe16
format change
Mar 12, 2019
e9f387a
merge decode_headers_return_stop_all_filter and decode_headers_return…
Mar 12, 2019
b7b8113
Merge branch 'master' into stopall
Mar 12, 2019
dd80759
fix a bug where decodeData() returns stop, we reset iteration_state_ …
Mar 13, 2019
eef5003
address review comments
Mar 18, 2019
bcb917b
Add more comments
Mar 18, 2019
5f21a63
address review comments
Mar 20, 2019
e691414
Empty commmit
Mar 20, 2019
5dfb999
comments
Mar 25, 2019
c60f2df
test cleanup
Mar 26, 2019
c645f9b
Add integration test for the encoding path
Mar 26, 2019
0ddb3af
add new file test/integration/filters/encode_headers_return_stop_all_…
Mar 26, 2019
beb19b8
address conn_manager_impl_test.cc comments
Mar 27, 2019
d875ca4
address review comments
Mar 27, 2019
e7cebab
address comments
Mar 28, 2019
d02d056
avoid saved state in addEn/decodedData
Mar 28, 2019
c2becd0
address comments
Apr 1, 2019
9e2e461
address comment
Apr 2, 2019
4ca783e
merge master
Apr 5, 2019
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
10 changes: 9 additions & 1 deletion include/envoy/http/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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.

would be nice to add a line on the use case for this type of status

Copy link
Copy Markdown
Contributor Author

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.

// TODO(soya3129): add tests for encoding path, and remove the decoding path condition.
StopAllTypesIteration

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.

nit: I would call this StopAllIteration personally.

Also, I think we likely need StopAllIterationAndBuffer as well as StopAllIterationAndWatermark or something along those lines to match the options we give on on data? WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Thanks!

};

/**
Expand Down
13 changes: 11 additions & 2 deletions source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,7 @@ void ConnectionManagerImpl::ActiveStreamFilterBase::commonContinue() {
static_cast<const void*>(this));
ASSERT(stopped_);
stopped_ = false;
stopped_all_ = false;

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.

nit: Instead of stopped_ and _stopped_all_ can we make this a tri-state enum? I think it will help the logic be more clear.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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_) {
Expand Down Expand Up @@ -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;

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.

Where do we look at stopped_all_ to determine to not call any filter callbacks? I don't see that? This is the place also that for data we will have to decide on buffer vs watermark processing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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!

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.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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!

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.

About stop-all options for data. I thought we want stop-all for headers only at least for now?

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
// can return StopAllTypesIteration."

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?

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.

Do you think it's reasonable to let users add rate-limiting filter, otherwise, fails when the limit is reached?

I don't think so. I think it would be more natural given the rest of the API to offer StopAllIterationAndBuffer and StopAllIterationAndWatermark, then given the return code, subsequent data can be handled in either the streaming or buffer way as the data return codes do. I don't think it would be very hard to implement. WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

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.

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.
Expand Down Expand Up @@ -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_);
Expand All @@ -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_);
Expand Down
5 changes: 4 additions & 1 deletion source/common/http/conn_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
struct ActiveStreamFilterBase : public virtual StreamFilterCallbacks {
ActiveStreamFilterBase(ActiveStream& parent, bool dual_filter)
: parent_(parent), headers_continued_(false), continue_headers_continued_(false),
stopped_(false), end_stream_(false), dual_filter_(dual_filter) {}
stopped_(false), stopped_all_(false), end_stream_(false), dual_filter_(dual_filter) {}

bool commonHandleAfter100ContinueHeadersCallback(FilterHeadersStatus status);
bool commonHandleAfterHeadersCallback(FilterHeadersStatus status, bool& headers_only);
Expand Down Expand Up @@ -132,6 +132,9 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
bool headers_continued_ : 1;
bool continue_headers_continued_ : 1;
bool stopped_ : 1;
// If true, filter iteration has been stopped for this filter and all the filters followed on
// all frame types.
bool stopped_all_ : 1;
// If true, end_stream is called for this filter.
bool end_stream_ : 1;
const bool dual_filter_ : 1;
Expand Down
2 changes: 2 additions & 0 deletions test/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ envoy_cc_test_library(
"//source/extensions/filters/network/http_connection_manager:config",
"//test/common/upstream:utility_lib",
"//test/integration/filters:add_trailers_filter_config_lib",
"//test/integration/filters:call_decodedata_once_filter_config_lib",
"//test/integration/filters:decode_headers_return_stop_all_filter_config_lib",
"//test/integration/filters:headers_only_filter_config_lib",
"//test/integration/filters:passthrough_filter_config_lib",
"//test/integration/filters:pause_filter_lib",
Expand Down
32 changes: 32 additions & 0 deletions test/integration/filters/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,35 @@ envoy_cc_test_library(
"//source/extensions/filters/http/common:pass_through_filter_lib",
],
)

envoy_cc_test_library(
name = "decode_headers_return_stop_all_filter_config_lib",
srcs = [
"decode_headers_return_stop_all_filter.cc",
],
deps = [
":common_lib",
"//include/envoy/event:timer_interface",
"//include/envoy/http:filter_interface",
"//include/envoy/registry",
"//include/envoy/server:filter_config_interface",
"//source/extensions/filters/http/common:empty_http_filter_config_lib",
"//source/extensions/filters/http/common:pass_through_filter_lib",
],
)

envoy_cc_test_library(
name = "call_decodedata_once_filter_config_lib",
srcs = [
"call_decodedata_once_filter.cc",
],
deps = [
":common_lib",
"//include/envoy/event:timer_interface",
"//include/envoy/http:filter_interface",
"//include/envoy/registry",
"//include/envoy/server:filter_config_interface",
"//source/extensions/filters/http/common:empty_http_filter_config_lib",
"//source/extensions/filters/http/common:pass_through_filter_lib",
],
)
40 changes: 40 additions & 0 deletions test/integration/filters/call_decodedata_once_filter.cc
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
67 changes: 67 additions & 0 deletions test/integration/filters/decode_headers_return_stop_all_filter.cc
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
2 changes: 2 additions & 0 deletions test/integration/http2_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ TEST_P(Http2IntegrationTest, DecodingHeaderOnlyResponse) { testHeadersOnlyFilter

TEST_P(Http2IntegrationTest, DecodingHeaderOnlyInterleaved) { testHeadersOnlyFilterInterleaved(); }

TEST_P(Http2IntegrationTest, DecodeHeadersReturnsStopAll) { testDecodeHeadersReturnsStopAll(); }

TEST_P(Http2IntegrationTest, DownstreamResetBeforeResponseComplete) {
testDownstreamResetBeforeResponseComplete();
}
Expand Down
54 changes: 54 additions & 0 deletions test/integration/http_integration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1636,4 +1636,58 @@ std::string HttpIntegrationTest::listenerStatPrefix(const std::string& stat_name
return "listener.[__1]_0." + stat_name;
}

// Tests StopAllTypesIteration. decode-headers-return-stop-all-filter returns StopAllTypesIteration
// for headers. As a result, call-decodedata-once-filter that follows headers-return-stop-all-filter
// will stop processing headers, data and trailer until the iteration resumes. Verifies decodeData()
// is called multiple times in decode-headers-return-stop-all-filter, and only called once in
// call-decodedata-once-filter after iteration resumes.
void HttpIntegrationTest::testDecodeHeadersReturnsStopAll() {
config_helper_.addFilter(R"EOF(
name: call-decodedata-once-filter
)EOF");
config_helper_.addFilter(R"EOF(
name: decode-headers-return-stop-all-filter
)EOF");
config_helper_.addFilter(R"EOF(
name: passthrough-filter
)EOF");

initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));

// Sends a request with headers and data.
auto encoder_decoder = codec_client_->startRequest(default_request_headers_);
request_encoder_ = &encoder_decoder.first;
auto response = std::move(encoder_decoder.second);
const int count = 10;
const int size = 100;
for (int i = 0; i < count; i++) {
codec_client_->sendData(*request_encoder_, size, false);
}
codec_client_->sendData(*request_encoder_, size, true);
waitForNextUpstreamRequest();

upstream_request_->encodeHeaders(default_response_headers_, true);
response->waitForEndStream();
ASSERT_TRUE(response->complete());
EXPECT_EQ((count + 1) * size, upstream_request_->bodyLength());
EXPECT_EQ(true, upstream_request_->complete());

// Sends a request with headers, data, and trailers.
auto encoder_decoder_2 = codec_client_->startRequest(default_request_headers_);
request_encoder_ = &encoder_decoder_2.first;
response = std::move(encoder_decoder_2.second);
for (int i = 0; i < count; i++) {
codec_client_->sendData(*request_encoder_, size, false);
}
Http::TestHeaderMapImpl request_trailers{{"trailer", "trailer"}};
codec_client_->sendTrailers(*request_encoder_, request_trailers);
waitForNextUpstreamRequest();

upstream_request_->encodeHeaders(default_response_headers_, true);
response->waitForEndStream();
EXPECT_EQ(count * size, upstream_request_->bodyLength());
EXPECT_EQ(true, upstream_request_->complete());
}

} // namespace Envoy
1 change: 1 addition & 0 deletions test/integration/http_integration.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class HttpIntegrationTest : public BaseIntegrationTest {
void testHeadersOnlyFilterEncodingIntermediateFilters();
void testHeadersOnlyFilterDecodingIntermediateFilters();
void testHeadersOnlyFilterInterleaved();
void testDecodeHeadersReturnsStopAll();

// Test that a request returns the same content with both allow_absolute_urls enabled and
// allow_absolute_urls disabled
Expand Down
2 changes: 2 additions & 0 deletions test/integration/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ TEST_P(IntegrationTest, DecodingHeaderOnlyResponseIntermediateFilters) {

TEST_P(IntegrationTest, DecodingHeaderOnlyInterleaved) { testHeadersOnlyFilterInterleaved(); }

TEST_P(IntegrationTest, testDecodeHeadersReturnsStopAll) { testDecodeHeadersReturnsStopAll(); }

TEST_P(IntegrationTest, RetryHittingBufferLimit) { testRetryHittingBufferLimit(); }

TEST_P(IntegrationTest, HittingDecoderFilterLimit) { testHittingDecoderFilterLimit(); }
Expand Down