Skip to content
Merged
5 changes: 4 additions & 1 deletion source/common/http/filter_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -447,14 +447,17 @@ 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(!(*entry)->complete() || status == FilterHeadersStatus::StopIteration,
"Filters should not FilterHeadersStatus::StopIteration after sending a local reply.");
Comment thread
alyssawilk marked this conversation as resolved.
Outdated

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) && !(*entry)->complete();

// If this filter ended the stream, decodeComplete() should be called for it.
if ((*entry)->end_stream_) {
Expand Down
2 changes: 2 additions & 0 deletions test/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,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
26 changes: 26 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,31 @@ 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) {
#ifdef NDEBUG
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.
auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);
EXPECT_LOG_CONTAINS(
"error",
"envoy bug failure: !(*entry)->complete() || status == FilterHeadersStatus::StopIteration. "
"Details: Filters should not FilterHeadersStatus::StopIteration after sending a local reply.",
response->waitForEndStream());
EXPECT_TRUE(response->complete());
EXPECT_EQ("200", response->headers().getStatusValue());
#endif
Comment thread
asraa marked this conversation as resolved.
}

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