From e537816865c81b73dc937a52433e9ca5c99c3c11 Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Mon, 1 Jun 2020 16:40:41 -0400 Subject: [PATCH 01/10] Add /echoheaders request path handler to test-server filter. Requests with this path get response with the dump of request headers instead of generated output. Signed-off-by: Misha Efimov --- source/server/http_test_server_filter.cc | 8 +++++++- source/server/http_test_server_filter.h | 1 + .../http_test_server_filter_integration_test.cc | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/source/server/http_test_server_filter.cc b/source/server/http_test_server_filter.cc index 2e6bd183c..0115a4f87 100644 --- a/source/server/http_test_server_filter.cc +++ b/source/server/http_test_server_filter.cc @@ -56,7 +56,7 @@ void HttpTestServerDecoderFilter::applyConfigToResponseHeaders( void HttpTestServerDecoderFilter::sendReply() { if (error_message_ == absl::nullopt) { decoder_callbacks_->sendLocalReply( - static_cast(200), std::string(base_config_.response_body_size(), 'a'), + static_cast(200), request_headers_dump_.has_value() ? *request_headers_dump_ : std::string(base_config_.response_body_size(), 'a'), [this](Envoy::Http::ResponseHeaderMap& direct_response_headers) { applyConfigToResponseHeaders(direct_response_headers, base_config_); }, @@ -78,6 +78,12 @@ HttpTestServerDecoderFilter::decodeHeaders(Envoy::Http::RequestHeaderMap& header if (request_config_header) { mergeJsonConfig(request_config_header->value().getStringView(), base_config_, error_message_); } + absl::string_view request_path = headers.Path()->value().getStringView(); + if (request_path.find("/echoheaders") != absl::string_view::npos) { + std::stringstream headers_dump; + headers_dump << "\nRequest Headers:\n" << headers; + request_headers_dump_ = headers_dump.str(); + } if (end_stream) { sendReply(); } diff --git a/source/server/http_test_server_filter.h b/source/server/http_test_server_filter.h index 113e6bbca..f7ba094a2 100644 --- a/source/server/http_test_server_filter.h +++ b/source/server/http_test_server_filter.h @@ -73,6 +73,7 @@ class HttpTestServerDecoderFilter : public Envoy::Http::StreamDecoderFilter { Envoy::Http::StreamDecoderFilterCallbacks* decoder_callbacks_; nighthawk::server::ResponseOptions base_config_; absl::optional error_message_; + absl::optional request_headers_dump_; }; } // namespace Server diff --git a/test/server/http_test_server_filter_integration_test.cc b/test/server/http_test_server_filter_integration_test.cc index 8d73636f5..aa6ec34b5 100644 --- a/test/server/http_test_server_filter_integration_test.cc +++ b/test/server/http_test_server_filter_integration_test.cc @@ -189,6 +189,22 @@ TEST_P(HttpTestServerIntegrationTest, TestHeaderConfig) { EXPECT_EQ(std::string(10, 'a'), response->body()); } +TEST_P(HttpTestServerIntegrationTest, TestEchoHeaders) { + Envoy::BufferingStreamDecoderPtr response = makeSingleRequest( + lookupPort("http"), "GET", "/echoheaders", "", downstream_protocol_, version_, "foo.com", "", + [](Envoy::Http::RequestHeaderMapImpl& request_headers) { + request_headers.addCopy(Envoy::Http::LowerCaseString("gray"), "pidgeon"); + request_headers.addCopy(Envoy::Http::LowerCaseString("red"), "fox"); + }); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().Status()->value().getStringView()); + EXPECT_THAT(response->body(), HasSubstr(R"(':authority', 'foo.com')")); + EXPECT_THAT(response->body(), HasSubstr(R"(':path', '/echoheaders')")); + EXPECT_THAT(response->body(), HasSubstr(R"(':method', 'GET')")); + EXPECT_THAT(response->body(), HasSubstr(R"('gray', 'pidgeon')")); + EXPECT_THAT(response->body(), HasSubstr(R"('red', 'fox')")); +} + class HttpTestServerIntegrationNoConfigTest : public HttpTestServerIntegrationTestBase { public: void SetUp() override { initialize(); } From ca88dac61aad14c67bf811a38eb2a099861c5c82 Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Mon, 1 Jun 2020 17:09:55 -0400 Subject: [PATCH 02/10] Fix format. Signed-off-by: Misha Efimov --- source/server/http_test_server_filter.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/server/http_test_server_filter.cc b/source/server/http_test_server_filter.cc index 0115a4f87..e4e2ee94c 100644 --- a/source/server/http_test_server_filter.cc +++ b/source/server/http_test_server_filter.cc @@ -56,7 +56,9 @@ void HttpTestServerDecoderFilter::applyConfigToResponseHeaders( void HttpTestServerDecoderFilter::sendReply() { if (error_message_ == absl::nullopt) { decoder_callbacks_->sendLocalReply( - static_cast(200), request_headers_dump_.has_value() ? *request_headers_dump_ : std::string(base_config_.response_body_size(), 'a'), + static_cast(200), + request_headers_dump_.has_value() ? *request_headers_dump_ + : std::string(base_config_.response_body_size(), 'a'), [this](Envoy::Http::ResponseHeaderMap& direct_response_headers) { applyConfigToResponseHeaders(direct_response_headers, base_config_); }, @@ -80,9 +82,9 @@ HttpTestServerDecoderFilter::decodeHeaders(Envoy::Http::RequestHeaderMap& header } absl::string_view request_path = headers.Path()->value().getStringView(); if (request_path.find("/echoheaders") != absl::string_view::npos) { - std::stringstream headers_dump; - headers_dump << "\nRequest Headers:\n" << headers; - request_headers_dump_ = headers_dump.str(); + std::stringstream headers_dump; + headers_dump << "\nRequest Headers:\n" << headers; + request_headers_dump_ = headers_dump.str(); } if (end_stream) { sendReply(); From 58dcb1fe7a97067c34ab4c12e5ed9b3c2c750be4 Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Tue, 2 Jun 2020 10:40:56 -0400 Subject: [PATCH 03/10] Use echo_request_headers ResponseOption instead of /echoheaders path. Signed-off-by: Misha Efimov --- api/server/response_options.proto | 7 ++++++- source/server/README.md | 11 +++++++++++ source/server/http_test_server_filter.cc | 11 ++++++----- .../http_test_server_filter_integration_test.cc | 6 ++++-- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/api/server/response_options.proto b/api/server/response_options.proto index 17ed8e1e9..69b3bd7d6 100644 --- a/api/server/response_options.proto +++ b/api/server/response_options.proto @@ -6,7 +6,12 @@ import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; import "envoy/api/v2/core/base.proto"; +// Options that control the test server response. message ResponseOptions { + // List of additional response headers. repeated envoy.api.v2.core.HeaderValueOption response_headers = 1; + // Number of 'a' characters in the the response body. uint32 response_body_size = 2 [(validate.rules).uint32 = {lte: 4194304}]; -} \ No newline at end of file + // If true, then echo request headers in the response body. + bool echo_request_headers = 3; +} diff --git a/source/server/README.md b/source/server/README.md index bdc72cb26..f9f91ab61 100644 --- a/source/server/README.md +++ b/source/server/README.md @@ -66,6 +66,17 @@ admin: port_value: 8081 ``` +## Response Options config + +The ResponseOptions proto can be used in the test server config or passed in `x-nighthawk-test-server-config`request header. +The following parameters are available: + +* `response_body_size` - number of 'a' characters repeated in the response body. +* `response_headers` - list of headers to add to response. If `append` is set to + `true`, then the header is appended. +* `echo_request_headers` - return the dump of request headers in the response + body. + ## Running the test server diff --git a/source/server/http_test_server_filter.cc b/source/server/http_test_server_filter.cc index e4e2ee94c..b43c413ed 100644 --- a/source/server/http_test_server_filter.cc +++ b/source/server/http_test_server_filter.cc @@ -55,10 +55,12 @@ void HttpTestServerDecoderFilter::applyConfigToResponseHeaders( void HttpTestServerDecoderFilter::sendReply() { if (error_message_ == absl::nullopt) { + std::string response_body(base_config_.response_body_size(), 'a'); + if (request_headers_dump_.has_value()) { + response_body += *request_headers_dump_; + } decoder_callbacks_->sendLocalReply( - static_cast(200), - request_headers_dump_.has_value() ? *request_headers_dump_ - : std::string(base_config_.response_body_size(), 'a'), + static_cast(200), response_body, [this](Envoy::Http::ResponseHeaderMap& direct_response_headers) { applyConfigToResponseHeaders(direct_response_headers, base_config_); }, @@ -80,8 +82,7 @@ HttpTestServerDecoderFilter::decodeHeaders(Envoy::Http::RequestHeaderMap& header if (request_config_header) { mergeJsonConfig(request_config_header->value().getStringView(), base_config_, error_message_); } - absl::string_view request_path = headers.Path()->value().getStringView(); - if (request_path.find("/echoheaders") != absl::string_view::npos) { + if (base_config_.echo_request_headers()) { std::stringstream headers_dump; headers_dump << "\nRequest Headers:\n" << headers; request_headers_dump_ = headers_dump.str(); diff --git a/test/server/http_test_server_filter_integration_test.cc b/test/server/http_test_server_filter_integration_test.cc index aa6ec34b5..0d5e6bf6b 100644 --- a/test/server/http_test_server_filter_integration_test.cc +++ b/test/server/http_test_server_filter_integration_test.cc @@ -191,15 +191,17 @@ TEST_P(HttpTestServerIntegrationTest, TestHeaderConfig) { TEST_P(HttpTestServerIntegrationTest, TestEchoHeaders) { Envoy::BufferingStreamDecoderPtr response = makeSingleRequest( - lookupPort("http"), "GET", "/echoheaders", "", downstream_protocol_, version_, "foo.com", "", + lookupPort("http"), "GET", "/somepath", "", downstream_protocol_, version_, "foo.com", "", [](Envoy::Http::RequestHeaderMapImpl& request_headers) { request_headers.addCopy(Envoy::Http::LowerCaseString("gray"), "pidgeon"); request_headers.addCopy(Envoy::Http::LowerCaseString("red"), "fox"); + request_headers.addCopy(Nighthawk::Server::TestServer::HeaderNames::get().TestServerConfig, + "{echo_request_headers: true}"); }); ASSERT_TRUE(response->complete()); EXPECT_EQ("200", response->headers().Status()->value().getStringView()); EXPECT_THAT(response->body(), HasSubstr(R"(':authority', 'foo.com')")); - EXPECT_THAT(response->body(), HasSubstr(R"(':path', '/echoheaders')")); + EXPECT_THAT(response->body(), HasSubstr(R"(':path', '/somepath')")); EXPECT_THAT(response->body(), HasSubstr(R"(':method', 'GET')")); EXPECT_THAT(response->body(), HasSubstr(R"('gray', 'pidgeon')")); EXPECT_THAT(response->body(), HasSubstr(R"('red', 'fox')")); From fcf2e0b56aada7aa6ed90b79541ed26f406ed6c4 Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Tue, 2 Jun 2020 10:52:11 -0400 Subject: [PATCH 04/10] Update README.md Signed-off-by: Misha Efimov --- source/server/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/server/README.md b/source/server/README.md index f9f91ab61..ff02e17c7 100644 --- a/source/server/README.md +++ b/source/server/README.md @@ -68,13 +68,15 @@ admin: ## Response Options config -The ResponseOptions proto can be used in the test server config or passed in `x-nighthawk-test-server-config`request header. +The ResponseOptions proto can be used in the test-server filter config or passed in `x-nighthawk-test-server-config` +request header. + The following parameters are available: * `response_body_size` - number of 'a' characters repeated in the response body. * `response_headers` - list of headers to add to response. If `append` is set to `true`, then the header is appended. -* `echo_request_headers` - return the dump of request headers in the response +* `echo_request_headers` - if set to `true`, then append the dump of request headers to the response body. ## Running the test server From 9eb59d46920a601dd7408cbcad559668f863c9ae Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Wed, 3 Jun 2020 10:01:14 -0400 Subject: [PATCH 05/10] Add an example to README.md Signed-off-by: Misha Efimov --- source/server/README.md | 44 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/source/server/README.md b/source/server/README.md index ff02e17c7..f870b9a5a 100644 --- a/source/server/README.md +++ b/source/server/README.md @@ -68,7 +68,7 @@ admin: ## Response Options config -The ResponseOptions proto can be used in the test-server filter config or passed in `x-nighthawk-test-server-config` +The ResponseOptions proto can be used in the test-server filter config or passed in `x-nighthawk-test-server-config`` request header. The following parameters are available: @@ -79,6 +79,48 @@ The following parameters are available: * `echo_request_headers` - if set to `true`, then append the dump of request headers to the response body. +The response options could be used to test and debug proxy or server configuration, for +example, to verify request headers that are added by intermediate proxy: + +``` +$ curl -6 -v [::1]:8080/nighthawk +``` +* Trying ::1:8080... +* TCP_NODELAY set +* Connected to ::1 (::1) port 8080 (#0) +> GET /nighthawk +> Host: [::1]:8080 +> User-Agent: curl/7.68.0 +> Accept: */* +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< content-length: 254 +< content-type: text/plain +< foo: bar +< foo: bar2 +< x-nh: 1 +< date: Wed, 03 Jun 2020 13:34:41 GMT +< server: envoy +< x-service: nighthawk_cluster +< via: 1.1 envoy +< +aaaaaaaaaa +Request Headers: +':authority', '[::1]:8080' +':path', '/nighthawk' +':method', 'GET' +':scheme', 'https' +'user-agent', 'curl/7.68.0' +'accept', '*/*' +'x-forwarded-proto', 'http' +'via', '1.1 google' +'x-forwarded-for', '::1,::1' +* Connection #0 to host ::1 left intact +``` +This example shows that intermediate proxy has added `x-forwarded-proto` and +`x-forwarded-for` request headers. + ## Running the test server From 77e8ff81ac11f4af93c66d37640b9078caa37ee6 Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Wed, 3 Jun 2020 10:03:25 -0400 Subject: [PATCH 06/10] Fix markdown. Signed-off-by: Misha Efimov --- source/server/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/server/README.md b/source/server/README.md index f870b9a5a..28e847771 100644 --- a/source/server/README.md +++ b/source/server/README.md @@ -84,7 +84,7 @@ example, to verify request headers that are added by intermediate proxy: ``` $ curl -6 -v [::1]:8080/nighthawk -``` + * Trying ::1:8080... * TCP_NODELAY set * Connected to ::1 (::1) port 8080 (#0) @@ -118,6 +118,7 @@ Request Headers: 'x-forwarded-for', '::1,::1' * Connection #0 to host ::1 left intact ``` + This example shows that intermediate proxy has added `x-forwarded-proto` and `x-forwarded-for` request headers. From 7d58a67364d868d06953f7a76077302568a0e297 Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Thu, 4 Jun 2020 10:24:26 -0400 Subject: [PATCH 07/10] Add unique_header to verify that echoed headers vary per request. Signed-off-by: Misha Efimov --- ...ttp_test_server_filter_integration_test.cc | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/test/server/http_test_server_filter_integration_test.cc b/test/server/http_test_server_filter_integration_test.cc index 0d5e6bf6b..d627db78c 100644 --- a/test/server/http_test_server_filter_integration_test.cc +++ b/test/server/http_test_server_filter_integration_test.cc @@ -190,21 +190,26 @@ TEST_P(HttpTestServerIntegrationTest, TestHeaderConfig) { } TEST_P(HttpTestServerIntegrationTest, TestEchoHeaders) { - Envoy::BufferingStreamDecoderPtr response = makeSingleRequest( - lookupPort("http"), "GET", "/somepath", "", downstream_protocol_, version_, "foo.com", "", - [](Envoy::Http::RequestHeaderMapImpl& request_headers) { - request_headers.addCopy(Envoy::Http::LowerCaseString("gray"), "pidgeon"); - request_headers.addCopy(Envoy::Http::LowerCaseString("red"), "fox"); - request_headers.addCopy(Nighthawk::Server::TestServer::HeaderNames::get().TestServerConfig, - "{echo_request_headers: true}"); - }); - ASSERT_TRUE(response->complete()); - EXPECT_EQ("200", response->headers().Status()->value().getStringView()); - EXPECT_THAT(response->body(), HasSubstr(R"(':authority', 'foo.com')")); - EXPECT_THAT(response->body(), HasSubstr(R"(':path', '/somepath')")); - EXPECT_THAT(response->body(), HasSubstr(R"(':method', 'GET')")); - EXPECT_THAT(response->body(), HasSubstr(R"('gray', 'pidgeon')")); - EXPECT_THAT(response->body(), HasSubstr(R"('red', 'fox')")); + for (auto unique_header : {"one", "two", "three"}) { + Envoy::BufferingStreamDecoderPtr response = makeSingleRequest( + lookupPort("http"), "GET", "/somepath", "", downstream_protocol_, version_, "foo.com", "", + [unique_header](Envoy::Http::RequestHeaderMapImpl& request_headers) { + request_headers.addCopy(Envoy::Http::LowerCaseString("gray"), "pidgeon"); + request_headers.addCopy(Envoy::Http::LowerCaseString("red"), "fox"); + request_headers.addCopy(Envoy::Http::LowerCaseString("unique_header"), unique_header); + request_headers.addCopy( + Nighthawk::Server::TestServer::HeaderNames::get().TestServerConfig, + "{echo_request_headers: true}"); + }); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().Status()->value().getStringView()); + EXPECT_THAT(response->body(), HasSubstr(R"(':authority', 'foo.com')")); + EXPECT_THAT(response->body(), HasSubstr(R"(':path', '/somepath')")); + EXPECT_THAT(response->body(), HasSubstr(R"(':method', 'GET')")); + EXPECT_THAT(response->body(), HasSubstr(R"('gray', 'pidgeon')")); + EXPECT_THAT(response->body(), HasSubstr(R"('red', 'fox')")); + EXPECT_THAT(response->body(), HasSubstr(unique_header)); + } } class HttpTestServerIntegrationNoConfigTest : public HttpTestServerIntegrationTestBase { From bda8c6e90842f83cc67f5ca9786fbc12f1f1dcf4 Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Thu, 4 Jun 2020 16:54:58 -0400 Subject: [PATCH 08/10] Kick CI Signed-off-by: Misha Efimov From 6fad7ec0dc3510c80a11d2c9b7cc6b75984c5376 Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Fri, 5 Jun 2020 15:15:07 -0400 Subject: [PATCH 09/10] Kick CI Signed-off-by: Misha Efimov From 40d530b856aa8f70e6669bc5828c355a50a3a68c Mon Sep 17 00:00:00 2001 From: Misha Efimov Date: Mon, 8 Jun 2020 09:31:38 -0400 Subject: [PATCH 10/10] Kick CI Signed-off-by: Misha Efimov