-
Notifications
You must be signed in to change notification settings - Fork 5.3k
http3: Add support for HTTP/3 METADATA #32568
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
Changes from all commits
7b6b381
71c28a9
6c0a232
5fcca3a
71cfd1c
3a7ab40
94ba4eb
5b6dcb5
01a0255
04f65b6
3dc9c20
bf454f5
ba391eb
cc29fb4
c38b40d
8771c89
c527858
fd0f89b
74858e6
33a9dad
19a35c6
756c8a5
fd47793
e2d1fe2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<Http::MetadataMap> | ||
| EnvoyQuicStream::metadataMapFromHeaderList(const quic::QuicHeaderList& header_list) { | ||
| auto metadata_map = std::make_unique<Http::MetadataMap>(); | ||
| for (const auto& [key, value] : header_list) { | ||
| (*metadata_map)[key] = value; | ||
| } | ||
| return metadata_map; | ||
| } | ||
|
|
||
| namespace { | ||
|
|
||
| // Returns a new `unique_ptr<char[]>` containing the characters copied from `str`. | ||
| std::unique_ptr<char[]> dataFromString(const std::string& str) { | ||
| auto data = std::make_unique<char[]>(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<quiche::QuicheMemSlice, 2>& 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_); | ||
|
alyssawilk marked this conversation as resolved.
|
||
|
|
||
| for (const Http::MetadataMapPtr& metadata : metadata_map_vector) { | ||
| absl::InlinedVector<quiche::QuicheMemSlice, 2> quic_slices; | ||
| quic_slices.reserve(2); | ||
| serializeMetadata(metadata, quic_stream_.id(), quic_slices); | ||
| absl::Span<quiche::QuicheMemSlice> 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Connection might be closed during write loop. Maybe early return in that case?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| } | ||
| // QUIC stream must take all. | ||
| if (result.bytes_consumed == 0) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be true if the metadata passed in is empty?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, because the METADATA frame header will always be present. |
||
| 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 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in H3, can metadata arrive before headers? I think it's an invariant of Envoy that headers arrive first and I'm not sure if it's an invariant of the quiche libraries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
METADATA is an HTTP/3 frame which is delivered on the QUIC stream, so it's guaranteed to be delivered in the order it was sent. I think this is true for HTTP/2 as well. But if a peer sent a METADATA frame before sending HEADERS I think QUICHE will expose it in that order. I think that's also the same for HTTP/2. But we could make this an error of some here, if you prefer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should either make it an error, or add an e2e test it doesn't cause problems in Envoy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! I think we have a test for this already!
ProxyMetadataInResponsedoes: