diff --git a/api/envoy/config/core/v3/protocol.proto b/api/envoy/config/core/v3/protocol.proto
index 8a4e292af508b..70af4851b8e76 100644
--- a/api/envoy/config/core/v3/protocol.proto
+++ b/api/envoy/config/core/v3/protocol.proto
@@ -486,10 +486,10 @@ message Http2ProtocolOptions {
// Allows proxying Websocket and other upgrades over H2 connect.
bool allow_connect = 5;
- // [#not-implemented-hide:] Hiding until envoy has full metadata support.
+ // [#not-implemented-hide:] Hiding until Envoy has full metadata support.
// Still under implementation. DO NOT USE.
//
- // Allows metadata. See [metadata
+ // Allows sending and receiving HTTP/2 METADATA frames. See [metadata
// docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more
// information.
bool allow_metadata = 6;
@@ -618,7 +618,7 @@ message GrpcProtocolOptions {
}
// A message which allows using HTTP/3.
-// [#next-free-field: 6]
+// [#next-free-field: 7]
message Http3ProtocolOptions {
QuicProtocolOptions quic_protocol_options = 1;
@@ -637,6 +637,14 @@ message Http3ProtocolOptions {
// `_
// Note that HTTP/3 CONNECT is not yet an RFC.
bool allow_extended_connect = 5 [(xds.annotations.v3.field_status).work_in_progress = true];
+
+ // [#not-implemented-hide:] Hiding until Envoy has full metadata support.
+ // Still under implementation. DO NOT USE.
+ //
+ // Allows sending and receiving HTTP/3 METADATA frames. See [metadata
+ // docs](https://github.com/envoyproxy/envoy/blob/main/source/docs/h2_metadata.md) for more
+ // information.
+ bool allow_metadata = 6;
}
// A message to control transformations to the :scheme header
diff --git a/changelogs/current.yaml b/changelogs/current.yaml
index e13ab82f5fb58..6d2dff7d9edbc 100644
--- a/changelogs/current.yaml
+++ b/changelogs/current.yaml
@@ -239,6 +239,9 @@ new_features:
change: |
Added :ref:`host_rewrite ` config to be used
during signature.
+- area: http3
+ change: |
+ Added experimental support for sending and receiving HTTP/3 METADATA frames.
- area: ext_proc
change: |
added
diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc
index 18e02bb0bdc36..1e659ac61e4c0 100644
--- a/source/common/quic/envoy_quic_client_stream.cc
+++ b/source/common/quic/envoy_quic_client_stream.cc
@@ -23,7 +23,7 @@ EnvoyQuicClientStream::EnvoyQuicClientStream(
const envoy::config::core::v3::Http3ProtocolOptions& http3_options)
: quic::QuicSpdyClientStream(id, client_session, type),
EnvoyQuicStream(
- *this,
+ *this, *client_session,
// Flow control receive window should be larger than 8k so that the send buffer can fully
// utilize congestion control window before it reaches the high watermark.
static_cast(GetReceiveWindow().value()), *filterManagerConnection(),
@@ -31,6 +31,7 @@ EnvoyQuicClientStream::EnvoyQuicClientStream(
stats, http3_options) {
ASSERT(static_cast(GetReceiveWindow().value()) > 8 * 1024,
"Send buffer limit should be larger than 8KB.");
+ RegisterMetadataVisitor(this);
}
Http::Status EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& headers,
@@ -411,6 +412,17 @@ QuicFilterManagerConnectionImpl* EnvoyQuicClientStream::filterManagerConnection(
return dynamic_cast(session());
}
+void EnvoyQuicClientStream::OnMetadataComplete(size_t /*frame_len*/,
+ const quic::QuicHeaderList& header_list) {
+ if (mustRejectMetadata(header_list.uncompressed_header_bytes())) {
+ onStreamError(true, quic::QUIC_HEADERS_TOO_LARGE);
+ return;
+ }
+ if (!header_list.empty()) {
+ response_decoder_->decodeMetadata(metadataMapFromHeaderList(header_list));
+ }
+}
+
void EnvoyQuicClientStream::onStreamError(absl::optional should_close_connection,
quic::QuicRstStreamErrorCode rst_code) {
if (details_.empty()) {
diff --git a/source/common/quic/envoy_quic_client_stream.h b/source/common/quic/envoy_quic_client_stream.h
index 98607867ea5be..2cb1c5105909e 100644
--- a/source/common/quic/envoy_quic_client_stream.h
+++ b/source/common/quic/envoy_quic_client_stream.h
@@ -9,6 +9,8 @@
#endif
#include "quiche/common/simple_buffer_allocator.h"
#include "quiche/quic/core/http/quic_spdy_client_stream.h"
+#include "quiche/quic/core/qpack/qpack_encoder.h"
+#include "quiche/quic/core/qpack/qpack_instruction_encoder.h"
namespace Envoy {
namespace Quic {
@@ -16,7 +18,8 @@ namespace Quic {
// This class is a quic stream and also a request encoder.
class EnvoyQuicClientStream : public quic::QuicSpdyClientStream,
public EnvoyQuicStream,
- public Http::RequestEncoder {
+ public Http::RequestEncoder,
+ public quic::QuicSpdyStream::MetadataVisitor {
public:
EnvoyQuicClientStream(quic::QuicStreamId id, quic::QuicSpdyClientSession* client_session,
quic::StreamType type, Http::Http3::CodecStats& stats,
@@ -52,6 +55,9 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream,
void clearWatermarkBuffer();
+ // quic::QuicSpdyStream::MetadataVisitor
+ void OnMetadataComplete(size_t frame_len, const quic::QuicHeaderList& header_list) override;
+
protected:
// EnvoyQuicStream
void switchStreamBlockState() override;
diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc
index 1932010257f7c..94a82cce10d71 100644
--- a/source/common/quic/envoy_quic_server_stream.cc
+++ b/source/common/quic/envoy_quic_server_stream.cc
@@ -30,7 +30,7 @@ EnvoyQuicServerStream::EnvoyQuicServerStream(
headers_with_underscores_action)
: quic::QuicSpdyServerStreamBase(id, session, type),
EnvoyQuicStream(
- *this,
+ *this, *session,
// Flow control receive window should be larger than 8k to fully utilize congestion
// control window before it reaches the high watermark.
static_cast(GetReceiveWindow().value()), *filterManagerConnection(),
@@ -42,6 +42,7 @@ EnvoyQuicServerStream::EnvoyQuicServerStream(
stats_gatherer_ = new QuicStatsGatherer(&filterManagerConnection()->dispatcher().timeSource());
set_ack_listener(stats_gatherer_);
+ RegisterMetadataVisitor(this);
}
void EnvoyQuicServerStream::encode1xxHeaders(const Http::ResponseHeaderMap& headers) {
@@ -449,6 +450,17 @@ EnvoyQuicServerStream::validateHeader(absl::string_view header_name,
return result;
}
+void EnvoyQuicServerStream::OnMetadataComplete(size_t /*frame_len*/,
+ const quic::QuicHeaderList& header_list) {
+ if (mustRejectMetadata(header_list.uncompressed_header_bytes())) {
+ onStreamError(true, quic::QUIC_HEADERS_TOO_LARGE);
+ return;
+ }
+ if (!header_list.empty()) {
+ request_decoder_->decodeMetadata(metadataMapFromHeaderList(header_list));
+ }
+}
+
void EnvoyQuicServerStream::onStreamError(absl::optional should_close_connection,
quic::QuicRstStreamErrorCode rst) {
if (details_.empty()) {
diff --git a/source/common/quic/envoy_quic_server_stream.h b/source/common/quic/envoy_quic_server_stream.h
index bb9c32003b070..9153217efcb18 100644
--- a/source/common/quic/envoy_quic_server_stream.h
+++ b/source/common/quic/envoy_quic_server_stream.h
@@ -8,6 +8,8 @@
#include "quiche/common/platform/api/quiche_reference_counted.h"
#include "quiche/quic/core/http/quic_spdy_server_stream_base.h"
+#include "quiche/quic/core/qpack/qpack_encoder.h"
+#include "quiche/quic/core/qpack/qpack_instruction_encoder.h"
namespace Envoy {
namespace Quic {
@@ -15,7 +17,8 @@ namespace Quic {
// This class is a quic stream and also a response encoder.
class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase,
public EnvoyQuicStream,
- public Http::ResponseEncoder {
+ public Http::ResponseEncoder,
+ public quic::QuicSpdyStream::MetadataVisitor {
public:
EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSpdySession* session,
quic::StreamType type, Http::Http3::CodecStats& stats,
@@ -78,6 +81,9 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase,
Http::HeaderUtility::HeaderValidationResult
validateHeader(absl::string_view header_name, absl::string_view header_value) override;
+ // quic::QuicSpdyStream::MetadataVisitor
+ void OnMetadataComplete(size_t frame_len, const quic::QuicHeaderList& header_list) override;
+
protected:
// EnvoyQuicStream
void switchStreamBlockState() override;
diff --git a/source/common/quic/envoy_quic_stream.cc b/source/common/quic/envoy_quic_stream.cc
index 8ca1e7950466b..280c7ab915135 100644
--- a/source/common/quic/envoy_quic_stream.cc
+++ b/source/common/quic/envoy_quic_stream.cc
@@ -102,10 +102,87 @@ void EnvoyQuicStream::encodeTrailersImpl(spdy::Http2HeaderBlock&& trailers) {
onLocalEndStream();
}
-void EnvoyQuicStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) {
- // Metadata Frame is not supported in QUICHE.
- ENVOY_STREAM_LOG(debug, "METADATA is not supported in Http3.", *this);
- stats_.metadata_not_supported_error_.inc();
+std::unique_ptr
+EnvoyQuicStream::metadataMapFromHeaderList(const quic::QuicHeaderList& header_list) {
+ auto metadata_map = std::make_unique();
+ for (const auto& [key, value] : header_list) {
+ (*metadata_map)[key] = value;
+ }
+ return metadata_map;
+}
+
+namespace {
+
+// Returns a new `unique_ptr` containing the characters copied from `str`.
+std::unique_ptr dataFromString(const std::string& str) {
+ auto data = std::make_unique(str.length());
+ memcpy(&data[0], str.data(), str.length()); // NOLINT(safe-memcpy)
+ return data;
+}
+
+void serializeMetadata(const Http::MetadataMapPtr& metadata, quic::QuicStreamId id,
+ absl::InlinedVector& slices) {
+ quic::NoopDecoderStreamErrorDelegate decoder_stream_error_delegate;
+ quic::QpackEncoder qpack_encoder(&decoder_stream_error_delegate,
+ quic::HuffmanEncoding::kDisabled);
+
+ spdy::Http2HeaderBlock header_block;
+ for (const auto& [key, value] : *metadata) {
+ header_block.AppendValueOrAddHeader(key, value);
+ }
+
+ // The METADATA frame consist of a frame header, which includes payload
+ // length, and a payload, which is the QPACK-encoded metadata block. In order
+ // to generate the frame header, the payload needs to be generated first.
+ std::string metadata_frame_payload =
+ qpack_encoder.EncodeHeaderList(id, header_block,
+ /* encoder_stream_sent_byte_count = */ nullptr);
+ std::string metadata_frame_header =
+ quic::HttpEncoder::SerializeMetadataFrameHeader(metadata_frame_payload.size());
+
+ slices.emplace_back(dataFromString(metadata_frame_header), metadata_frame_header.length());
+ slices.emplace_back(dataFromString(metadata_frame_payload), metadata_frame_payload.length());
+}
+
+} // namespace
+
+void EnvoyQuicStream::encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) {
+ if (!http3_options_.allow_metadata()) {
+ ENVOY_STREAM_LOG(debug, "METADATA not supported by config.", *this);
+ stats_.metadata_not_supported_error_.inc();
+ return;
+ }
+ if (quic_stream_.write_side_closed()) {
+ return;
+ }
+ ASSERT(!local_end_stream_);
+
+ for (const Http::MetadataMapPtr& metadata : metadata_map_vector) {
+ absl::InlinedVector quic_slices;
+ quic_slices.reserve(2);
+ serializeMetadata(metadata, quic_stream_.id(), quic_slices);
+ absl::Span metadata_frame(quic_slices);
+
+ SendBufferMonitor::ScopedWatermarkBufferUpdater updater(&quic_stream_, this);
+ quic::QuicConsumedData result{0, false};
+ {
+ IncrementalBytesSentTracker tracker(quic_stream_, *mutableBytesMeter(), false);
+ result = quic_stream_.WriteMemSlices(metadata_frame, /*end_stream=*/false);
+ }
+ // QUIC stream must take all.
+ if (result.bytes_consumed == 0) {
+ IS_ENVOY_BUG(fmt::format("Send buffer didn't take all the data. Stream is write {} with {} "
+ "bytes in send buffer. Current write was rejected.",
+ quic_stream_.write_side_closed() ? "closed" : "open",
+ quic_stream_.BufferedDataBytes()));
+ quic_stream_.Reset(quic::QUIC_ERROR_PROCESSING_STREAM);
+ return;
+ }
+ if (!quic_session_.connection()->connected()) {
+ // Return early if sending METADATA caused the connection to close.
+ return;
+ }
+ }
}
} // namespace Quic
diff --git a/source/common/quic/envoy_quic_stream.h b/source/common/quic/envoy_quic_stream.h
index 360788d2fdcdb..bbe4759339ae2 100644
--- a/source/common/quic/envoy_quic_stream.h
+++ b/source/common/quic/envoy_quic_stream.h
@@ -33,13 +33,13 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder,
public:
// |buffer_limit| is the high watermark of the stream send buffer, and the low
// watermark will be half of it.
- EnvoyQuicStream(quic::QuicSpdyStream& quic_stream, uint32_t buffer_limit,
- QuicFilterManagerConnectionImpl& filter_manager_connection,
+ EnvoyQuicStream(quic::QuicSpdyStream& quic_stream, quic::QuicSession& quic_session,
+ uint32_t buffer_limit, QuicFilterManagerConnectionImpl& filter_manager_connection,
std::function below_low_watermark,
std::function above_high_watermark, Http::Http3::CodecStats& stats,
const envoy::config::core::v3::Http3ProtocolOptions& http3_options)
: Http::MultiplexedStreamImplBase(filter_manager_connection.dispatcher()), stats_(stats),
- http3_options_(http3_options), quic_stream_(quic_stream),
+ http3_options_(http3_options), quic_stream_(quic_stream), quic_session_(quic_session),
send_buffer_simulation_(buffer_limit / 2, buffer_limit, std::move(below_low_watermark),
std::move(above_high_watermark), ENVOY_LOGGER()),
filter_manager_connection_(filter_manager_connection),
@@ -180,6 +180,17 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder,
void encodeTrailersImpl(spdy::Http2HeaderBlock&& trailers);
+ // Converts `header_list` into a new `Http::MetadataMap`.
+ std::unique_ptr
+ metadataMapFromHeaderList(const quic::QuicHeaderList& header_list);
+
+ // Returns true if the cumulative limit on METADATA headers has been reached
+ // after adding `bytes`.
+ bool mustRejectMetadata(size_t bytes) {
+ received_metadata_bytes_ += bytes;
+ return received_metadata_bytes_ > 1 << 20;
+ }
+
#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS
// Setting |http_datagram_handler_| enables HTTP Datagram support.
std::unique_ptr http_datagram_handler_;
@@ -210,8 +221,9 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder,
bool saw_regular_headers_{false};
private:
- // QUIC stream that this EnvoyQuicStream wraps.
+ // QUIC stream and session that this EnvoyQuicStream wraps.
quic::QuicSpdyStream& quic_stream_;
+ quic::QuicSession& quic_session_;
// Keeps track of bytes buffered in the stream send buffer in QUICHE and reacts
// upon crossing high and low watermarks.
@@ -232,6 +244,7 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder,
absl::optional content_length_;
size_t received_content_bytes_{0};
http2::adapter::HeaderValidator header_validator_;
+ size_t received_metadata_bytes_{0};
};
// Object used for updating a BytesMeter to track bytes sent on a QuicStream since this object was
diff --git a/source/common/quic/platform/quiche_flags_constants.h b/source/common/quic/platform/quiche_flags_constants.h
index cff579bad68ce..a38bd99a0fc61 100644
--- a/source/common/quic/platform/quiche_flags_constants.h
+++ b/source/common/quic/platform/quiche_flags_constants.h
@@ -17,6 +17,8 @@
/* Envoy only supports RFC-v1 in the long term, so disable IETF draft 29 implementation by \
* default. */ \
KEY_VALUE_PAIR(quic_disable_version_draft_29, true) \
+ /* Enable support for HTTP/3 metadata decoding in QUICHE. */ \
+ KEY_VALUE_PAIR(quic_enable_http3_metadata_decoding, true) \
/* This flag enables BBR, otherwise QUIC will use Cubic which is less performant */ \
KEY_VALUE_PAIR(quic_default_to_bbr, true)
diff --git a/source/common/quic/platform/quiche_flags_impl.cc b/source/common/quic/platform/quiche_flags_impl.cc
index f676005f91b74..0f8d64fcfce95 100644
--- a/source/common/quic/platform/quiche_flags_impl.cc
+++ b/source/common/quic/platform/quiche_flags_impl.cc
@@ -117,7 +117,7 @@ FlagRegistry::FlagRegistry() : reloadable_flags_(makeReloadableFlagMap()) {}
FlagRegistry& FlagRegistry::getInstance() {
static auto* instance = new FlagRegistry();
ASSERT(sizeof(quiche_reloadable_flag_overrides) / sizeof(std::pair) ==
- 2);
+ 3);
ASSERT(sizeof(quiche_protocol_flag_overrides) /
sizeof(std::pair>) ==
3);
diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h
index 24a89f96d7c0f..78bc9b1566aa9 100644
--- a/test/integration/fake_upstream.h
+++ b/test/integration/fake_upstream.h
@@ -644,6 +644,7 @@ struct FakeUpstreamConfig {
http2_options_.set_allow_connect(true);
http2_options_.set_allow_metadata(true);
http3_options_.set_allow_extended_connect(true);
+ http3_options_.set_allow_metadata(true);
}
Event::TestTimeSystem& time_system_;
diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc
index c25103ffabb08..2064c9629c456 100644
--- a/test/integration/http_integration.cc
+++ b/test/integration/http_integration.cc
@@ -276,13 +276,14 @@ IntegrationCodecClientPtr HttpIntegrationTest::makeRawHttpConnection(
.value();
http2_options.value().set_allow_connect(true);
http2_options.value().set_allow_metadata(true);
+ }
#ifdef ENVOY_ENABLE_QUIC
- } else {
- cluster->http3_options_ = ConfigHelper::http2ToHttp3ProtocolOptions(
- http2_options.value(), quic::kStreamReceiveWindowLimit);
- cluster->http3_options_.set_allow_extended_connect(true);
+ cluster->http3_options_ = ConfigHelper::http2ToHttp3ProtocolOptions(
+ http2_options.value(), quic::kStreamReceiveWindowLimit);
+ cluster->http3_options_.set_allow_extended_connect(true);
+ cluster->http3_options_.set_allow_metadata(true);
#endif
- }
+
cluster->http2_options_ = http2_options.value();
cluster->http1_settings_.enable_trailers_ = true;
diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc
index d83e2e395ef2c..4f534ecaf40d6 100644
--- a/test/integration/multiplexed_integration_test.cc
+++ b/test/integration/multiplexed_integration_test.cc
@@ -276,7 +276,7 @@ static std::string response_metadata_filter = R"EOF(
name: response-metadata-filter
)EOF";
-class Http2MetadataIntegrationTest : public HttpProtocolIntegrationTest {
+class MetadataIntegrationTest : public HttpProtocolIntegrationTest {
public:
void SetUp() override {
HttpProtocolIntegrationTest::SetUp();
@@ -284,15 +284,25 @@ class Http2MetadataIntegrationTest : public HttpProtocolIntegrationTest {
[&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void {
RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, "");
ConfigHelper::HttpProtocolOptions protocol_options;
- protocol_options.mutable_explicit_http_config()
- ->mutable_http2_protocol_options()
- ->set_allow_metadata(true);
+ if (GetParam().upstream_protocol == Http::CodecType::HTTP3) {
+ protocol_options.mutable_explicit_http_config()
+ ->mutable_http3_protocol_options()
+ ->set_allow_metadata(true);
+ protocol_options.mutable_upstream_http_protocol_options()->set_auto_sni(true);
+ } else {
+ protocol_options.mutable_explicit_http_config()
+ ->mutable_http2_protocol_options()
+ ->set_allow_metadata(true);
+ }
ConfigHelper::setProtocolOptions(
*bootstrap.mutable_static_resources()->mutable_clusters(0), protocol_options);
});
config_helper_.addConfigModifier(
[&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
- hcm) -> void { hcm.mutable_http2_protocol_options()->set_allow_metadata(true); });
+ hcm) -> void {
+ hcm.mutable_http2_protocol_options()->set_allow_metadata(true);
+ hcm.mutable_http3_protocol_options()->set_allow_metadata(true);
+ });
}
void testRequestMetadataWithStopAllFilter();
@@ -301,6 +311,8 @@ class Http2MetadataIntegrationTest : public HttpProtocolIntegrationTest {
void runHeaderOnlyTest(bool send_request_body, size_t body_size);
+ Http::CodecClient::Type upstreamProtocol() { return GetParam().upstream_protocol; }
+
protected:
// Utility function to prepend filters. Note that the filters
// are added in reverse order.
@@ -312,7 +324,7 @@ class Http2MetadataIntegrationTest : public HttpProtocolIntegrationTest {
};
// Verifies metadata can be sent at different locations of the responses.
-TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) {
+TEST_P(MetadataIntegrationTest, ProxyMetadataInResponse) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -445,7 +457,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) {
test_server_->waitForCounterEq(counter, 1);
}
-TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadata) {
+TEST_P(MetadataIntegrationTest, ProxyMultipleMetadata) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -485,7 +497,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadata) {
// Disabled temporarily see #19040
#if 0
-TEST_P(Http2MetadataIntegrationTest, ProxyInvalidMetadata) {
+TEST_P(MetadataIntegrationTest, ProxyInvalidMetadata) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -516,14 +528,16 @@ TEST_P(Http2MetadataIntegrationTest, ProxyInvalidMetadata) {
#endif
void verifyExpectedMetadata(Http::MetadataMap metadata_map, std::set keys) {
+ EXPECT_EQ(metadata_map.size(), keys.size());
for (const auto& key : keys) {
// keys are the same as their corresponding values.
+ auto it = metadata_map.find(key);
+ ASSERT_FALSE(it == metadata_map.end()) << "key: " << key;
EXPECT_EQ(metadata_map.find(key)->second, key);
}
- EXPECT_EQ(metadata_map.size(), keys.size());
}
-TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) {
+TEST_P(MetadataIntegrationTest, TestResponseMetadata) {
prependFilters({response_metadata_filter});
config_helper_.addConfigModifier(
[&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
@@ -540,6 +554,11 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) {
ASSERT_TRUE(response->waitForEndStream());
ASSERT_TRUE(response->complete());
std::set expected_metadata_keys = {"headers", "duplicate"};
+ if (upstreamProtocol() == Http::CodecType::HTTP3) {
+ // HTTP/3 Sends "end stream" in an empty DATA frame which results in the test filter
+ // adding the "data" metadata header.
+ expected_metadata_keys.insert("data");
+ }
verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys);
// Upstream responds with headers and data.
@@ -571,13 +590,9 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) {
EXPECT_EQ(4, response->metadataMapsDecodedCount());
// Upstream responds with headers, 100-continue and data.
- response =
- codec_client_->makeRequestWithBody(Http::TestRequestHeaderMapImpl{{":method", "GET"},
- {":path", "/dynamo/url"},
- {":scheme", "http"},
- {":authority", "host"},
- {"expect", "100-contINUE"}},
- 10);
+ Http::TestRequestHeaderMapImpl headers = default_request_headers_;
+ headers.addCopy("expect", "100-contINUE");
+ response = codec_client_->makeRequestWithBody(headers, 10);
waitForNextUpstreamRequest();
upstream_request_->encode1xxHeaders(Http::TestResponseHeaderMapImpl{{":status", "100"}});
@@ -609,8 +624,17 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) {
expected_metadata_keys.erase("100-continue");
expected_metadata_keys.insert("aaa");
expected_metadata_keys.insert("keep");
+ if (upstreamProtocol() == Http::CodecType::HTTP3) {
+ // HTTP/3 Sends "end stream" in an empty DATA frame which results in the test filter
+ // adding the "data" metadata header.
+ expected_metadata_keys.insert("data");
+ }
verifyExpectedMetadata(response->metadataMap(), expected_metadata_keys);
- EXPECT_EQ(2, response->metadataMapsDecodedCount());
+ if (upstreamProtocol() == Http::CodecType::HTTP3) {
+ EXPECT_EQ(3, response->metadataMapsDecodedCount());
+ } else {
+ EXPECT_EQ(2, response->metadataMapsDecodedCount());
+ }
// Upstream responds with headers, data and metadata that will be consumed.
response = codec_client_->makeRequestWithBody(default_request_headers_, 10);
@@ -633,7 +657,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) {
EXPECT_EQ(3, response->metadataMapsDecodedCount());
}
-TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) {
+TEST_P(MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -659,7 +683,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) {
}
// Verifies small metadata can be sent at different locations of a request.
-TEST_P(Http2MetadataIntegrationTest, ProxySmallMetadataInRequest) {
+TEST_P(MetadataIntegrationTest, ProxySmallMetadataInRequest) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -688,7 +712,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxySmallMetadataInRequest) {
}
// Verifies large metadata can be sent at different locations of a request.
-TEST_P(Http2MetadataIntegrationTest, ProxyLargeMetadataInRequest) {
+TEST_P(MetadataIntegrationTest, ProxyLargeMetadataInRequest) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -717,7 +741,7 @@ TEST_P(Http2MetadataIntegrationTest, ProxyLargeMetadataInRequest) {
ASSERT_TRUE(response->complete());
}
-TEST_P(Http2MetadataIntegrationTest, RequestMetadataReachSizeLimit) {
+TEST_P(MetadataIntegrationTest, RequestMetadataReachSizeLimit) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -742,7 +766,7 @@ TEST_P(Http2MetadataIntegrationTest, RequestMetadataReachSizeLimit) {
ASSERT_FALSE(response->complete());
}
-TEST_P(Http2MetadataIntegrationTest, RequestMetadataThenTrailers) {
+TEST_P(MetadataIntegrationTest, RequestMetadataThenTrailers) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -765,7 +789,7 @@ static std::string request_metadata_filter = R"EOF(
name: request-metadata-filter
)EOF";
-TEST_P(Http2MetadataIntegrationTest, ConsumeAndInsertRequestMetadata) {
+TEST_P(MetadataIntegrationTest, ConsumeAndInsertRequestMetadata) {
prependFilters({request_metadata_filter});
config_helper_.addConfigModifier(
[&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
@@ -784,6 +808,11 @@ TEST_P(Http2MetadataIntegrationTest, ConsumeAndInsertRequestMetadata) {
// Verifies a headers metadata added.
std::set expected_metadata_keys = {"headers"};
expected_metadata_keys.insert("metadata");
+ if (downstreamProtocol() == Http::CodecType::HTTP3) {
+ // HTTP/3 Sends "end stream" in an empty DATA frame which results in the test filter
+ // adding the "data" metadata header.
+ expected_metadata_keys.insert("data");
+ }
verifyExpectedMetadata(upstream_request_->metadataMap(), expected_metadata_keys);
// Sends a headers only request with metadata. An empty data frame carries end_stream.
@@ -869,7 +898,7 @@ TEST_P(Http2MetadataIntegrationTest, ConsumeAndInsertRequestMetadata) {
EXPECT_EQ(upstream_request_->duplicatedMetadataKeyCount().find("metadata")->second, 6);
}
-void Http2MetadataIntegrationTest::runHeaderOnlyTest(bool send_request_body, size_t body_size) {
+void MetadataIntegrationTest::runHeaderOnlyTest(bool send_request_body, size_t body_size) {
config_helper_.addConfigModifier(
[&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
hcm) -> void { hcm.set_proxy_100_continue(true); });
@@ -880,18 +909,9 @@ void Http2MetadataIntegrationTest::runHeaderOnlyTest(bool send_request_body, siz
// Sends a request with body. Only headers will pass through filters.
IntegrationStreamDecoderPtr response;
if (send_request_body) {
- response = codec_client_->makeRequestWithBody(
- Http::TestRequestHeaderMapImpl{{":method", "POST"},
- {":path", "/test/long/url"},
- {":scheme", "http"},
- {":authority", "host"}},
- body_size);
+ response = codec_client_->makeRequestWithBody(default_request_headers_, body_size);
} else {
- response = codec_client_->makeHeaderOnlyRequest(
- Http::TestRequestHeaderMapImpl{{":method", "POST"},
- {":path", "/test/long/url"},
- {":scheme", "http"},
- {":authority", "host"}});
+ response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);
}
waitForNextUpstreamRequest();
@@ -900,10 +920,15 @@ void Http2MetadataIntegrationTest::runHeaderOnlyTest(bool send_request_body, siz
ASSERT_TRUE(response->complete());
}
-void Http2MetadataIntegrationTest::verifyHeadersOnlyTest() {
+void MetadataIntegrationTest::verifyHeadersOnlyTest() {
// Verifies a headers metadata added.
std::set expected_metadata_keys = {"headers"};
expected_metadata_keys.insert("metadata");
+ if (downstreamProtocol() == Http::CodecType::HTTP3) {
+ // HTTP/3 Sends "end stream" in an empty DATA frame which results in the test filter
+ // adding the "data" metadata header.
+ expected_metadata_keys.insert("data");
+ }
verifyExpectedMetadata(upstream_request_->metadataMap(), expected_metadata_keys);
// Verifies zero length data received, and end_stream is true.
@@ -912,14 +937,14 @@ void Http2MetadataIntegrationTest::verifyHeadersOnlyTest() {
EXPECT_EQ(true, upstream_request_->complete());
}
-TEST_P(Http2MetadataIntegrationTest, HeadersOnlyRequestWithRequestMetadata) {
+TEST_P(MetadataIntegrationTest, HeadersOnlyRequestWithRequestMetadata) {
prependFilters({request_metadata_filter});
// Send a headers only request.
runHeaderOnlyTest(false, 0);
verifyHeadersOnlyTest();
}
-void Http2MetadataIntegrationTest::testRequestMetadataWithStopAllFilter() {
+void MetadataIntegrationTest::testRequestMetadataWithStopAllFilter() {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -953,17 +978,17 @@ static std::string metadata_stop_all_filter = R"EOF(
name: metadata-stop-all-filter
)EOF";
-TEST_P(Http2MetadataIntegrationTest, RequestMetadataWithStopAllFilterBeforeMetadataFilter) {
+TEST_P(MetadataIntegrationTest, RequestMetadataWithStopAllFilterBeforeMetadataFilter) {
prependFilters({request_metadata_filter, metadata_stop_all_filter});
testRequestMetadataWithStopAllFilter();
}
-TEST_P(Http2MetadataIntegrationTest, RequestMetadataWithStopAllFilterAfterMetadataFilter) {
+TEST_P(MetadataIntegrationTest, RequestMetadataWithStopAllFilterAfterMetadataFilter) {
prependFilters({metadata_stop_all_filter, request_metadata_filter});
testRequestMetadataWithStopAllFilter();
}
-TEST_P(Http2MetadataIntegrationTest, TestAddEncodedMetadata) {
+TEST_P(MetadataIntegrationTest, TestAddEncodedMetadata) {
config_helper_.prependFilter(R"EOF(
name: encode-headers-return-stop-all-filter
)EOF");
@@ -1704,9 +1729,10 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, MultiplexedRingHashIntegrationTest,
{Http::CodecType::HTTP1})),
HttpProtocolIntegrationTest::protocolTestParamsToString);
-INSTANTIATE_TEST_SUITE_P(IpVersions, Http2MetadataIntegrationTest,
+INSTANTIATE_TEST_SUITE_P(IpVersions, MetadataIntegrationTest,
testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams(
- {Http::CodecType::HTTP2}, {Http::CodecType::HTTP2})),
+ {Http::CodecType::HTTP2, Http::CodecType::HTTP3},
+ {Http::CodecType::HTTP2, Http::CodecType::HTTP3})),
HttpProtocolIntegrationTest::protocolTestParamsToString);
void MultiplexedRingHashIntegrationTest::sendMultipleRequests(
@@ -2700,7 +2726,11 @@ TEST_P(Http2FrameIntegrationTest, DownstreamSendingEmptyMetadata) {
}
// Tests that an empty metadata map from upstream is ignored.
-TEST_P(Http2MetadataIntegrationTest, UpstreamSendingEmptyMetadata) {
+TEST_P(MetadataIntegrationTest, UpstreamSendingEmptyMetadata) {
+ if (upstreamProtocol() == Http::CodecType::HTTP3) {
+ // rawWriteConnection is not available for QUIC.
+ return;
+ }
initialize();
// Send a request and make sure an upstream connection is established.
@@ -2728,7 +2758,7 @@ TEST_P(Http2MetadataIntegrationTest, UpstreamSendingEmptyMetadata) {
}
// Tests upstream sending a metadata frame after ending a stream.
-TEST_P(Http2MetadataIntegrationTest, UpstreamMetadataAfterEndStream) {
+TEST_P(MetadataIntegrationTest, UpstreamMetadataAfterEndStream) {
initialize();
codec_client_ = makeHttpConnection(lookupPort("http"));
@@ -2758,6 +2788,7 @@ TEST_P(Http2MetadataIntegrationTest, UpstreamMetadataAfterEndStream) {
ASSERT_TRUE(fake_upstream_connection_->close());
ASSERT_TRUE(response->complete());
EXPECT_EQ("200", response->headers().getStatusValue());
+ cleanupUpstreamAndDownstream();
}
TEST_P(MultiplexedIntegrationTest, InvalidTrailers) {