diff --git a/test/integration/http2_integration_test.cc b/test/integration/http2_integration_test.cc index 3665698d89da6..cf011c258b9cc 100644 --- a/test/integration/http2_integration_test.cc +++ b/test/integration/http2_integration_test.cc @@ -95,18 +95,355 @@ TEST_P(Http2IntegrationTest, RetryAttemptCount) { testRetryAttemptCountHeader(); TEST_P(Http2IntegrationTest, EnvoyHandling100Continue) { testEnvoyHandling100Continue(); } +static std::string response_metadata_filter = R"EOF( +name: response-metadata-filter +config: {} +)EOF"; + +// Verifies metadata can be sent at different locations of the responses. TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { - testEnvoyProxyMetadataInResponse(); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Sends the first request. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata before response header. + const std::string key = "key"; + std::string value = std::string(80 * 1024, '1'); + Http::MetadataMap metadata_map = {{key, value}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + Http::MetadataMapVector metadata_map_vector; + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(12, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the second request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata after response header followed by an empty data frame with end_stream true. + value = std::string(10, '2'); + upstream_request_->encodeHeaders(default_response_headers_, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(0, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the third request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata after response header and before data. + value = std::string(10, '3'); + upstream_request_->encodeHeaders(default_response_headers_, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(10, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the fourth request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata between data frames. + value = std::string(10, '4'); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(10, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(10, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the fifth request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata after the last non-empty data frames. + value = std::string(10, '5'); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(10, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(0, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the sixth request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata before reset. + value = std::string(10, '6'); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(10, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeResetStream(); + + // Verifies stream is reset. + response->waitForReset(); + ASSERT_FALSE(response->complete()); } -TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadata) { testEnvoyProxyMultipleMetadata(); } +TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadata) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Sends a request. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + const int size = 4; + std::vector multiple_vecs(size); + for (int i = 0; i < size; i++) { + Runtime::RandomGeneratorImpl random; + int value_size = random.random() % Http::METADATA_MAX_PAYLOAD_SIZE + 1; + Http::MetadataMap metadata_map = {{std::string(i, 'a'), std::string(value_size, 'b')}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + multiple_vecs[i].push_back(std::move(metadata_map_ptr)); + } + upstream_request_->encodeMetadata(multiple_vecs[0]); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeMetadata(multiple_vecs[1]); + upstream_request_->encodeData(12, false); + upstream_request_->encodeMetadata(multiple_vecs[2]); + upstream_request_->encodeData(12, false); + upstream_request_->encodeMetadata(multiple_vecs[3]); + upstream_request_->encodeData(12, true); + + // Verifies multiple metadata are received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + for (int i = 0; i < size; i++) { + for (const auto& metadata : *multiple_vecs[i][0]) { + EXPECT_EQ(response->metadata_map().find(metadata.first)->second, metadata.second); + } + } + EXPECT_EQ(response->metadata_map().size(), multiple_vecs.size()); +} -TEST_P(Http2MetadataIntegrationTest, ProxyInvalidMetadata) { testEnvoyProxyInvalidMetadata(); } +TEST_P(Http2MetadataIntegrationTest, ProxyInvalidMetadata) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); -TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { testResponseMetadata(); } + // Sends a request. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends over-sized metadata before response header. + const std::string key = "key"; + std::string value = std::string(1024 * 1024, 'a'); + Http::MetadataMap metadata_map = {{key, value}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + Http::MetadataMapVector metadata_map_vector; + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(12, false); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(12, true); + + // Verifies metadata is not received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().size(), 0); +} + +void verifyExpectedMetadata(Http::MetadataMap metadata_map, std::set keys) { + for (const auto key : keys) { + // keys are the same as their corresponding values. + EXPECT_EQ(metadata_map.find(key)->second, key); + } + EXPECT_EQ(metadata_map.size(), keys.size()); +} + +TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { + + addFilters({response_metadata_filter}); + config_helper_.addConfigModifier( + [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) + -> void { hcm.set_proxy_100_continue(true); }); + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Upstream responds with headers. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(default_response_headers_, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers", "duplicate" and "keep". + std::set expected_metadata_keys = {"headers", "duplicate", "keep"}; + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + + // Upstream responds with headers and data. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(100, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers" and "duplicate" and metadata added in + // encodeData(): "data" and "duplicate" are received by the client. Note that "remove" is + // consumed. + expected_metadata_keys.insert("data"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("duplicate"), 2); + EXPECT_EQ(response->keyCount("keep"), 2); + + // Upstream responds with headers, data and trailers. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(10, false); + Http::TestHeaderMapImpl response_trailers{{"response", "trailer"}}; + upstream_request_->encodeTrailers(response_trailers); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in + // encodeData(): "data" and "duplicate", and metadata added in encodeTrailer(): "trailers" and + // "duplicate" are received by the client. Note that "remove" is consumed. + expected_metadata_keys.insert("trailers"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("duplicate"), 3); + EXPECT_EQ(response->keyCount("keep"), 4); + + // Upstream responds with headers, 100-continue and data. + response = codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/dynamo/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"expect", "100-continue"}}, + 10); + + waitForNextUpstreamRequest(); + upstream_request_->encode100ContinueHeaders(Http::TestHeaderMapImpl{{":status", "100"}}); + response->waitForContinueHeaders(); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(100, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders: "headers" and "duplicate", and metadata added in + // encodeData(): "data" and "duplicate", and metadata added in encode100Continue(): "100-continue" + // and "duplicate" are received by the client. Note that "remove" is consumed. + expected_metadata_keys.erase("trailers"); + expected_metadata_keys.insert("100-continue"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("duplicate"), 4); + EXPECT_EQ(response->keyCount("keep"), 4); + + // Upstream responds with headers and metadata that will not be consumed. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + Http::MetadataMap metadata_map = {{"aaa", "aaa"}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + Http::MetadataMapVector metadata_map_vector; + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeHeaders(default_response_headers_, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in + // encodeMetadata(): "aaa", "keep" and "duplicate" are received by the client. Note that "remove" + // is consumed. + expected_metadata_keys.erase("data"); + expected_metadata_keys.erase("100-continue"); + expected_metadata_keys.insert("aaa"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("keep"), 2); + + // Upstream responds with headers, data and metadata that will be consumed. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + metadata_map = {{"consume", "consume"}, {"remove", "remove"}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.clear(); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(100, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in + // encodeData(): "data", "duplicate", and metadata added in encodeMetadata(): "keep", "duplicate", + // "replace" are received by the client. Note that key "remove" and "consume" are consumed. + expected_metadata_keys.erase("aaa"); + expected_metadata_keys.insert("data"); + expected_metadata_keys.insert("replace"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("duplicate"), 2); + EXPECT_EQ(response->keyCount("keep"), 3); +} TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) { - testEnvoyMultipleMetadataReachSizeLimit(); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Sends a request. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends multiple metadata after response header until max size limit is reached. + upstream_request_->encodeHeaders(default_response_headers_, false); + const int size = 200; + std::vector multiple_vecs(size); + for (int i = 0; i < size; i++) { + Http::MetadataMap metadata_map = {{"key", std::string(10000, 'a')}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + multiple_vecs[i].push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(multiple_vecs[i]); + } + upstream_request_->encodeData(12, true); + + // Verifies reset is received. + response->waitForReset(); + ASSERT_FALSE(response->complete()); } TEST_P(Http2IntegrationTest, EnvoyHandlingDuplicate100Continue) { diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 0cee15a594947..be7c1cc488cec 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -42,23 +42,6 @@ using testing::Not; namespace Envoy { namespace { -std::string normalizeDate(const std::string& s) { - const std::regex date_regex("date:[^\r]+"); - return std::regex_replace(s, date_regex, "date: Mon, 01 Jan 2017 00:00:00 GMT"); -} - -void setAllowAbsoluteUrl( - envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) { - envoy::api::v2::core::Http1ProtocolOptions options; - options.mutable_allow_absolute_url()->set_value(true); - hcm.mutable_http_protocol_options()->CopyFrom(options); -}; - -void setAllowHttp10WithDefaultHost( - envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) { - hcm.mutable_http_protocol_options()->set_accept_http_10(true); - hcm.mutable_http_protocol_options()->set_default_host_for_http_10("default.com"); -} envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager::CodecType typeToCodecType(Http::CodecClient::Type type) { @@ -442,47 +425,6 @@ void HttpIntegrationTest::testRouterRedirect() { response->headers().get(Http::Headers::get().Location)->value().c_str()); } -void HttpIntegrationTest::testRouterDirectResponse() { - const std::string body = "Response body"; - const std::string file_path = TestEnvironment::writeStringToFileForTest("test_envoy", body); - static const std::string domain("direct.example.com"); - static const std::string prefix("/"); - static const Http::Code status(Http::Code::OK); - config_helper_.addConfigModifier( - [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) - -> void { - auto* route_config = hcm.mutable_route_config(); - auto* header_value_option = route_config->mutable_response_headers_to_add()->Add(); - header_value_option->mutable_header()->set_key("x-additional-header"); - header_value_option->mutable_header()->set_value("example-value"); - header_value_option->mutable_append()->set_value(false); - header_value_option = route_config->mutable_response_headers_to_add()->Add(); - header_value_option->mutable_header()->set_key("content-type"); - header_value_option->mutable_header()->set_value("text/html"); - header_value_option->mutable_append()->set_value(false); - auto* virtual_host = route_config->add_virtual_hosts(); - virtual_host->set_name(domain); - virtual_host->add_domains(domain); - virtual_host->add_routes()->mutable_match()->set_prefix(prefix); - virtual_host->mutable_routes(0)->mutable_direct_response()->set_status( - static_cast(status)); - virtual_host->mutable_routes(0)->mutable_direct_response()->mutable_body()->set_filename( - file_path); - }); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "GET", "/", "", downstream_protocol_, version_, "direct.example.com"); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("200", response->headers().Status()->value().c_str()); - EXPECT_STREQ("example-value", response->headers() - .get(Envoy::Http::LowerCaseString("x-additional-header")) - ->value() - .c_str()); - EXPECT_STREQ("text/html", response->headers().ContentType()->value().c_str()); - EXPECT_EQ(body, response->body()); -} - // Add a health check filter and verify correct computation of health based on upstream status. void HttpIntegrationTest::testComputedHealthCheck() { config_helper_.addFilter(R"EOF( @@ -1060,219 +1002,6 @@ void HttpIntegrationTest::testHittingEncoderFilterLimit() { EXPECT_STREQ("500", response->headers().Status()->value().c_str()); } -// Verifies metadata can be sent at different locations of the responses. -void HttpIntegrationTest::testEnvoyProxyMetadataInResponse() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Sends the first request. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata before response header. - const std::string key = "key"; - std::string value = std::string(80 * 1024, '1'); - Http::MetadataMap metadata_map = {{key, value}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - Http::MetadataMapVector metadata_map_vector; - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(12, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the second request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata after response header followed by an empty data frame with end_stream true. - value = std::string(10, '2'); - upstream_request_->encodeHeaders(default_response_headers_, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(0, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the third request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata after response header and before data. - value = std::string(10, '3'); - upstream_request_->encodeHeaders(default_response_headers_, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(10, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the fourth request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata between data frames. - value = std::string(10, '4'); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(10, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(10, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the fifth request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata after the last non-empty data frames. - value = std::string(10, '5'); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(10, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(0, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the sixth request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata before reset. - value = std::string(10, '6'); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(10, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeResetStream(); - - // Verifies stream is reset. - response->waitForReset(); - ASSERT_FALSE(response->complete()); -} - -void HttpIntegrationTest::testEnvoyProxyMultipleMetadata() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Sends a request. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - const int size = 4; - std::vector multiple_vecs(size); - for (int i = 0; i < size; i++) { - Runtime::RandomGeneratorImpl random; - int value_size = random.random() % Http::METADATA_MAX_PAYLOAD_SIZE + 1; - Http::MetadataMap metadata_map = {{std::string(i, 'a'), std::string(value_size, 'b')}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - multiple_vecs[i].push_back(std::move(metadata_map_ptr)); - } - upstream_request_->encodeMetadata(multiple_vecs[0]); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeMetadata(multiple_vecs[1]); - upstream_request_->encodeData(12, false); - upstream_request_->encodeMetadata(multiple_vecs[2]); - upstream_request_->encodeData(12, false); - upstream_request_->encodeMetadata(multiple_vecs[3]); - upstream_request_->encodeData(12, true); - - // Verifies multiple metadata are received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - for (int i = 0; i < size; i++) { - for (const auto& metadata : *multiple_vecs[i][0]) { - EXPECT_EQ(response->metadata_map().find(metadata.first)->second, metadata.second); - } - } - EXPECT_EQ(response->metadata_map().size(), multiple_vecs.size()); -} - -void HttpIntegrationTest::testEnvoyProxyInvalidMetadata() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Sends a request. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends over-sized metadata before response header. - const std::string key = "key"; - std::string value = std::string(1024 * 1024, 'a'); - Http::MetadataMap metadata_map = {{key, value}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - Http::MetadataMapVector metadata_map_vector; - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(12, false); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(12, true); - - // Verifies metadata is not received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().size(), 0); -} - -void HttpIntegrationTest::testEnvoyMultipleMetadataReachSizeLimit() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Sends a request. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends multiple metadata after response header until max size limit is reached. - upstream_request_->encodeHeaders(default_response_headers_, false); - const int size = 200; - std::vector multiple_vecs(size); - for (int i = 0; i < size; i++) { - Http::MetadataMap metadata_map = {{"key", std::string(10000, 'a')}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - multiple_vecs[i].push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(multiple_vecs[i]); - } - upstream_request_->encodeData(12, true); - - // Verifies reset is received. - response->waitForReset(); - ASSERT_FALSE(response->complete()); -} - void HttpIntegrationTest::testEnvoyHandling100Continue(bool additional_continue_from_upstream, const std::string& via) { initialize(); @@ -1563,240 +1292,6 @@ void HttpIntegrationTest::testTwoRequests(bool network_backup) { EXPECT_EQ(1024U, response->body().size()); } -void HttpIntegrationTest::testBadFirstline() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "hello", &response); - EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); -} - -void HttpIntegrationTest::testMissingDelimiter() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET / HTTP/1.1\r\nHost: host\r\nfoo bar\r\n\r\n", &response); - EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); -} - -void HttpIntegrationTest::testInvalidCharacterInFirstline() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GE(T / HTTP/1.1\r\nHost: host\r\n\r\n", - &response); - EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); -} - -void HttpIntegrationTest::testInvalidVersion() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.01\r\nHost: host\r\n\r\n", - &response); - EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); -} - -void HttpIntegrationTest::testHttp10DisabledWithUpgrade() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\nUpgrade: h2c\r\n\r\n", - &response, true); - EXPECT_TRUE(response.find("HTTP/1.1 426 Upgrade Required\r\n") == 0); -} - -void HttpIntegrationTest::testHttp10Disabled() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\n\r\n", &response, true); - EXPECT_TRUE(response.find("HTTP/1.1 426 Upgrade Required\r\n") == 0); -} - -// Turn HTTP/1.0 support on and verify the request is proxied and the default host is sent upstream. -void HttpIntegrationTest::testHttp10Enabled() { - autonomous_upstream_ = true; - config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\n\r\n", &response, false); - EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); - EXPECT_THAT(response, HasSubstr("connection: close")); - EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); - - std::unique_ptr upstream_headers = - reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); - ASSERT_TRUE(upstream_headers != nullptr); - EXPECT_EQ(upstream_headers->Host()->value(), "default.com"); - - sendRawHttpAndWaitForResponse(lookupPort("http"), "HEAD / HTTP/1.0\r\n\r\n", &response, false); - EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); - EXPECT_THAT(response, HasSubstr("connection: close")); - EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); -} - -// Verify for HTTP/1.0 a keep-alive header results in no connection: close. -// Also verify existing host headers are passed through for the HTTP/1.0 case. -void HttpIntegrationTest::testHttp10WithHostAndKeepAlive() { - autonomous_upstream_ = true; - config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET / HTTP/1.0\r\nHost: foo.com\r\nConnection:Keep-alive\r\n\r\n", - &response, true); - EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); - EXPECT_THAT(response, Not(HasSubstr("connection: close"))); - EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); - - std::unique_ptr upstream_headers = - reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); - ASSERT_TRUE(upstream_headers != nullptr); - EXPECT_EQ(upstream_headers->Host()->value(), "foo.com"); -} - -// Turn HTTP/1.0 support on and verify 09 style requests work. -void HttpIntegrationTest::testHttp09Enabled() { - autonomous_upstream_ = true; - config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET /\r\n\r\n", &response, false); - EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); - EXPECT_THAT(response, HasSubstr("connection: close")); - EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); - - std::unique_ptr upstream_headers = - reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); - ASSERT_TRUE(upstream_headers != nullptr); - EXPECT_EQ(upstream_headers->Host()->value(), "default.com"); -} - -void HttpIntegrationTest::testAbsolutePath() { - // Configure www.redirect.com to send a redirect, and ensure the redirect is - // encountered via absolute URL. - config_helper_.addRoute("www.redirect.com", "/", "cluster_0", true, - envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, - envoy::api::v2::route::VirtualHost::ALL); - config_helper_.addConfigModifier(&setAllowAbsoluteUrl); - - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET http://www.redirect.com HTTP/1.1\r\nHost: host\r\n\r\n", - &response, true); - EXPECT_FALSE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); -} - -void HttpIntegrationTest::testAbsolutePathWithPort() { - // Configure www.namewithport.com:1234 to send a redirect, and ensure the redirect is - // encountered via absolute URL with a port. - config_helper_.addRoute("www.namewithport.com:1234", "/", "cluster_0", true, - envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, - envoy::api::v2::route::VirtualHost::ALL); - config_helper_.addConfigModifier(&setAllowAbsoluteUrl); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse( - lookupPort("http"), "GET http://www.namewithport.com:1234 HTTP/1.1\r\nHost: host\r\n\r\n", - &response, true); - EXPECT_FALSE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); -} - -void HttpIntegrationTest::testAbsolutePathWithoutPort() { - // Add a restrictive default match, to avoid the request hitting the * / catchall. - config_helper_.setDefaultHostAndRoute("foo.com", "/found"); - // Set a matcher for www.namewithport.com:1234 and verify http://www.namewithport.com does not - // match - config_helper_.addRoute("www.namewithport.com:1234", "/", "cluster_0", true, - envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, - envoy::api::v2::route::VirtualHost::ALL); - config_helper_.addConfigModifier(&setAllowAbsoluteUrl); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET http://www.namewithport.com HTTP/1.1\r\nHost: host\r\n\r\n", - &response, true); - EXPECT_TRUE(response.find("HTTP/1.1 404 Not Found\r\n") == 0) << response; -} - -void HttpIntegrationTest::testAllowAbsoluteSameRelative() { - // TODO(mattwoodyard) run this test. - // Ensure that relative urls behave the same with allow_absolute_url enabled and without - testEquivalent("GET /foo/bar HTTP/1.1\r\nHost: host\r\n\r\n"); -} - -void HttpIntegrationTest::testConnect() { - // Ensure that connect behaves the same with allow_absolute_url enabled and without - testEquivalent("CONNECT www.somewhere.com:80 HTTP/1.1\r\nHost: host\r\n\r\n"); -} - -void HttpIntegrationTest::testInlineHeaders() { - autonomous_upstream_ = true; - config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET / HTTP/1.1\r\n" - "Host: foo.com\r\n" - "Foo: bar\r\n" - "Cache-control: public\r\n" - "Cache-control: 123\r\n" - "Eep: baz\r\n\r\n", - &response, true); - EXPECT_THAT(response, HasSubstr("HTTP/1.1 200 OK\r\n")); - - std::unique_ptr upstream_headers = - reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); - ASSERT_TRUE(upstream_headers != nullptr); - EXPECT_EQ(upstream_headers->Host()->value(), "foo.com"); - EXPECT_EQ(upstream_headers->CacheControl()->value(), "public,123"); - ASSERT_TRUE(upstream_headers->get(Envoy::Http::LowerCaseString("foo")) != nullptr); - EXPECT_STREQ("bar", upstream_headers->get(Envoy::Http::LowerCaseString("foo"))->value().c_str()); - ASSERT_TRUE(upstream_headers->get(Envoy::Http::LowerCaseString("eep")) != nullptr); - EXPECT_STREQ("baz", upstream_headers->get(Envoy::Http::LowerCaseString("eep"))->value().c_str()); -} - -void HttpIntegrationTest::testEquivalent(const std::string& request) { - config_helper_.addConfigModifier([&](envoy::config::bootstrap::v2::Bootstrap& bootstrap) -> void { - // Clone the whole listener. - auto static_resources = bootstrap.mutable_static_resources(); - auto* old_listener = static_resources->mutable_listeners(0); - auto* cloned_listener = static_resources->add_listeners(); - cloned_listener->CopyFrom(*old_listener); - old_listener->set_name("http_forward"); - }); - // Set the first listener to allow absolute URLs. - config_helper_.addConfigModifier(&setAllowAbsoluteUrl); - initialize(); - - std::string response1; - sendRawHttpAndWaitForResponse(lookupPort("http"), request.c_str(), &response1, true); - - std::string response2; - sendRawHttpAndWaitForResponse(lookupPort("http_forward"), request.c_str(), &response2, true); - - EXPECT_EQ(normalizeDate(response1), normalizeDate(response2)); -} - -void HttpIntegrationTest::testBadPath() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET http://api.lyft.com HTTP/1.1\r\nHost: host\r\n\r\n", &response, - true); - EXPECT_TRUE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); -} - -void HttpIntegrationTest::testNoHost() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - Http::TestHeaderMapImpl request_headers{ - {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}}; - auto response = codec_client_->makeHeaderOnlyRequest(request_headers); - response->waitForEndStream(); - - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("400", response->headers().Status()->value().c_str()); -} - void HttpIntegrationTest::testValidZeroLengthContent() { initialize(); @@ -1903,28 +1398,6 @@ void HttpIntegrationTest::testLargeRequestHeaders(uint32_t size, uint32_t max_si } } -void HttpIntegrationTest::testUpstreamProtocolError() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - auto encoder_decoder = codec_client_->startRequest(Http::TestHeaderMapImpl{ - {":method", "GET"}, {":path", "/test/long/url"}, {":authority", "host"}}); - auto response = std::move(encoder_decoder.second); - - FakeRawConnectionPtr fake_upstream_connection; - ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); - // TODO(mattklein123): Waiting for exact amount of data is a hack. This needs to - // be fixed. - std::string data; - ASSERT_TRUE(fake_upstream_connection->waitForData(187, &data)); - ASSERT_TRUE(fake_upstream_connection->write("bad protocol data!")); - ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - codec_client_->waitForDisconnect(); - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); -} - void HttpIntegrationTest::testHeadersOnlyFilterEncoding() { config_helper_.addFilter(R"EOF( name: encode-headers-only @@ -2156,143 +1629,6 @@ void HttpIntegrationTest::testTrailers(uint64_t request_size, uint64_t response_ } } -static std::string response_metadata_filter = R"EOF( -name: response-metadata-filter -config: {} -)EOF"; - -void verifyExpectedMetadata(Http::MetadataMap metadata_map, std::set keys) { - for (const auto key : keys) { - // keys are the same as their corresponding values. - EXPECT_EQ(metadata_map.find(key)->second, key); - } - EXPECT_EQ(metadata_map.size(), keys.size()); -} - -void HttpIntegrationTest::testResponseMetadata() { - addFilters({response_metadata_filter}); - config_helper_.addConfigModifier( - [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) - -> void { hcm.set_proxy_100_continue(true); }); - - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Upstream responds with headers. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(default_response_headers_, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers", "duplicate" and "keep". - std::set expected_metadata_keys = {"headers", "duplicate", "keep"}; - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - - // Upstream responds with headers and data. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(100, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers" and "duplicate" and metadata added in - // encodeData(): "data" and "duplicate" are received by the client. Note that "remove" is - // consumed. - expected_metadata_keys.insert("data"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("duplicate"), 2); - EXPECT_EQ(response->keyCount("keep"), 2); - - // Upstream responds with headers, data and trailers. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(10, false); - Http::TestHeaderMapImpl response_trailers{{"response", "trailer"}}; - upstream_request_->encodeTrailers(response_trailers); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in - // encodeData(): "data" and "duplicate", and metadata added in encodeTrailer(): "trailers" and - // "duplicate" are received by the client. Note that "remove" is consumed. - expected_metadata_keys.insert("trailers"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("duplicate"), 3); - EXPECT_EQ(response->keyCount("keep"), 4); - - // Upstream responds with headers, 100-continue and data. - response = codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "GET"}, - {":path", "/dynamo/url"}, - {":scheme", "http"}, - {":authority", "host"}, - {"expect", "100-continue"}}, - 10); - - waitForNextUpstreamRequest(); - upstream_request_->encode100ContinueHeaders(Http::TestHeaderMapImpl{{":status", "100"}}); - response->waitForContinueHeaders(); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(100, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders: "headers" and "duplicate", and metadata added in - // encodeData(): "data" and "duplicate", and metadata added in encode100Continue(): "100-continue" - // and "duplicate" are received by the client. Note that "remove" is consumed. - expected_metadata_keys.erase("trailers"); - expected_metadata_keys.insert("100-continue"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("duplicate"), 4); - EXPECT_EQ(response->keyCount("keep"), 4); - - // Upstream responds with headers and metadata that will not be consumed. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - Http::MetadataMap metadata_map = {{"aaa", "aaa"}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - Http::MetadataMapVector metadata_map_vector; - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeHeaders(default_response_headers_, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in - // encodeMetadata(): "aaa", "keep" and "duplicate" are received by the client. Note that "remove" - // is consumed. - expected_metadata_keys.erase("data"); - expected_metadata_keys.erase("100-continue"); - expected_metadata_keys.insert("aaa"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("keep"), 2); - - // Upstream responds with headers, data and metadata that will be consumed. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - metadata_map = {{"consume", "consume"}, {"remove", "remove"}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.clear(); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(100, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in - // encodeData(): "data", "duplicate", and metadata added in encodeMetadata(): "keep", "duplicate", - // "replace" are received by the client. Note that key "remove" and "consume" are consumed. - expected_metadata_keys.erase("aaa"); - expected_metadata_keys.insert("data"); - expected_metadata_keys.insert("replace"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("duplicate"), 2); - EXPECT_EQ(response->keyCount("keep"), 3); -} - std::string HttpIntegrationTest::listenerStatPrefix(const std::string& stat_name) { if (version_ == Network::Address::IpVersion::v4) { return "listener.127.0.0.1_0." + stat_name; diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index fa7ddfbdd7119..a0d2b0d2bc8a9 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -130,7 +130,6 @@ class HttpIntegrationTest : public BaseIntegrationTest { int upstream_index); void testRouterRedirect(); - void testRouterDirectResponse(); void testRouterNotFound(); void testRouterNotFoundWithBody(); void testRouterClusterNotFound404(); @@ -160,28 +159,9 @@ class HttpIntegrationTest : public BaseIntegrationTest { void testHeadersOnlyFilterEncodingIntermediateFilters(); void testHeadersOnlyFilterDecodingIntermediateFilters(); void testHeadersOnlyFilterInterleaved(); - // HTTP/1 tests - void testBadFirstline(); - void testMissingDelimiter(); - void testInvalidCharacterInFirstline(); - void testInvalidVersion(); - void testHttp10Disabled(); - void testHttp10DisabledWithUpgrade(); - void testHttp09Enabled(); - void testHttp10Enabled(); - void testHttp10WithHostAndKeepAlive(); - void testUpstreamProtocolError(); - void testBadPath(); - void testAbsolutePath(); - void testAbsolutePathWithPort(); - void testAbsolutePathWithoutPort(); - void testConnect(); - void testInlineHeaders(); - void testAllowAbsoluteSameRelative(); + // Test that a request returns the same content with both allow_absolute_urls enabled and // allow_absolute_urls disabled - void testEquivalent(const std::string& request); - void testNoHost(); void testDefaultHost(); void testValidZeroLengthContent(); void testInvalidContentLength(); @@ -198,11 +178,6 @@ class HttpIntegrationTest : public BaseIntegrationTest { void testRetryHostPredicateFilter(); void testHittingDecoderFilterLimit(); void testHittingEncoderFilterLimit(); - void testEnvoyProxyMetadataInResponse(); - void testEnvoyProxyMultipleMetadata(); - void testEnvoyProxyInvalidMetadata(); - void testResponseMetadata(); - void testEnvoyMultipleMetadataReachSizeLimit(); void testEnvoyHandling100Continue(bool additional_continue_from_upstream = false, const std::string& via = ""); void testEnvoyProxying100Continue(bool continue_before_upstream_complete = false, diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index b1c2c984fc47a..5e250d981e753 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -8,6 +8,7 @@ #include "common/http/headers.h" #include "common/protobuf/utility.h" +#include "test/integration/autonomous_upstream.h" #include "test/integration/utility.h" #include "test/mocks/http/mocks.h" #include "test/test_common/network_utility.h" @@ -26,6 +27,28 @@ using testing::Not; namespace Envoy { +namespace { + +std::string normalizeDate(const std::string& s) { + const std::regex date_regex("date:[^\r]+"); + return std::regex_replace(s, date_regex, "date: Mon, 01 Jan 2017 00:00:00 GMT"); +} + +void setAllowAbsoluteUrl( + envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) { + envoy::api::v2::core::Http1ProtocolOptions options; + options.mutable_allow_absolute_url()->set_value(true); + hcm.mutable_http_protocol_options()->CopyFrom(options); +}; + +void setAllowHttp10WithDefaultHost( + envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) { + hcm.mutable_http_protocol_options()->set_accept_http_10(true); + hcm.mutable_http_protocol_options()->set_default_host_for_http_10("default.com"); +} + +} // namespace + INSTANTIATE_TEST_SUITE_P(IpVersions, IntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); @@ -40,7 +63,46 @@ TEST_P(IntegrationTest, RouterClusterNotFound503) { testRouterClusterNotFound503 TEST_P(IntegrationTest, RouterRedirect) { testRouterRedirect(); } -TEST_P(IntegrationTest, RouterDirectResponse) { testRouterDirectResponse(); } +TEST_P(IntegrationTest, RouterDirectResponse) { + const std::string body = "Response body"; + const std::string file_path = TestEnvironment::writeStringToFileForTest("test_envoy", body); + static const std::string domain("direct.example.com"); + static const std::string prefix("/"); + static const Http::Code status(Http::Code::OK); + config_helper_.addConfigModifier( + [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) + -> void { + auto* route_config = hcm.mutable_route_config(); + auto* header_value_option = route_config->mutable_response_headers_to_add()->Add(); + header_value_option->mutable_header()->set_key("x-additional-header"); + header_value_option->mutable_header()->set_value("example-value"); + header_value_option->mutable_append()->set_value(false); + header_value_option = route_config->mutable_response_headers_to_add()->Add(); + header_value_option->mutable_header()->set_key("content-type"); + header_value_option->mutable_header()->set_value("text/html"); + header_value_option->mutable_append()->set_value(false); + auto* virtual_host = route_config->add_virtual_hosts(); + virtual_host->set_name(domain); + virtual_host->add_domains(domain); + virtual_host->add_routes()->mutable_match()->set_prefix(prefix); + virtual_host->mutable_routes(0)->mutable_direct_response()->set_status( + static_cast(status)); + virtual_host->mutable_routes(0)->mutable_direct_response()->mutable_body()->set_filename( + file_path); + }); + initialize(); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/", "", downstream_protocol_, version_, "direct.example.com"); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); + EXPECT_STREQ("example-value", response->headers() + .get(Envoy::Http::LowerCaseString("x-additional-header")) + ->value() + .c_str()); + EXPECT_STREQ("text/html", response->headers().ContentType()->value().c_str()); + EXPECT_EQ(body, response->body()); +} TEST_P(IntegrationTest, ComputedHealthCheck) { testComputedHealthCheck(); } @@ -198,37 +260,230 @@ TEST_P(IntegrationTest, HittingGrpcFilterLimitBufferingHeaders) { TEST_P(IntegrationTest, HittingEncoderFilterLimit) { testHittingEncoderFilterLimit(); } -TEST_P(IntegrationTest, BadFirstline) { testBadFirstline(); } +TEST_P(IntegrationTest, BadFirstline) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "hello", &response); + EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); +} + +TEST_P(IntegrationTest, MissingDelimiter) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET / HTTP/1.1\r\nHost: host\r\nfoo bar\r\n\r\n", &response); + EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); +} + +TEST_P(IntegrationTest, InvalidCharacterInFirstline) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GE(T / HTTP/1.1\r\nHost: host\r\n\r\n", + &response); + EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); +} + +TEST_P(IntegrationTest, InvalidVersion) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.01\r\nHost: host\r\n\r\n", + &response); + EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); +} + +TEST_P(IntegrationTest, Http10Disabled) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\n\r\n", &response, true); + EXPECT_TRUE(response.find("HTTP/1.1 426 Upgrade Required\r\n") == 0); +} + +TEST_P(IntegrationTest, Http10DisabledWithUpgrade) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\nUpgrade: h2c\r\n\r\n", + &response, true); + EXPECT_TRUE(response.find("HTTP/1.1 426 Upgrade Required\r\n") == 0); +} + +// Turn HTTP/1.0 support on and verify 09 style requests work. +TEST_P(IntegrationTest, Http09Enabled) { + autonomous_upstream_ = true; + config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET /\r\n\r\n", &response, false); + EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); + EXPECT_THAT(response, HasSubstr("connection: close")); + EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); + + std::unique_ptr upstream_headers = + reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); + ASSERT_TRUE(upstream_headers != nullptr); + EXPECT_EQ(upstream_headers->Host()->value(), "default.com"); +} + +// Turn HTTP/1.0 support on and verify the request is proxied and the default host is sent upstream. +TEST_P(IntegrationTest, Http10Enabled) { + autonomous_upstream_ = true; + config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\n\r\n", &response, false); + EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); + EXPECT_THAT(response, HasSubstr("connection: close")); + EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); + + std::unique_ptr upstream_headers = + reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); + ASSERT_TRUE(upstream_headers != nullptr); + EXPECT_EQ(upstream_headers->Host()->value(), "default.com"); + + sendRawHttpAndWaitForResponse(lookupPort("http"), "HEAD / HTTP/1.0\r\n\r\n", &response, false); + EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); + EXPECT_THAT(response, HasSubstr("connection: close")); + EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); +} -TEST_P(IntegrationTest, MissingDelimiter) { testMissingDelimiter(); } +TEST_P(IntegrationTest, TestInlineHeaders) { + autonomous_upstream_ = true; + config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET / HTTP/1.1\r\n" + "Host: foo.com\r\n" + "Foo: bar\r\n" + "Cache-control: public\r\n" + "Cache-control: 123\r\n" + "Eep: baz\r\n\r\n", + &response, true); + EXPECT_THAT(response, HasSubstr("HTTP/1.1 200 OK\r\n")); -TEST_P(IntegrationTest, InvalidCharacterInFirstline) { testInvalidCharacterInFirstline(); } + std::unique_ptr upstream_headers = + reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); + ASSERT_TRUE(upstream_headers != nullptr); + EXPECT_EQ(upstream_headers->Host()->value(), "foo.com"); + EXPECT_EQ(upstream_headers->CacheControl()->value(), "public,123"); + ASSERT_TRUE(upstream_headers->get(Envoy::Http::LowerCaseString("foo")) != nullptr); + EXPECT_STREQ("bar", upstream_headers->get(Envoy::Http::LowerCaseString("foo"))->value().c_str()); + ASSERT_TRUE(upstream_headers->get(Envoy::Http::LowerCaseString("eep")) != nullptr); + EXPECT_STREQ("baz", upstream_headers->get(Envoy::Http::LowerCaseString("eep"))->value().c_str()); +} -TEST_P(IntegrationTest, InvalidVersion) { testInvalidVersion(); } +// Verify for HTTP/1.0 a keep-alive header results in no connection: close. +// Also verify existing host headers are passed through for the HTTP/1.0 case. +TEST_P(IntegrationTest, Http10WithHostandKeepAlive) { + autonomous_upstream_ = true; + config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET / HTTP/1.0\r\nHost: foo.com\r\nConnection:Keep-alive\r\n\r\n", + &response, true); + EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); + EXPECT_THAT(response, Not(HasSubstr("connection: close"))); + EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); + + std::unique_ptr upstream_headers = + reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); + ASSERT_TRUE(upstream_headers != nullptr); + EXPECT_EQ(upstream_headers->Host()->value(), "foo.com"); +} -TEST_P(IntegrationTest, Http10Disabled) { testHttp10Disabled(); } +TEST_P(IntegrationTest, NoHost) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); -TEST_P(IntegrationTest, Http10DisabledWithUpgrade) { testHttp10DisabledWithUpgrade(); } + Http::TestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}}; + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + response->waitForEndStream(); -TEST_P(IntegrationTest, Http09Enabled) { testHttp09Enabled(); } + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("400", response->headers().Status()->value().c_str()); +} -TEST_P(IntegrationTest, Http10Enabled) { testHttp10Enabled(); } +TEST_P(IntegrationTest, BadPath) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET http://api.lyft.com HTTP/1.1\r\nHost: host\r\n\r\n", &response, + true); + EXPECT_TRUE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); +} -TEST_P(IntegrationTest, TestInlineHeaders) { testInlineHeaders(); } +TEST_P(IntegrationTest, AbsolutePath) { + // Configure www.redirect.com to send a redirect, and ensure the redirect is + // encountered via absolute URL. + config_helper_.addRoute("www.redirect.com", "/", "cluster_0", true, + envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, + envoy::api::v2::route::VirtualHost::ALL); + config_helper_.addConfigModifier(&setAllowAbsoluteUrl); -TEST_P(IntegrationTest, Http10WithHostandKeepAlive) { testHttp10WithHostAndKeepAlive(); } + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET http://www.redirect.com HTTP/1.1\r\nHost: host\r\n\r\n", + &response, true); + EXPECT_FALSE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); +} -TEST_P(IntegrationTest, NoHost) { testNoHost(); } +TEST_P(IntegrationTest, AbsolutePathWithPort) { + // Configure www.namewithport.com:1234 to send a redirect, and ensure the redirect is + // encountered via absolute URL with a port. + config_helper_.addRoute("www.namewithport.com:1234", "/", "cluster_0", true, + envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, + envoy::api::v2::route::VirtualHost::ALL); + config_helper_.addConfigModifier(&setAllowAbsoluteUrl); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse( + lookupPort("http"), "GET http://www.namewithport.com:1234 HTTP/1.1\r\nHost: host\r\n\r\n", + &response, true); + EXPECT_FALSE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); +} -TEST_P(IntegrationTest, BadPath) { testBadPath(); } +TEST_P(IntegrationTest, AbsolutePathWithoutPort) { + // Add a restrictive default match, to avoid the request hitting the * / catchall. + config_helper_.setDefaultHostAndRoute("foo.com", "/found"); + // Set a matcher for www.namewithport.com:1234 and verify http://www.namewithport.com does not + // match + config_helper_.addRoute("www.namewithport.com:1234", "/", "cluster_0", true, + envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, + envoy::api::v2::route::VirtualHost::ALL); + config_helper_.addConfigModifier(&setAllowAbsoluteUrl); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET http://www.namewithport.com HTTP/1.1\r\nHost: host\r\n\r\n", + &response, true); + EXPECT_TRUE(response.find("HTTP/1.1 404 Not Found\r\n") == 0) << response; +} -TEST_P(IntegrationTest, AbsolutePath) { testAbsolutePath(); } +// Ensure that connect behaves the same with allow_absolute_url enabled and without +TEST_P(IntegrationTest, Connect) { + const std::string& request = "CONNECT www.somewhere.com:80 HTTP/1.1\r\nHost: host\r\n\r\n"; + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v2::Bootstrap& bootstrap) -> void { + // Clone the whole listener. + auto static_resources = bootstrap.mutable_static_resources(); + auto* old_listener = static_resources->mutable_listeners(0); + auto* cloned_listener = static_resources->add_listeners(); + cloned_listener->CopyFrom(*old_listener); + old_listener->set_name("http_forward"); + }); + // Set the first listener to allow absolute URLs. + config_helper_.addConfigModifier(&setAllowAbsoluteUrl); + initialize(); -TEST_P(IntegrationTest, AbsolutePathWithPort) { testAbsolutePathWithPort(); } + std::string response1; + sendRawHttpAndWaitForResponse(lookupPort("http"), request.c_str(), &response1, true); -TEST_P(IntegrationTest, AbsolutePathWithoutPort) { testAbsolutePathWithoutPort(); } + std::string response2; + sendRawHttpAndWaitForResponse(lookupPort("http_forward"), request.c_str(), &response2, true); -TEST_P(IntegrationTest, Connect) { testConnect(); } + EXPECT_EQ(normalizeDate(response1), normalizeDate(response2)); +} TEST_P(IntegrationTest, ValidZeroLengthContent) { testValidZeroLengthContent(); } @@ -240,7 +495,27 @@ TEST_P(IntegrationTest, LargeHeadersRejected) { testLargeRequestHeaders(62, 60); TEST_P(IntegrationTest, LargeHeadersAccepted) { testLargeRequestHeaders(62, 63); } -TEST_P(IntegrationTest, UpstreamProtocolError) { testUpstreamProtocolError(); } +TEST_P(IntegrationTest, UpstreamProtocolError) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto encoder_decoder = codec_client_->startRequest(Http::TestHeaderMapImpl{ + {":method", "GET"}, {":path", "/test/long/url"}, {":authority", "host"}}); + auto response = std::move(encoder_decoder.second); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + // TODO(mattklein123): Waiting for exact amount of data is a hack. This needs to + // be fixed. + std::string data; + ASSERT_TRUE(fake_upstream_connection->waitForData(187, &data)); + ASSERT_TRUE(fake_upstream_connection->write("bad protocol data!")); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + codec_client_->waitForDisconnect(); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); +} TEST_P(IntegrationTest, TestHead) { initialize(); diff --git a/test/integration/integration_test.h b/test/integration/integration_test.h index 96f17288e1712..40d32f782a582 100644 --- a/test/integration/integration_test.h +++ b/test/integration/integration_test.h @@ -4,6 +4,7 @@ #include "gtest/gtest.h" +// A test class for testing HTTP/1.1 upstream and downstreams namespace Envoy { class IntegrationTest : public HttpIntegrationTest, public testing::TestWithParam {