Skip to content
Merged
6 changes: 5 additions & 1 deletion source/common/http/filter_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -477,14 +477,18 @@ void FilterManager::decodeHeaders(ActiveStreamDecoderFilter* filter, RequestHead
ASSERT(!(status == FilterHeadersStatus::ContinueAndDontEndStream && !(*entry)->end_stream_),
"Filters should not return FilterHeadersStatus::ContinueAndDontEndStream from "
"decodeHeaders when end_stream is already false");
ENVOY_BUG(
!state_.local_complete_ || status == FilterHeadersStatus::StopIteration,
"Filters should return FilterHeadersStatus::StopIteration after sending a local reply.");

state_.filter_call_state_ &= ~FilterCallState::DecodeHeaders;
ENVOY_STREAM_LOG(trace, "decode headers called: filter={} status={}", *this,
static_cast<const void*>((*entry).get()), static_cast<uint64_t>(status));

(*entry)->decode_headers_called_ = true;

const auto continue_iteration = (*entry)->commonHandleAfterHeadersCallback(status, end_stream);
const auto continue_iteration =
(*entry)->commonHandleAfterHeadersCallback(status, end_stream) && !state_.local_complete_;

// If this filter ended the stream, decodeComplete() should be called for it.
if ((*entry)->end_stream_) {
Expand Down
4 changes: 2 additions & 2 deletions test/common/http/conn_manager_impl_test_2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1453,7 +1453,7 @@ TEST_F(HttpConnectionManagerImplTest, FilterHeadReply) {
.WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus {
decoder_filters_[0]->callbacks_->sendLocalReply(Code::BadRequest, "Bad request", nullptr,
absl::nullopt, "");
return FilterHeadersStatus::Continue;
return FilterHeadersStatus::StopIteration;
}));

EXPECT_CALL(response_encoder_, streamErrorOnInvalidHttpMessage()).WillOnce(Return(true));
Expand Down Expand Up @@ -1483,7 +1483,7 @@ TEST_F(HttpConnectionManagerImplTest, ResetWithStoppedFilter) {
.WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus {
decoder_filters_[0]->callbacks_->sendLocalReply(Code::BadRequest, "Bad request", nullptr,
absl::nullopt, "");
return FilterHeadersStatus::Continue;
return FilterHeadersStatus::StopIteration;
}));

EXPECT_CALL(response_encoder_, streamErrorOnInvalidHttpMessage()).WillOnce(Return(true));
Expand Down
2 changes: 2 additions & 0 deletions test/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -443,11 +443,13 @@ envoy_cc_test(
"//source/extensions/filters/http/buffer:config",
"//source/extensions/filters/http/health_check:config",
"//test/common/http/http2:http2_frame",
"//test/integration/filters:continue_after_local_reply_filter_lib",
"//test/integration/filters:continue_headers_only_inject_body",
"//test/integration/filters:encoder_decoder_buffer_filter_lib",
"//test/integration/filters:invalid_header_filter_lib",
"//test/integration/filters:local_reply_during_encoding_filter_lib",
"//test/integration/filters:random_pause_filter_lib",
"//test/test_common:logging_lib",
"//test/test_common:utility_lib",
"@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto",
"@envoy_api//envoy/config/route/v3:pkg_cc_proto",
Expand Down
15 changes: 15 additions & 0 deletions test/integration/filters/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ envoy_cc_test_library(
],
)

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

envoy_cc_test_library(
name = "continue_headers_only_inject_body",
srcs = [
Expand Down
30 changes: 30 additions & 0 deletions test/integration/filters/continue_after_local_reply_filter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include "envoy/registry/registry.h"
#include "envoy/server/filter_config.h"

#include "extensions/filters/http/common/pass_through_filter.h"

#include "test/extensions/filters/http/common/empty_http_filter_config.h"
#include "test/integration/filters/common.h"

#include "gtest/gtest.h"

namespace Envoy {

// A filter that only calls Http::FilterHeadersStatus::Continue after a local reply.
class ContinueAfterLocalReplyFilter : public Http::PassThroughFilter {
public:
constexpr static char name[] = "continue-after-local-reply-filter";

Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap&, bool) override {
decoder_callbacks_->sendLocalReply(Envoy::Http::Code::OK, "", nullptr, absl::nullopt,
"ContinueAfterLocalReplyFilter is ready");
return Http::FilterHeadersStatus::Continue;
}
};

constexpr char ContinueAfterLocalReplyFilter::name[];
static Registry::RegisterFactory<SimpleFilterConfig<ContinueAfterLocalReplyFilter>,
Server::Configuration::NamedHttpFilterConfigFactory>
register_;

} // namespace Envoy
35 changes: 35 additions & 0 deletions test/integration/protocol_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "test/mocks/upstream/retry_priority.h"
#include "test/mocks/upstream/retry_priority_factory.h"
#include "test/test_common/environment.h"
#include "test/test_common/logging.h"
#include "test/test_common/network_utility.h"
#include "test/test_common/registry.h"

Expand Down Expand Up @@ -287,6 +288,40 @@ TEST_P(ProtocolIntegrationTest, ContinueHeadersOnlyInjectBodyFilter) {
EXPECT_EQ(response->body(), "body");
}

// Tests a filter that returns a FilterHeadersStatus::Continue after a local reply. In debug mode,
// this fails on ENVOY_BUG. In opt mode, the status is corrected and the failure is logged.
TEST_P(ProtocolIntegrationTest, ContinueAfterLocalReply) {
config_helper_.addFilter(R"EOF(
name: continue-after-local-reply-filter
typed_config:
"@type": type.googleapis.com/google.protobuf.Empty
)EOF");
initialize();

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

// Send a headers only request.
IntegrationStreamDecoderPtr response;
const std::string error = "envoy bug failure: !state_.local_complete_ || status == "
"FilterHeadersStatus::StopIteration. Details: Filters should return "
"FilterHeadersStatus::StopIteration after sending a local reply.";
#ifdef NDEBUG
EXPECT_LOG_CONTAINS("error", error, {
response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);
response->waitForEndStream();
});
EXPECT_TRUE(response->complete());
EXPECT_EQ("200", response->headers().getStatusValue());
#else
EXPECT_DEATH(
{
response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);
response->waitForEndStream();
},
error);
#endif
}

TEST_P(ProtocolIntegrationTest, AddEncodedTrailers) {
config_helper_.addFilter(R"EOF(
name: add-trailers-filter
Expand Down