Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Minor Behavior Changes
* bandwidth_limit: added :ref:`bandwidth limit stats <config_http_filters_bandwidth_limit>` *request_enforced* and *response_enforced*.
* config: the log message for "gRPC config stream closed" now uses the most recent error message, and reports seconds instead of milliseconds for how long the most recent status has been received.
* dns: now respecting the returned DNS TTL for resolved hosts, rather than always relying on the hard-coded :ref:`dns_refresh_rate. <envoy_v3_api_field_config.cluster.v3.Cluster.dns_refresh_rate>` This behavior can be temporarily reverted by setting the runtime guard ``envoy.reloadable_features.use_dns_ttl`` to false.
* http: envoy will now proxy 102 and 103 headers from upstream, though as with 100s only the first 1xx response headers will be sent. This behavioral change by can temporarily reverted by setting runtime guard ``envoy.reloadable_features.proxy_102_103`` to false.
* http: usage of the experimental matching API is no longer guarded behind a feature flag, as the corresponding protobuf fields have been marked as WIP.
* json: switching from rapidjson to nlohmann/json. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.remove_legacy_json`` to false.
* listener: destroy per network filter chain stats when a network filter chain is removed during the listener in place update.
Expand Down
4 changes: 2 additions & 2 deletions envoy/http/codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ class ResponseEncoder : public virtual StreamEncoder {
public:
/**
* Encode supported 1xx headers.
* Currently only 100-Continue headers are supported.
* Currently 100-Continue, 102-Processing, and 103-Early-Data headers are supported.
* @param headers supplies the 1xx header map to encode.
*/
virtual void encode1xxHeaders(const ResponseHeaderMap& headers) PURE;
Expand Down Expand Up @@ -237,7 +237,7 @@ class ResponseDecoder : public virtual StreamDecoder {
public:
/**
* Called with decoded 1xx headers.
* Currently only 100-Continue headers are supported.
* Currently 100-Continue, 102-Processing, and 103-Early-Data headers are supported.
* @param headers supplies the decoded 1xx headers map.
*/
virtual void decode1xxHeaders(ResponseHeaderMapPtr&& headers) PURE;
Expand Down
5 changes: 5 additions & 0 deletions source/common/http/header_utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ bool HeaderUtility::authorityIsValid(const absl::string_view header_value) {
}

bool HeaderUtility::isSpecial1xx(const ResponseHeaderMap& response_headers) {
if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.proxy_102_103") &&
(response_headers.Status()->value() == "102" ||
response_headers.Status()->value() == "103")) {
return true;
}
return response_headers.Status()->value() == "100";
}

Expand Down
4 changes: 2 additions & 2 deletions source/common/http/http1/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ void StreamEncoderImpl::encodeHeadersBase(const RequestOrResponseHeaderMap& head
if (saw_content_length || disable_chunk_encoding_) {
chunk_encoding_ = false;
} else {
if (status && *status == 100) {
// Make sure we don't serialize chunk information with 100-Continue headers.
if (status && (*status == 100 || *status == 102 || *status == 103)) {
// Make sure we don't serialize chunk information with 1xx headers.
chunk_encoding_ = false;
} else if (status && *status == 304 && connection_.noChunkedEncodingHeaderFor304()) {
// For 304 response, since it should never have a body, we should not need to chunk_encode at
Expand Down
2 changes: 1 addition & 1 deletion source/common/http/http2/codec_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ void ConnectionImpl::ClientStreamImpl::decodeHeaders() {
received_noninformational_headers_ =
!CodeUtility::is1xx(status) || status == enumToInt(Http::Code::SwitchingProtocols);

if (status == enumToInt(Http::Code::Continue)) {
if (HeaderUtility::isSpecial1xx(*headers)) {
ASSERT(!remote_end_stream_);
response_decoder_.decode1xxHeaders(std::move(headers));
} else {
Expand Down
5 changes: 3 additions & 2 deletions source/common/router/router.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1227,8 +1227,9 @@ void Filter::handleNon5xxResponseHeaders(absl::optional<Grpc::Status::GrpcStatus

void Filter::onUpstream1xxHeaders(Http::ResponseHeaderMapPtr&& headers,
UpstreamRequest& upstream_request) {
chargeUpstreamCode(100, *headers, upstream_request.upstreamHost(), false);
ENVOY_STREAM_LOG(debug, "upstream 100 continue", *callbacks_);
const uint64_t response_code = Http::Utility::getResponseStatus(*headers);
chargeUpstreamCode(response_code, *headers, upstream_request.upstreamHost(), false);
ENVOY_STREAM_LOG(debug, "upstream 1xx ({}).", *callbacks_, response_code);

downstream_response_started_ = true;
final_upstream_request_ = &upstream_request;
Expand Down
1 change: 1 addition & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ constexpr const char* runtime_features[] = {
"envoy.reloadable_features.new_tcp_connection_pool",
"envoy.reloadable_features.no_chunked_encoding_header_for_304",
"envoy.reloadable_features.preserve_downstream_scheme",
"envoy.reloadable_features.proxy_102_103",
"envoy.reloadable_features.remove_legacy_json",
"envoy.reloadable_features.require_strict_1xx_and_204_response_headers",
"envoy.reloadable_features.send_strict_1xx_and_204_response_headers",
Expand Down
50 changes: 46 additions & 4 deletions test/common/http/http1/codec_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2508,6 +2508,50 @@ TEST_F(Http1ClientConnectionImplTest, ContinueHeaders) {
EXPECT_TRUE(status.ok());
}

// 102 response followed by 200 results in a [decode1xxHeaders, decodeHeaders] sequence.
TEST_F(Http1ClientConnectionImplTest, ProcessingHeaders) {
initialize();

NiceMock<MockResponseDecoder> 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());

EXPECT_CALL(response_decoder, decode1xxHeaders_(_));
EXPECT_CALL(response_decoder, decodeData(_, _)).Times(0);
Buffer::OwnedImpl initial_response("HTTP/1.1 102 Processing\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());
}

// 103 response followed by 200 results in a [decode1xxHeaders, decodeHeaders] sequence.
TEST_F(Http1ClientConnectionImplTest, EarlyHintHeaders) {
initialize();

NiceMock<MockResponseDecoder> 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());

EXPECT_CALL(response_decoder, decode1xxHeaders_(_));
EXPECT_CALL(response_decoder, decodeData(_, _)).Times(0);
Buffer::OwnedImpl initial_response("HTTP/1.1 103 Early Hints\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_F(Http1ClientConnectionImplTest, MultipleContinueHeaders) {
initialize();
Expand Down Expand Up @@ -2536,9 +2580,7 @@ TEST_F(Http1ClientConnectionImplTest, MultipleContinueHeaders) {
EXPECT_TRUE(status.ok());
}

// 101/102 headers etc. are passed to the response encoder (who is responsibly for deciding to
// upgrade, ignore, etc.).
TEST_F(Http1ClientConnectionImplTest, 1xxNonContinueHeaders) {
TEST_F(Http1ClientConnectionImplTest, Unsupported1xxHeader) {
initialize();

NiceMock<MockResponseDecoder> response_decoder;
Expand All @@ -2547,7 +2589,7 @@ TEST_F(Http1ClientConnectionImplTest, 1xxNonContinueHeaders) {
EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok());

EXPECT_CALL(response_decoder, decodeHeaders_(_, false));
Buffer::OwnedImpl response("HTTP/1.1 102 Processing\r\n\r\n");
Buffer::OwnedImpl response("HTTP/1.1 199 Unknown\r\n\r\n");
auto status = codec_->dispatch(response);
EXPECT_TRUE(status.ok());
}
Expand Down
6 changes: 3 additions & 3 deletions test/common/http/http2/codec_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -527,17 +527,17 @@ TEST_P(Http2CodecImplTest, MultipleContinueHeaders) {
response_encoder_->encodeHeaders(response_headers, true);
};

// 101/102 headers etc. are passed to the response encoder (who is responsibly for deciding to
// 104 headers etc. are passed to the response encoder (who is responsibly for deciding to
// upgrade, ignore, etc.).
TEST_P(Http2CodecImplTest, 1xxNonContinueHeaders) {
TEST_P(Http2CodecImplTest, Unsupported1xxHeader) {
initialize();

TestRequestHeaderMapImpl request_headers;
HttpTestUtility::addDefaultHeaders(request_headers);
EXPECT_CALL(request_decoder_, decodeHeaders_(_, true));
EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok());

TestResponseHeaderMapImpl other_headers{{":status", "102"}};
TestResponseHeaderMapImpl other_headers{{":status", "104"}};
EXPECT_CALL(response_decoder_, decodeHeaders_(_, false));
response_encoder_->encodeHeaders(other_headers, false);
};
Expand Down
4 changes: 2 additions & 2 deletions test/common/quic/envoy_quic_client_stream_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,14 @@ TEST_F(EnvoyQuicClientStreamTest, PostRequestAnd1xx) {
}));
EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false))
.WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) {
EXPECT_EQ("103", headers->getStatusValue());
EXPECT_EQ("199", headers->getStatusValue());
EXPECT_EQ("1", headers->get(Http::LowerCaseString("i"))[0]->value().getStringView());
}));
size_t offset = 0;
size_t i = 0;
// Receive several 10x headers, only the first 100 Continue header should be
// delivered.
for (const std::string& status : {"100", "103", "100"}) {
for (const std::string& status : {"100", "199", "100"}) {
spdy::SpdyHeaderBlock continue_header;
continue_header[":status"] = status;
continue_header["i"] = absl::StrCat("", i++);
Expand Down
25 changes: 14 additions & 11 deletions test/integration/http_integration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,7 @@ void HttpIntegrationTest::testEnvoyHandling1xx(bool additional_continue_from_ups
auto response = std::move(encoder_decoder.second);
ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_));
// The continue headers should arrive immediately.
response->waitForContinueHeaders();
response->waitFor1xxHeaders();
ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_));

// Send the rest of the request.
Expand All @@ -994,7 +994,7 @@ void HttpIntegrationTest::testEnvoyHandling1xx(bool additional_continue_from_ups
}

if (disconnect_after_100) {
response->waitForContinueHeaders();
response->waitFor1xxHeaders();
codec_client_->close();
EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("100"));
ASSERT_TRUE(fake_upstream_connection_->close());
Expand All @@ -1020,7 +1020,8 @@ void HttpIntegrationTest::testEnvoyHandling1xx(bool additional_continue_from_ups

void HttpIntegrationTest::testEnvoyProxying1xx(bool continue_before_upstream_complete,
bool with_encoder_filter,
bool with_multiple_1xx_headers) {
bool with_multiple_1xx_headers,
absl::string_view initial_code) {
if (with_encoder_filter) {
// Add a filter to make sure 100s play well with them.
config_helper_.prependFilter("name: passthrough-filter");
Expand All @@ -1044,37 +1045,39 @@ void HttpIntegrationTest::testEnvoyProxying1xx(bool continue_before_upstream_com
ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_));
ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_));

// This case tests sending on 100-Continue headers before the client has sent all the
// request data.
if (continue_before_upstream_complete) {
upstream_request_->encode1xxHeaders(
Http::TestResponseHeaderMapImpl{{":status", initial_code.data()}});
if (with_multiple_1xx_headers) {
upstream_request_->encode1xxHeaders(Http::TestResponseHeaderMapImpl{{":status", "100"}});
upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "102"}}, false);
upstream_request_->encode1xxHeaders(Http::TestResponseHeaderMapImpl{{":status", "100"}});
}
// This case tests sending on 100-Continue headers before the client has sent all the
// request data.
upstream_request_->encode1xxHeaders(Http::TestResponseHeaderMapImpl{{":status", "100"}});
response->waitForContinueHeaders();
response->waitFor1xxHeaders();
}
// Send all of the request data and wait for it to be received upstream.
codec_client_->sendData(*request_encoder_, 10, true);
ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_));

