Skip to content
Closed
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
10 changes: 10 additions & 0 deletions source/common/http/filter_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1699,6 +1699,10 @@ bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers)
return false;
}

parent_.state_.decoder_filter_chain_aborted_ = true;
parent_.state_.encoder_filter_chain_aborted_ = true;
parent_.state_.recreated_stream_ = true;

parent_.streamInfo().setResponseCodeDetails(
StreamInfo::ResponseCodeDetails::get().InternalRedirect);

Expand Down Expand Up @@ -1765,6 +1769,12 @@ void ActiveStreamEncoderFilter::drainSavedResponseMetadata() {
}

void ActiveStreamEncoderFilter::handleMetadataAfterHeadersCallback() {
if (parent_.state_.recreated_stream_) {
// The stream has been recreated. In this case, there's no reason to encode saved metadata.
getSavedResponseMetadata()->clear();
return;
}

// If we drain accumulated metadata, the iteration must start with the current filter.
const bool saved_state = iterate_from_current_filter_;
iterate_from_current_filter_ = true;
Expand Down
5 changes: 4 additions & 1 deletion source/common/http/filter_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,8 @@ class FilterManager : public ScopeTrackedObject,
has_1xx_headers_(false), created_filter_chain_(false), is_head_request_(false),
is_grpc_request_(false), non_100_response_headers_encoded_(false),
under_on_local_reply_(false), decoder_filter_chain_aborted_(false),
encoder_filter_chain_aborted_(false), saw_downstream_reset_(false) {}
encoder_filter_chain_aborted_(false), saw_downstream_reset_(false),
recreated_stream_(false) {}
uint32_t filter_call_state_{0};

// Set after decoder filter chain has completed iteration. Prevents further calls to decoder
Expand Down Expand Up @@ -928,6 +929,8 @@ class FilterManager : public ScopeTrackedObject,
bool decoder_filter_chain_aborted_ : 1;
bool encoder_filter_chain_aborted_ : 1;
bool saw_downstream_reset_ : 1;
// True when the stream was recreated.
bool recreated_stream_ : 1;

// The following 3 members are booleans rather than part of the space-saving bitfield as they
// are passed as arguments to functions expecting bools. Extend State using the bitfield
Expand Down
2 changes: 2 additions & 0 deletions test/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -770,12 +770,14 @@ envoy_cc_test(
"//source/common/http:header_map_lib",
"//source/extensions/filters/http/buffer:config",
"//test/integration/filters:add_body_filter_config_lib",
"//test/integration/filters:add_encode_metadata_filter_lib",
"//test/integration/filters:add_invalid_data_filter_lib",
"//test/integration/filters:assert_non_reentrant_filter_lib",
"//test/integration/filters:buffer_continue_filter_lib",
"//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:encoder_recreate_stream_filter_lib",
"//test/integration/filters:invalid_header_filter_lib",
"//test/integration/filters:local_reply_during_encoding_data_filter_lib",
"//test/integration/filters:local_reply_during_encoding_filter_lib",
Expand Down
80 changes: 80 additions & 0 deletions test/integration/filter_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1575,5 +1575,85 @@ TEST_P(FilterIntegrationTest, FilterAddsDataToHeaderOnlyRequestWithIndependentHa
testFilterAddsDataAndTrailersToHeaderOnlyRequest();
}

// Add metadata in the first filter before recreate the stream in the second filter,
// on response path.
TEST_P(FilterIntegrationTest, RecreateStreamAfterEncodeMetadata) {
// recreateStream is not supported in Upstream filter chain.
if (!testing_downstream_filter_) {
return;
}

prependFilter("{ name: add-metadata-encode-headers-filter }");
prependFilter("{ name: encoder-recreate-stream-filter }");
config_helper_.addConfigModifier(
[&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
hcm) -> void { hcm.mutable_http2_protocol_options()->set_allow_metadata(true); });

initialize();

codec_client_ = makeHttpConnection(lookupPort("http"));
auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);
waitForNextUpstreamRequest();
upstream_request_->encodeHeaders(default_response_headers_, true);

// Second upstream request is triggered by recreateStream.
FakeStreamPtr upstream_request_2;
// Wait for the next stream on the upstream connection.
ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_2));
// Wait for the stream to be completely received.
ASSERT_TRUE(upstream_request_2->waitForEndStream(*dispatcher_));
upstream_request_2->encodeHeaders(default_response_headers_, true);

