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

hcm: avoid invoking 100-continue handling on decode filter. #10929

Merged
merged 2 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion source/common/http/conn_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2067,7 +2067,7 @@ void ConnectionManagerImpl::ActiveStreamFilterBase::commonContinue() {
allowIteration();

// Only resume with do100ContinueHeaders() if we've actually seen a 100-Continue.
if (parent_.state_.has_continue_headers_ && !continue_headers_continued_) {
if (has100Continueheaders()) {
continue_headers_continued_ = true;
do100ContinueHeaders();
// If the response headers have not yet come in, don't continue on with
Expand Down
5 changes: 5 additions & 0 deletions source/common/http/conn_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
virtual Buffer::WatermarkBufferPtr createBuffer() PURE;
virtual Buffer::WatermarkBufferPtr& bufferedData() PURE;
virtual bool complete() PURE;
virtual bool has100Continueheaders() PURE;
virtual void do100ContinueHeaders() PURE;
virtual void doHeaders(bool end_stream) PURE;
virtual void doData(bool end_stream) PURE;
Expand Down Expand Up @@ -237,6 +238,7 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
Buffer::WatermarkBufferPtr createBuffer() override;
Buffer::WatermarkBufferPtr& bufferedData() override { return parent_.buffered_request_data_; }
bool complete() override { return parent_.state_.remote_complete_; }
bool has100Continueheaders() override { return false; }
Copy link
Contributor

Choose a reason for hiding this comment

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

nice :)

void do100ContinueHeaders() override { NOT_REACHED_GCOVR_EXCL_LINE; }
void doHeaders(bool end_stream) override {
parent_.decodeHeaders(this, *parent_.request_headers_, end_stream);
Expand Down Expand Up @@ -349,6 +351,9 @@ class ConnectionManagerImpl : Logger::Loggable<Logger::Id::http>,
Buffer::WatermarkBufferPtr createBuffer() override;
Buffer::WatermarkBufferPtr& bufferedData() override { return parent_.buffered_response_data_; }
bool complete() override { return parent_.state_.local_complete_; }
bool has100Continueheaders() override {
return parent_.state_.has_continue_headers_ && !continue_headers_continued_;
}
void do100ContinueHeaders() override {
parent_.encode100ContinueHeaders(this, *parent_.continue_headers_);
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions test/common/http/conn_manager_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,71 @@ TEST_F(HttpConnectionManagerImplTest, PauseResume100Continue) {
decoder_filters_[1]->callbacks_->encodeHeaders(std::move(response_headers), false);
}

// Regression test for https://github.com/envoyproxy/envoy/issues/10923.
TEST_F(HttpConnectionManagerImplTest, 100ContinueResponseWithDecoderPause) {
proxy_100_continue_ = true;
setup(false, "envoy-custom-server", false);

std::shared_ptr<MockStreamDecoderFilter> filter(new NiceMock<MockStreamDecoderFilter>());

// Allow headers to pass.
EXPECT_CALL(*filter, decodeHeaders(_, false))
.WillRepeatedly(
InvokeWithoutArgs([]() -> FilterHeadersStatus { return FilterHeadersStatus::Continue; }));
// Pause and then resume the decode pipeline, this is key to triggering #10923.
EXPECT_CALL(*filter, decodeData(_, false)).WillOnce(InvokeWithoutArgs([]() -> FilterDataStatus {
return FilterDataStatus::StopIterationAndBuffer;
}));
EXPECT_CALL(*filter, decodeData(_, true))
.WillRepeatedly(
InvokeWithoutArgs([]() -> FilterDataStatus { return FilterDataStatus::Continue; }));

EXPECT_CALL(*filter, setDecoderFilterCallbacks(_));

EXPECT_CALL(filter_factory_, createFilterChain(_))
.WillRepeatedly(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> void {
callbacks.addStreamDecoderFilter(filter);
}));

EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_));

RequestDecoder* decoder = nullptr;
NiceMock<MockResponseEncoder> encoder;
EXPECT_CALL(*codec_, dispatch(_)).WillRepeatedly(Invoke([&](Buffer::Instance& data) -> void {
decoder = &conn_manager_->newStream(encoder);

// Test not charging stats on the second call.
RequestHeaderMapPtr headers{
new TestRequestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}};
decoder->decodeHeaders(std::move(headers), false);
// Allow the decode pipeline to pause.
decoder->decodeData(data, false);

ResponseHeaderMapPtr continue_headers{new TestResponseHeaderMapImpl{{":status", "100"}}};
filter->callbacks_->encode100ContinueHeaders(std::move(continue_headers));

// Resume decode pipeline after encoding 100 continue headers, we're now ready to trigger
// #10923.
decoder->decodeData(data, true);

ResponseHeaderMapPtr response_headers{new TestResponseHeaderMapImpl{{":status", "200"}}};
filter->callbacks_->encodeHeaders(std::move(response_headers), true);

data.drain(4);
}));

// Kick off the incoming data.
Buffer::OwnedImpl fake_input("1234");
conn_manager_->onData(fake_input, false);

EXPECT_EQ(1U, stats_.named_.downstream_rq_1xx_.value());
EXPECT_EQ(1U, listener_stats_.downstream_rq_1xx_.value());
EXPECT_EQ(1U, stats_.named_.downstream_rq_2xx_.value());
EXPECT_EQ(1U, listener_stats_.downstream_rq_2xx_.value());
EXPECT_EQ(2U, stats_.named_.downstream_rq_completed_.value());
EXPECT_EQ(2U, listener_stats_.downstream_rq_completed_.value());
}

// By default, Envoy will set the server header to the server name, here "custom-value"
TEST_F(HttpConnectionManagerImplTest, ServerHeaderOverwritten) {
setup(false, "custom-value", false);
Expand Down
1 change: 1 addition & 0 deletions test/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ envoy_cc_test(
"//test/integration/filters:clear_route_cache_filter_lib",
"//test/integration/filters:encoder_decoder_buffer_filter_lib",
"//test/integration/filters:process_context_lib",
"//test/integration/filters:stop_iteration_and_continue",
"//test/mocks/http:http_mocks",
"//test/test_common:utility_lib",
"@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto",
Expand Down
10 changes: 10 additions & 0 deletions test/integration/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,16 @@ TEST_P(IntegrationTest, EnvoyProxyingLate100ContinueWithEncoderFilter) {
testEnvoyProxying100Continue(false, true);
}

// Regression test for https://github.com/envoyproxy/envoy/issues/10923.
TEST_P(IntegrationTest, EnvoyProxying100ContinueWithDecodeDataPause) {
config_helper_.addFilter(R"EOF(
name: stop-iteration-and-continue-filter
typed_config:
"@type": type.googleapis.com/google.protobuf.Empty
)EOF");
testEnvoyProxying100Continue(true);
}

// This is a regression for https://github.com/envoyproxy/envoy/issues/2715 and validates that a
// pending request is not sent on a connection that has been half-closed.
TEST_P(IntegrationTest, UpstreamDisconnectWithTwoRequests) {
Expand Down