// This case tests forwarding 100-Continue after the client has sent all data.
if (!continue_before_upstream_complete) {
upstream_request_->encode1xxHeaders(
Http::TestResponseHeaderMapImpl{{":status", initial_code.data()}});
if (with_multiple_1xx_headers) {
upstream_request_->encode1xxHeaders(Http::TestResponseHeaderMapImpl{{":status", "100"}});
upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "102"}}, false);
upstream_request_->encode1xxHeaders(Http::TestResponseHeaderMapImpl{{":status", "100"}});
}
// This case tests forwarding 100-Continue after the client has sent all data.
upstream_request_->encode1xxHeaders(Http::TestResponseHeaderMapImpl{{":status", "100"}});
response->waitForContinueHeaders();
response->waitFor1xxHeaders();
}
// Now send the rest of the response.
upstream_request_->encodeHeaders(default_response_headers_, true);
ASSERT_TRUE(response->waitForEndStream());
EXPECT_TRUE(response->complete());
ASSERT(response->informationalHeaders() != nullptr);
EXPECT_EQ("100", response->informationalHeaders()->getStatusValue());
EXPECT_EQ(initial_code, response->informationalHeaders()->getStatusValue());

EXPECT_EQ("200", response->headers().getStatusValue());
}
Expand Down
3 changes: 2 additions & 1 deletion test/integration/http_integration.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,8 @@ class HttpIntegrationTest : public BaseIntegrationTest {
const std::string& via = "", bool disconnect_after_100 = false);
void testEnvoyProxying1xx(bool continue_before_upstream_complete = false,
bool with_encoder_filter = false,
bool with_multiple_1xx_headers = false);
bool with_multiple_1xx_headers = false,
absl::string_view initial_code = "100");
void simultaneousRequest(uint32_t request1_bytes, uint32_t request2_bytes,
uint32_t response1_bytes, uint32_t response2_bytes);