// Wait for the response to be completely received.
ASSERT_TRUE(response->waitForEndStream());
ASSERT_TRUE(response->complete());

// Verify the metadata is received.
std::set<std::string> expected_metadata_keys = {"headers", "duplicate"};
EXPECT_EQ(response->metadataMap().size(), expected_metadata_keys.size());
for (const auto& key : expected_metadata_keys) {
// keys are the same as their corresponding values.
auto it = response->metadataMap().find(key);
ASSERT_FALSE(it == response->metadataMap().end()) << "key: " << key;
EXPECT_EQ(response->metadataMap().find(key)->second, key);
}
}

// Add metadata in the first filter on local reply path.
TEST_P(FilterIntegrationTest, EncodeMetadataOnLocalReply) {
// Local replies are not seen by upstream HTTP filters. add-metadata-encode-headers-filter will
// not be invoked if it is installed in upstream filter chain.
// Thus, this test is only applicable to downstream filter chain.
if (!testing_downstream_filter_) {
return;
}

prependFilter("{ name: local-reply-during-decode }");
prependFilter("{ name: add-metadata-encode-headers-filter }");

config_helper_.addConfigModifier(
[&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
hcm) -> void { hcm.mutable_http2_protocol_options()->set_allow_metadata(true); });

initialize();

codec_client_ = makeHttpConnection(lookupPort("http"));
auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);

ASSERT_TRUE(response->waitForEndStream());
EXPECT_TRUE(response->complete());
EXPECT_EQ("500", response->headers().getStatusValue());

// Verify the metadata is received.
std::set<std::string> expected_metadata_keys = {"headers", "duplicate"};
EXPECT_EQ(response->metadataMap().size(), expected_metadata_keys.size());
for (const auto& key : expected_metadata_keys) {
// keys are the same as their corresponding values.
auto it = response->metadataMap().find(key);
ASSERT_FALSE(it == response->metadataMap().end()) << "key: " << key;
EXPECT_EQ(response->metadataMap().find(key)->second, key);
}
}

} // namespace
} // namespace Envoy
33 changes: 33 additions & 0 deletions test/integration/filters/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1013,3 +1013,36 @@ envoy_cc_test_library(
"//test/extensions/filters/http/common:empty_http_filter_config_lib",
],
)

envoy_cc_test_library(
name = "add_encode_metadata_filter_lib",
srcs = [
"add_encode_metadata_filter.cc",
],
deps = [
":common_lib",
"//envoy/event:timer_interface",
"//envoy/http:filter_interface",
"//envoy/registry",
"//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 = "encoder_recreate_stream_filter_lib",
srcs = [
"encoder_recreate_stream_filter.cc",
],
deps = [
":common_lib",
"//envoy/event:timer_interface",
"//envoy/http:filter_interface",
"//envoy/registry",
"//envoy/server:filter_config_interface",
"//source/common/router:string_accessor_lib",
"//source/extensions/filters/http/common:pass_through_filter_lib",
"//test/extensions/filters/http/common:empty_http_filter_config_lib",
],
)
41 changes: 41 additions & 0 deletions test/integration/filters/add_encode_metadata_filter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#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 "source/common/buffer/buffer_impl.h"
#include "source/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 add encoded metadata in encodeHeaders.
class AddEncodeMetadataFilter : public Http::PassThroughFilter {
public:
constexpr static char name[] = "add-metadata-encode-headers-filter";

Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap&, bool) override {
Http::MetadataMap metadata_map = {{"headers", "headers"}, {"duplicate", "duplicate"}};
Http::MetadataMapPtr metadata_map_ptr = std::make_unique<Http::MetadataMap>(metadata_map);
encoder_callbacks_->addEncodedMetadata(std::move(metadata_map_ptr));
return Http::FilterHeadersStatus::Continue;
}

Http::FilterDataStatus encodeData(Buffer::Instance&, bool) override {
return Http::FilterDataStatus::Continue;
}
};

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

} // namespace Envoy
55 changes: 55 additions & 0 deletions test/integration/filters/encoder_recreate_stream_filter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#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 "source/common/buffer/buffer_impl.h"
#include "source/extensions/filters/http/common/pass_through_filter.h"

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

