diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8f8fd13044922..9b3425f5789a7 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -139,6 +139,12 @@ minor_behavior_changes: change: | Changing HTTP/2 semi-colon prefixed headers to being sanitized by Envoy code rather than nghttp2. Should be a functional no-op but guarded by ``envoy.reloadable_features.sanitize_http2_headers_without_nghttp2``. +- area: http + change: | + http: envoy will now proxy 104 headers from upstream, though as with 100s only the first 1xx response + headers will be sent.104 headers are designated by ietf's draft-ietf-httpbis-resumable-upload rfc. + This behavioral can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.proxy_104`` to ``false``. - area: jwt_authn change: | Changes the behavior of the diff --git a/contrib/generic_proxy/filters/network/source/codecs/http1/config.cc b/contrib/generic_proxy/filters/network/source/codecs/http1/config.cc index c9a8803a3903f..c5167e14fb5fb 100644 --- a/contrib/generic_proxy/filters/network/source/codecs/http1/config.cc +++ b/contrib/generic_proxy/filters/network/source/codecs/http1/config.cc @@ -604,6 +604,7 @@ Http::Http1::CallbackResult Http1ClientCodec::onMessageCompleteImpl() { // 101 Switching Protocols response. Ignore it because we don't support upgrade for now. // 102 Processing response. Ignore it. // 103 Early Hints response. Ignore it. + // 104 Upload Resumption Supported response. Ignore it. // Return success to continue parsing the actual response. return Http::Http1::CallbackResult::Success; diff --git a/envoy/http/codec.h b/envoy/http/codec.h index 5ad04479d47f1..7408d4085a30e 100644 --- a/envoy/http/codec.h +++ b/envoy/http/codec.h @@ -145,7 +145,8 @@ class ResponseEncoder : public virtual StreamEncoder { public: /** * Encode supported 1xx headers. - * Currently 100-Continue, 102-Processing, and 103-Early-Data headers are supported. + * Currently 100-Continue, 102-Processing, 103-Early-Data, and 104-Upload-Resumption-Supported + * headers are supported. * @param headers supplies the 1xx header map to encode. */ virtual void encode1xxHeaders(const ResponseHeaderMap& headers) PURE; @@ -270,7 +271,8 @@ class ResponseDecoder : public virtual StreamDecoder { public: /** * Called with decoded 1xx headers. - * Currently 100-Continue, 102-Processing, and 103-Early-Data headers are supported. + * Currently 100-Continue, 102-Processing, 103-Early-Data, and 104-Upload-Resumption-Supported + * headers are supported. * @param headers supplies the decoded 1xx headers map. */ virtual void decode1xxHeaders(ResponseHeaderMapPtr&& headers) PURE; diff --git a/mobile/library/java/org/chromium/net/impl/HttpReason.java b/mobile/library/java/org/chromium/net/impl/HttpReason.java index 9b8814cb561fc..c22f351a7bb41 100644 --- a/mobile/library/java/org/chromium/net/impl/HttpReason.java +++ b/mobile/library/java/org/chromium/net/impl/HttpReason.java @@ -23,6 +23,7 @@ private static Map buildReasonMap() { map.put(101, "Switching Protocols"); map.put(102, "Processing"); map.put(103, "Early Hints"); + map.put(104, "Upload Resumption Supported"); map.put(200, "OK"); map.put(201, "Created"); map.put(202, "Accepted"); diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index c06a0f68f3b20..ad6b5ba8658da 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -242,6 +242,10 @@ bool HeaderUtility::authorityIsValid(const absl::string_view header_value) { } bool HeaderUtility::isSpecial1xx(const ResponseHeaderMap& response_headers) { + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.proxy_104") && + response_headers.Status()->value() == "104") { + return true; + } return response_headers.Status()->value() == "100" || response_headers.Status()->value() == "102" || response_headers.Status()->value() == "103"; } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 78ad5cf1c6200..9acafc9aae2e3 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -72,6 +72,7 @@ RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name); RUNTIME_GUARD(envoy_reloadable_features_normalize_host_for_preresolve_dfp_dns); RUNTIME_GUARD(envoy_reloadable_features_oauth_use_url_encoding); RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout); +RUNTIME_GUARD(envoy_reloadable_features_proxy_104); RUNTIME_GUARD(envoy_reloadable_features_proxy_status_mapping_more_core_response_flags); RUNTIME_GUARD(envoy_reloadable_features_quic_fix_filter_manager_uaf); RUNTIME_GUARD(envoy_reloadable_features_quic_receive_ecn); diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index be6918b963d84..58ef006592473 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -2914,6 +2914,40 @@ TEST_P(Http1ClientConnectionImplTest, EarlyHintHeaders) { EXPECT_TRUE(status.ok()); } +// 104 response followed by 200 results in a [decode1xxHeaders, decodeHeaders] sequence. +TEST_P(Http1ClientConnectionImplTest, UploadResumptionSupportedHeaders) { + initialize(); + + NiceMock response_decoder; + Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); + TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); + + // As per Resumable Uploads for HTTP draft, the upload-draft-interop-version header + // must be passed through with 104 responses. + EXPECT_CALL(response_decoder, decode1xxHeaders_(_)) + .WillOnce(Invoke([&](ResponseHeaderMapPtr& headers) { + EXPECT_EQ(headers->get(Http::LowerCaseString("upload-draft-interop-version")).size(), 1); + EXPECT_EQ(headers->get(Http::LowerCaseString("upload-draft-interop-version"))[0] + ->value() + .getStringView(), + "4"); + })); + EXPECT_CALL(response_decoder, decodeData(_, _)).Times(0); + + Buffer::OwnedImpl initial_response( + "HTTP/1.1 104 Upload Resumption Supported\r\nUpload-Draft-Interop-Version: 4\r\n\r\n"); + auto status = codec_->dispatch(initial_response); + EXPECT_TRUE(status.ok()); + + EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); + EXPECT_CALL(response_decoder, decodeData(_, _)).Times(0); + + Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\n\r\n"); + status = codec_->dispatch(response); + EXPECT_TRUE(status.ok()); +} + // Multiple 100 responses are passed to the response encoder (who is responsible for coalescing). TEST_P(Http1ClientConnectionImplTest, MultipleContinueHeaders) { initialize(); diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index 1d28237960d24..37beba4f4f828 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -766,7 +766,7 @@ TEST_P(Http2CodecImplTest, MultipleContinueHeaders) { driveToCompletion(); }; -// 104 headers etc. are passed to the response encoder (who is responsibly for deciding to +// 105 headers etc. are passed to the response encoder (who is responsibly for deciding to // upgrade, ignore, etc.). TEST_P(Http2CodecImplTest, Unsupported1xxHeader) { initialize(); @@ -777,7 +777,7 @@ TEST_P(Http2CodecImplTest, Unsupported1xxHeader) { EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); driveToCompletion(); - TestResponseHeaderMapImpl other_headers{{":status", "104"}}; + TestResponseHeaderMapImpl other_headers{{":status", "105"}}; EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); response_encoder_->encodeHeaders(other_headers, false); driveToCompletion(); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index f97ec8038bc40..e62bc43914219 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -1553,6 +1553,10 @@ TEST_P(ProtocolIntegrationTest, EnvoyProxying103) { testEnvoyProxying1xx(false, false, false, "103"); } +TEST_P(ProtocolIntegrationTest, EnvoyProxying104) { + testEnvoyProxying1xx(false, false, false, "104"); +} + TEST_P(ProtocolIntegrationTest, TwoRequests) { testTwoRequests(); } TEST_P(ProtocolIntegrationTest, TwoRequestsWithForcedBackup) { testTwoRequests(true); } diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index c7a74df4c2a13..f254b197a0902 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -876,6 +876,7 @@ inotify inserter instantiation instantiations +interop interpretable intra ints @@ -1208,6 +1209,7 @@ resolv resolvers responder restarter +resumable resync ret retransmissions