Expand Down
2 changes: 1 addition & 1 deletion test/integration/integration_stream_decoder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace Envoy {
IntegrationStreamDecoder::IntegrationStreamDecoder(Event::Dispatcher& dispatcher)
: dispatcher_(dispatcher) {}

void IntegrationStreamDecoder::waitForContinueHeaders() {
void IntegrationStreamDecoder::waitFor1xxHeaders() {
if (!continue_headers_.get()) {
waiting_for_continue_headers_ = true;
dispatcher_.run(Event::Dispatcher::RunType::Block);
Expand Down
2 changes: 1 addition & 1 deletion test/integration/integration_stream_decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class IntegrationStreamDecoder : public Http::ResponseDecoder, public Http::Stre
const Http::MetadataMap& metadataMap() { return *metadata_map_; }
uint64_t keyCount(std::string key) { return duplicated_metadata_key_count_[key]; }
uint32_t metadataMapsDecodedCount() const { return metadata_maps_decoded_count_; }
void waitForContinueHeaders();
void waitFor1xxHeaders();
void waitForHeaders();
// This function waits until body_ has at least size bytes in it (it might have more). clearBody()
// can be used if the previous body data is not relevant and the test wants to wait for a specific
Expand Down
2 changes: 1 addition & 1 deletion test/integration/multiplexed_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) {

waitForNextUpstreamRequest();
upstream_request_->encode1xxHeaders(Http::TestResponseHeaderMapImpl{{":status", "100"}});
response->waitForContinueHeaders();
response->waitFor1xxHeaders();
upstream_request_->encodeHeaders(default_response_headers_, false);
upstream_request_->encodeData(100, true);

Expand Down
8 changes: 8 additions & 0 deletions test/integration/protocol_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,14 @@ TEST_P(ProtocolIntegrationTest, EnvoyProxyingLateMultiple1xx) {
testEnvoyProxying1xx(false, false, true);
}

TEST_P(ProtocolIntegrationTest, EnvoyProxying102) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also have a 103 test here?

testEnvoyProxying1xx(false, false, false, "102");
}

TEST_P(ProtocolIntegrationTest, EnvoyProxying103) {
testEnvoyProxying1xx(false, false, false, "103");
}

TEST_P(ProtocolIntegrationTest, TwoRequests) { testTwoRequests(); }

TEST_P(ProtocolIntegrationTest, TwoRequestsWithForcedBackup) { testTwoRequests(true); }
Expand Down