#include "gtest/gtest.h"

namespace Envoy {

class EncoderRecreateStreamFilter : public Http::PassThroughFilter {
public:
constexpr static char name[] = "encoder-recreate-stream-filter";

Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap&, bool) override {
const auto* filter_state =
decoder_callbacks_->streamInfo().filterState()->getDataReadOnly<Router::StringAccessorImpl>(
"test_key");

if (filter_state != nullptr) {
return ::Envoy::Http::FilterHeadersStatus::Continue;
}

decoder_callbacks_->streamInfo().filterState()->setData(
"test_key", std::make_unique<Router::StringAccessorImpl>("test_value"),
StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Request);

if (decoder_callbacks_->recreateStream(nullptr)) {
return ::Envoy::Http::FilterHeadersStatus::StopIteration;
}

return ::Envoy::Http::FilterHeadersStatus::Continue;
}

void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override {
decoder_callbacks_ = &callbacks;
}
};

// perform static registration
constexpr char EncoderRecreateStreamFilter::name[];
static Registry::RegisterFactory<SimpleFilterConfig<EncoderRecreateStreamFilter>,
Server::Configuration::NamedHttpFilterConfigFactory>
register_;

} // namespace Envoy
24 changes: 12 additions & 12 deletions tools/base/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1059,18 +1059,18 @@ ply==3.11 \
--hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \
--hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce
# via -r requirements.in
protobuf==5.28.0 \
--hash=sha256:018db9056b9d75eb93d12a9d35120f97a84d9a919bcab11ed56ad2d399d6e8dd \
--hash=sha256:510ed78cd0980f6d3218099e874714cdf0d8a95582e7b059b06cabad855ed0a0 \
--hash=sha256:532627e8fdd825cf8767a2d2b94d77e874d5ddb0adefb04b237f7cc296748681 \
--hash=sha256:6206afcb2d90181ae8722798dcb56dc76675ab67458ac24c0dd7d75d632ac9bd \
--hash=sha256:66c3edeedb774a3508ae70d87b3a19786445fe9a068dd3585e0cefa8a77b83d0 \
--hash=sha256:6d7cc9e60f976cf3e873acb9a40fed04afb5d224608ed5c1a105db4a3f09c5b6 \
--hash=sha256:853db610214e77ee817ecf0514e0d1d052dff7f63a0c157aa6eabae98db8a8de \
--hash=sha256:d001a73c8bc2bf5b5c1360d59dd7573744e163b3607fa92788b7f3d5fefbd9a5 \
--hash=sha256:dde74af0fa774fa98892209992295adbfb91da3fa98c8f67a88afe8f5a349add \
--hash=sha256:dde9fcaa24e7a9654f4baf2a55250b13a5ea701493d904c54069776b99a8216b \
--hash=sha256:eef7a8a2f4318e2cb2dee8666d26e58eaf437c14788f3a2911d0c3da40405ae8
protobuf==5.28.2 \
--hash=sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132 \
--hash=sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f \
--hash=sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece \
--hash=sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0 \
--hash=sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f \
--hash=sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0 \
--hash=sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276 \
--hash=sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7 \
--hash=sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3 \
--hash=sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36 \
--hash=sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d
# via
# -r requirements.in
# envoy-base-utils
Expand Down