Skip to content
Closed
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
7 changes: 6 additions & 1 deletion api/server/response_options.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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}];
}
// If true, then echo request headers in the response body.
bool echo_request_headers = 3;
}
56 changes: 56 additions & 0 deletions source/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,62 @@ admin:
port_value: 8081
```

## Response Options 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:

* `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` - 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


Expand Down
11 changes: 10 additions & 1 deletion source/server/http_test_server_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +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<Envoy::Http::Code>(200), std::string(base_config_.response_body_size(), 'a'),
static_cast<Envoy::Http::Code>(200), response_body,
[this](Envoy::Http::ResponseHeaderMap& direct_response_headers) {
applyConfigToResponseHeaders(direct_response_headers, base_config_);
},
Expand All @@ -78,6 +82,11 @@ HttpTestServerDecoderFilter::decodeHeaders(Envoy::Http::RequestHeaderMap& header
if (request_config_header) {
mergeJsonConfig(request_config_header->value().getStringView(), base_config_, error_message_);
}
if (base_config_.echo_request_headers()) {
std::stringstream headers_dump;
headers_dump << "\nRequest Headers:\n" << headers;
request_headers_dump_ = headers_dump.str();
}
if (end_stream) {
sendReply();
}
Expand Down
1 change: 1 addition & 0 deletions source/server/http_test_server_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class HttpTestServerDecoderFilter : public Envoy::Http::StreamDecoderFilter {
Envoy::Http::StreamDecoderFilterCallbacks* decoder_callbacks_;
nighthawk::server::ResponseOptions base_config_;
absl::optional<std::string> error_message_;
absl::optional<std::string> request_headers_dump_;
};

} // namespace Server
Expand Down
23 changes: 23 additions & 0 deletions test/server/http_test_server_filter_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,29 @@ TEST_P(HttpTestServerIntegrationTest, TestHeaderConfig) {
EXPECT_EQ(std::string(10, 'a'), response->body());
}

TEST_P(HttpTestServerIntegrationTest, TestEchoHeaders) {
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 {
public:
void SetUp() override { initialize(); }
Expand Down