Skip to content
Closed
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5b8758f
add test case
Ronnie-personal Sep 29, 2023
16e39bd
fix format issue
Ronnie-personal Sep 29, 2023
cce7b89
honor HeaderValueOption.append_action, if append_action does not exis…
Ronnie-personal Oct 3, 2023
37185a7
add test cases
Ronnie-personal Oct 4, 2023
44b5297
fix tests
Ronnie-personal Oct 5, 2023
cf742c2
fix test
Ronnie-personal Oct 6, 2023
98c852f
fix tests
Ronnie-personal Oct 7, 2023
00acddf
add checker to append_action
Ronnie-personal Oct 7, 2023
cc4a88f
add checker to append_action
Ronnie-personal Oct 7, 2023
c6372f3
test case append or add
Ronnie-personal Oct 7, 2023
ba085b3
test case append or add
Ronnie-personal Oct 7, 2023
32d6589
test case append or add
Ronnie-personal Oct 7, 2023
f70f495
update test
Ronnie-personal Oct 7, 2023
872aaa3
fix format
Ronnie-personal Oct 7, 2023
fbd6de8
debug test
Ronnie-personal Oct 8, 2023
214929f
Merge branch 'main' into extprocsetcookie29861
Ronnie-personal Oct 8, 2023
ad337c2
add false runtime guard
Ronnie-personal Oct 8, 2023
f8ce857
fix format
Ronnie-personal Oct 8, 2023
a54590f
update test cases
Ronnie-personal Oct 8, 2023
b8bc861
add tests to filter_test
Ronnie-personal Oct 9, 2023
98cd440
debug test cases
Ronnie-personal Oct 9, 2023
77ac18f
cleanup tests
Ronnie-personal Oct 10, 2023
c19cc3d
cleanup tests
Ronnie-personal Oct 10, 2023
aa1cb9b
fix test
Ronnie-personal Oct 10, 2023
aca53c2
add test for more code coverage
Ronnie-personal Oct 11, 2023
962dd86
fix format
Ronnie-personal Oct 11, 2023
43b3982
fix test
Ronnie-personal Oct 11, 2023
23e2fb8
fix format
Ronnie-personal Oct 12, 2023
291817c
check append_action has a value
Ronnie-personal Oct 12, 2023
061f3aa
Merge branch 'extprocsetcookie29861' of https://github.com/Ronnie-per…
Ronnie-personal Oct 12, 2023
ad19ed9
address review comment
Ronnie-personal Oct 13, 2023
fec6ae3
fix yaml format
Ronnie-personal Oct 13, 2023
65d38d4
fix yaml format
Ronnie-personal Oct 13, 2023
e994879
fix yaml format
Ronnie-personal Oct 13, 2023
a30229b
Merge branch 'main' into extprocsetcookie29861
Ronnie-personal Oct 18, 2023
121a42b
fix format
Ronnie-personal Oct 18, 2023
7ae3f78
modify code to check if append has a value
Ronnie-personal Oct 21, 2023
3cc6e3f
fix format
Ronnie-personal Oct 21, 2023
f7c4aac
fix syntax error
Ronnie-personal Oct 21, 2023
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
8 changes: 7 additions & 1 deletion api/envoy/config/core/v3/base.proto
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,13 @@ message HeaderValueOption {
// Describes the supported actions types for header append action.
enum HeaderAppendAction {
// This action will append the specified value to the existing values if the header
// already exists. If the header doesn't exist then this will add the header with
// already exists and the header is inline header.

@tyxia tyxia Oct 17, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's revert this change in API proto. I didn't mean to change the API here (Sorry if my previous comment in the other thread misleads you)

Basically we don't need to impose additional restriction on it and it can just follow the general rule in header map.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No problem at all, I will revert it back.

// set-cookie is special predefined inline header that allows to be multi headers present
// rather than concatenation.
// Reject append request on single-value header like host, content-length.
// You can find more detailed information at RFC 7230 section 3.2.2.
//
// If the header doesn't exist then this will add the header with
// specified key and value.
APPEND_IF_EXISTS_OR_ADD = 0;

Expand Down
5 changes: 5 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,8 @@ deprecated:
change: |
deprecated runtime key ``overload.global_downstream_max_connections`` in favor of :ref:`downstream connections monitor
<envoy_v3_api_msg_extensions.resource_monitors.downstream_connections.v3.DownstreamConnectionsConfig>`.
- area: http ext_proc filter
change: |
Change to hornor append_action <envoy_v3_api_field_config.core.v3.HeaderValueOption.append_action>.
This change can be reverted temporarily by setting the runtime guard
``envoy.reloadable_features.header_value_option_change_action`` to false.
2 changes: 2 additions & 0 deletions source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_enable_universal_header_validator)
FALSE_RUNTIME_GUARD(envoy_reloadable_features_no_downgrade_to_canonical_name);
// TODO(pksohn): enable after fixing https://github.com/envoyproxy/envoy/issues/29930
FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_defer_logging_to_ack_listener);
// TODO(ronnie-personal): enable by default after a complete deprecation period.
FALSE_RUNTIME_GUARD(envoy_reloadable_features_header_value_option_change_action)

// Block of non-boolean flags. Use of int flags is deprecated. Do not add more.
ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT
Expand Down
151 changes: 125 additions & 26 deletions source/extensions/filters/http/ext_proc/mutation_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ using Stats::Counter;
using envoy::service::ext_proc::v3::BodyMutation;
using envoy::service::ext_proc::v3::HeaderMutation;

using HeaderAppendAction = envoy::config::core::v3::HeaderValueOption::HeaderAppendAction;
using HeaderValueOption = envoy::config::core::v3::HeaderValueOption;

bool MutationUtils::headerInMatcher(
absl::string_view key, const std::vector<Matchers::StringMatcherPtr>& header_matchers) {
return std::any_of(header_matchers.begin(), header_matchers.end(),
Expand Down Expand Up @@ -146,6 +149,11 @@ absl::Status MutationUtils::applyHeaderMutations(const HeaderMutation& mutation,
}
}

bool append_mode_for_append_action;
Filters::Common::MutationRules::CheckOperation check_op_for_append_action;
Filters::Common::MutationRules::CheckResult checkResult_for_append_action;
auto is_duplicate = false;

for (const auto& sh : mutation.set_headers()) {
if (!sh.has_header()) {
continue;
Expand Down Expand Up @@ -174,40 +182,131 @@ absl::Status MutationUtils::applyHeaderMutations(const HeaderMutation& mutation,
return absl::InvalidArgumentError("Invalid character in set_headers mutation.");
}
const LowerCaseString header_name(sh.header().key());
const bool append = PROTOBUF_GET_WRAPPED_OR_DEFAULT(sh, append, false);
const auto check_op = (append && !headers.get(header_name).empty()) ? CheckOperation::APPEND
: CheckOperation::SET;
auto check_result = checker.check(check_op, header_name, header_value);
if (replacing_message && header_name == Http::Headers::get().Method) {
// Special handling to allow changing ":method" when the
// CONTINUE_AND_REPLACE option is selected, to stay compatible.
check_result = CheckResult::OK;
}
switch (check_result) {
case CheckResult::OK:
ENVOY_LOG(trace, "Setting header {} append = {}", sh.header().key(), append);
if (append) {
headers.addCopy(header_name, header_value);
} else {
headers.setCopy(header_name, header_value);

if (sh.append_action().has_value()) {
switch (sh.append_action()) {
case HeaderValueOption::APPEND_IF_EXISTS_OR_ADD:
// Check if the header already exists with the same name and value.
is_duplicate = false;

@yanjunxiang-google yanjunxiang-google Oct 12, 2023

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.

I think the desired behavior is that If ext_proc server sends duplicate headers with identical key/value, ext_proc filter probably should just accept them all in the case: APPEND_IF_EXISTS_OR_ADD. Is there any harm for Envoy to have duplicate headers when forwarding the message to upstream/downstream?
@htuch @tyxia @stevenzzzz

@tyxia tyxia Oct 12, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I feel it should be the other way around, as stated in RFC 7230 section 3.2.2:

A sender MUST NOT generate multiple header fields with the same field name in a message unless either the entire field value for that header field is defined as a comma-separated list [i.e., #(values)] or the header field is a well-known exception (as noted below).

We probably should be compliant with RFC with exceptions that are allowed(e.g., set-cookie is well-known exception)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you all for your great comments! I can update the code accordingly. Can we have the requirement or specification documented somewhere so that I exactly follow it?

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.

Adding @yanavlasov

What does UHV do if client sends multiple identical headers with same value, like
"key: foo, value: bar"
"key: foo, value: bar"
"key: foo, value: bar"

@tyxia tyxia Oct 13, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think ext_proc should follow the Envoy's general principle and existing implementation that is compliant with RFC I mentioned above:
a. For predefined inline headers: concatenate with comma
b. Other headers: multiple headers allowed (I think this can be further discussed (especially from sidestream perspective) as I have seen the implementation only allows (a) and don't allow (b))

Please note:

  1. There are some headers are single-value header like host, content-length. They are just simply can not multi-value and will be rejected as bad request
  2. set-cookie is special predefined inline header that allows to be multi headers present rather than concatenation , per RFC and corresponding envoy implementation

@tyxia tyxia Oct 17, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @htuch !

@Ronnie-personal. As I mentioned above. we should just use addCopy to handle APPEND_IF_EXISTS_OR_ADD. ext_proc doesn't need to detect duplicate header manually as addCopy internally already handles all cases: duplicate inline and non-inline headers as well as set-cookie. This actually leads to a question for you: I am wondering what is the issue this PR aims to address? Using HeaderValueOption.append with existing code should resolve your set-cookie issue as it is using add_copy already. Or you are improving the code by handling different actions inside of HeaderAppendAction

@yanjunxiang-google Regarding your previous question about rejecting malformed mutation. I feel ext_proc doesn't need to reject malformed headers as long as low-level codec reject them. And it will be ext_proc server's responsibility of using right API. For example, single-value header like content-length should not be appended.
With that said, we should (1)make sure the codec rejection behavior and (2) have test like this example to check the bad request.

@Ronnie-personal Ronnie-personal Oct 17, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks @htuch !

@Ronnie-personal. As I mentioned above. we should just use addCopy to handle APPEND_IF_EXISTS_OR_ADD. ext_proc doesn't need to detect duplicate header manually as addCopy internally already handles all cases: duplicate inline and non-inline headers as well as set-cookie. This actually leads to a question for you: I am wondering what is the issue this PR aims to address? Using HeaderValueOption.append with existing code should resolve your set-cookie issue as it is using add_copy already. Or you are improving the code by handling different actions inside of HeaderAppendAction

If I understand correctly, 'append' should be deprecated and we need to honor 'append_action'.

@tyxia tyxia Oct 17, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In theory, BoolValue append will be deprecated in v4 version (current is v3), In reality, v4 is not going to happen. In other words, append will never be deprecated. Here is related discussion.

Therefore, I think

  1. ext_proc filter should support append forever ( as long as V4 is not coming) and runtime flag in this PR is not needed as there will be no deprecation.

  2. It is OK for ext_proc to add support for HeaderAppendAction , especially since it supports more fine-grained OP OVERWRITE etc behavior that is not available in BoolValue append.

  3. APPEND_IF_EXISTS_OR_ADD will not replace BoolValue append,again because there is no deprecation. Basically HeaderValueOption::APPEND_IF_EXISTS_OR_ADD == BoolValue append They should be handled in same way using HeaderMapImpl::addCopy

  4. For other options in HeaderAppendAction (like OVERWRITE_IF_EXISTS_OR_ADD etc) you can handle them based on proto definition.

WDYT?

@Ronnie-personal Ronnie-personal Oct 18, 2023

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the detailed instructions. It makes perfect sense.

I have one more question if you don't mind.
It appears that both 'append' and 'append_action' are present in 'HeaderAppendAction' and have values. How can we know which one the end user or app intends to use?

FYI, I have also tried 'if (sh.append_action().has_value())', the code errored out.

@tyxia tyxia Oct 18, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

  1. They are not both set. Just APPEND_IF_EXISTS_OR_ADD is default value 0 as it doesn't have a UNSPECIFIED enum field.
  2. You should remove if (sh.append_action().has_value()). It is enum that can not be used with has_value()
  3. When HeaderAppendAction is introduced to ext_proc, it should be strongly recommended that only one of append and HeaderAppendAction should be set.
    Unfortunately we can not enforce this from proto level via oneof (breaking API change), so easiest handling is probably like something below: new append_action only will be effective if old append is NOT set.
bool append = false;
if (sh.has_append()) {
  append = sh.append().value();
} else {
  switch (sh.append_action()) {
  case APPEND_IF_EXISTS_OR_ADD:
  ...
    break;
  ...
  ...
  }
}

if (!headers.get(header_name).empty()) {
Http::HeaderMap::GetResult result = headers.get(header_name);
absl::string_view existing_value;
for (size_t i = 0; i < result.size(); ++i) {
existing_value = result[i]->value().getStringView();

// Compare the existing value with your desired header_value
if (existing_value == header_value) {
is_duplicate = true;
break;
}
}
}
if (!is_duplicate) {
append_mode_for_append_action = true;
check_op_for_append_action =
(!headers.get(header_name).empty()) ? CheckOperation::APPEND : CheckOperation::SET;
checkResult_for_append_action = handleCheckResult(
headers, replacing_message, checker, rejected_mutations, check_op_for_append_action,
header_name, header_value, append_mode_for_append_action);
if (checkResult_for_append_action == CheckResult::FAIL) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid attempt to modify ", static_cast<absl::string_view>(header_name)));
}
}
break;
case HeaderValueOption::ADD_IF_ABSENT:
if (headers.get(header_name).empty()) {
append_mode_for_append_action = true;
check_op_for_append_action = CheckOperation::SET;
checkResult_for_append_action = handleCheckResult(
headers, replacing_message, checker, rejected_mutations, check_op_for_append_action,
header_name, header_value, append_mode_for_append_action);
if (checkResult_for_append_action == CheckResult::FAIL) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid attempt to modify ", static_cast<absl::string_view>(header_name)));
}
}
break;
case HeaderValueOption::OVERWRITE_IF_EXISTS_OR_ADD:
append_mode_for_append_action = false;
check_op_for_append_action = CheckOperation::SET;
checkResult_for_append_action = handleCheckResult(
headers, replacing_message, checker, rejected_mutations, check_op_for_append_action,
header_name, header_value, append_mode_for_append_action);
if (checkResult_for_append_action == CheckResult::FAIL) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid attempt to modify ", static_cast<absl::string_view>(header_name)));
}
break;
case HeaderValueOption::OVERWRITE_IF_EXISTS:
if (!headers.get(header_name).empty()) {
append_mode_for_append_action = false;
check_op_for_append_action = CheckOperation::SET;
checkResult_for_append_action = handleCheckResult(
headers, replacing_message, checker, rejected_mutations, check_op_for_append_action,
header_name, header_value, append_mode_for_append_action);
if (checkResult_for_append_action == CheckResult::FAIL) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid attempt to modify ", static_cast<absl::string_view>(header_name)));
}
}
break;
default:
// Handle unknown/invalid append_action value
return absl::InvalidArgumentError(absl::StrCat(
"Invalid append_action value ", static_cast<absl::string_view>(header_name)));
}
} else {
const bool append_mode = PROTOBUF_GET_WRAPPED_OR_DEFAULT(sh, append, false);
const auto check_op = (append_mode && !headers.get(header_name).empty())
? CheckOperation::APPEND
: CheckOperation::SET;
auto checkResult = handleCheckResult(headers, replacing_message, checker, rejected_mutations,
check_op, header_name, header_value, append_mode);
if (checkResult == CheckResult::FAIL) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid attempt to modify ", static_cast<absl::string_view>(header_name)));
}
break;
case CheckResult::IGNORE:
ENVOY_LOG(debug, "Header {} may not be modified per rules", header_name);
rejected_mutations.inc();
break;
case CheckResult::FAIL:
ENVOY_LOG(debug, "Header {} may not be modified. Returning error", header_name);
rejected_mutations.inc();
return absl::InvalidArgumentError(
absl::StrCat("Invalid attempt to modify ", static_cast<absl::string_view>(header_name)));
}
}

// After header mutation, check the ending headers are not exceeding the HCM limit.
return headerMutationResultCheck(headers, rejected_mutations);
}

// Define a common function to handle the check result logic
Filters::Common::MutationRules::CheckResult MutationUtils::handleCheckResult(
Http::HeaderMap& headers, bool replacing_message,
const Filters::Common::MutationRules::Checker& checker, Stats::Counter& rejected_mutations,
Filters::Common::MutationRules::CheckOperation check_op, Http::LowerCaseString header_name,
absl::string_view header_value, bool append_mode) {
auto check_result = checker.check(check_op, header_name, header_value);
if (replacing_message && header_name == Http::Headers::get().Method) {
// Special handling to allow changing ":method" when the
// CONTINUE_AND_REPLACE option is selected, to stay compatible.
check_result = CheckResult::OK;
}
// Print the value as an integer
switch (check_result) {
case CheckResult::OK:
if (append_mode) {
headers.addCopy(header_name, header_value);
} else {
headers.setCopy(header_name, header_value);
}
break;
case CheckResult::IGNORE:
rejected_mutations.inc();
break;
case CheckResult::FAIL:
rejected_mutations.inc();
// You can add additional handling for the FAIL case here if needed.
break;
}

return check_result;
}

void MutationUtils::applyBodyMutations(const BodyMutation& mutation, Buffer::Instance& buffer) {
switch (mutation.mutation_case()) {
case BodyMutation::MutationCase::kClearBody:
Expand Down
6 changes: 6 additions & 0 deletions source/extensions/filters/http/ext_proc/mutation_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class MutationUtils : public Logger::Loggable<Logger::Id::ext_proc> {
// Check whether the header size after mutation is over the HCM size config.
static absl::Status headerMutationResultCheck(const Http::HeaderMap& headers,
Stats::Counter& rejected_mutations);

static Filters::Common::MutationRules::CheckResult handleCheckResult(
Http::HeaderMap& headers, bool replacing_message,
const Filters::Common::MutationRules::Checker& checker, Stats::Counter& rejected_mutations,
Filters::Common::MutationRules::CheckOperation check_op, Http::LowerCaseString header_name,
absl::string_view header_value, bool append_mode);
};

} // namespace ExternalProcessing
Expand Down
97 changes: 97 additions & 0 deletions test/extensions/filters/http/ext_proc/filter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3293,6 +3293,103 @@ TEST_F(HttpFilter2Test, LastEncodeDataCallExceedsStreamBufferLimitWouldJustRaise
conn_manager_->onData(fake_input, false);
}

TEST_F(HttpFilterTest, AppendActionTest) {
TestScopedRuntime scoped_runtime;
scoped_runtime.mergeValues({
{"envoy.reloadable_features.send_header_raw_value", "false"},
{"envoy.reloadable_features.header_value_option_change_action", "true"},
});
initialize(R"EOF(
grpc_service:
envoy_grpc:
cluster_name: "ext_proc_server"
)EOF");

// Initialize request headers.
request_headers_.addCopy(LowerCaseString("x-original-header"), "original_value");

EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, false));

// Apply the "append_action" to add a value to an existing header.
processRequestHeaders(
false, [](const HttpHeaders&, ProcessingResponse&, HeadersResponse& header_resp) {
auto headers_mut = header_resp.mutable_response()->mutable_header_mutation();
auto s = headers_mut->add_set_headers();
s->mutable_header()->set_key("x-original-header");
s->mutable_header()->set_value("appended_value");
s->set_append_action(::envoy::config::core::v3::HeaderValueOption_HeaderAppendAction::
HeaderValueOption_HeaderAppendAction_APPEND_IF_EXISTS_OR_ADD);
s = headers_mut->add_set_headers();
s->mutable_header()->set_key("another-request");
s->mutable_header()->set_value("request");
s->set_append_action(::envoy::config::core::v3::HeaderValueOption_HeaderAppendAction::
HeaderValueOption_HeaderAppendAction_OVERWRITE_IF_EXISTS_OR_ADD);
});

// Check that the header was appended correctly.
TestRequestHeaderMapImpl expected{{":path", "/"},
{":method", "POST"},
{":scheme", "http"},
{"host", "host"},
{"x-original-header", "original_value"},
{"x-original-header", "appended_value"},
{"another-request", "request"}};
EXPECT_THAT(&request_headers_, HeaderMapEqualIgnoreOrder(&expected));

// Continue processing data and trailers.
Buffer::OwnedImpl req_data("foo");
EXPECT_EQ(FilterDataStatus::Continue, filter_->decodeData(req_data, true));
EXPECT_EQ(FilterTrailersStatus::Continue, filter_->decodeTrailers(request_trailers_));

// Initialize response headers.
response_headers_.addCopy(LowerCaseString(":status"), "200");
response_headers_.addCopy(LowerCaseString("content-type"), "text/plain");
response_headers_.addCopy(LowerCaseString("content-length"), "3");

EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->encodeHeaders(response_headers_, false));

// Apply the "append_action" to response headers.
processResponseHeaders(false, [](const HttpHeaders& response_headers, ProcessingResponse&,
HeadersResponse& header_resp) {
EXPECT_FALSE(response_headers.end_of_stream());
TestRequestHeaderMapImpl expected_response{
{":status", "200"}, {"content-type", "text/plain"}, {"content-length", "3"}};
EXPECT_THAT(response_headers.headers(), HeaderProtosEqual(expected_response));

auto* resp_headers_mut = header_resp.mutable_response()->mutable_header_mutation();
auto* s = resp_headers_mut->add_set_headers();
s->mutable_header()->set_key("x-original-response-header");
s->mutable_header()->set_value("appended_response_value");
s->set_append_action(envoy::config::core::v3::HeaderValueOption_HeaderAppendAction::
HeaderValueOption_HeaderAppendAction_ADD_IF_ABSENT);
s = resp_headers_mut->add_set_headers();
s->mutable_header()->set_key("x-original-response-header");
s->mutable_header()->set_value("second value");
s->set_append_action(envoy::config::core::v3::HeaderValueOption_HeaderAppendAction::
HeaderValueOption_HeaderAppendAction_OVERWRITE_IF_EXISTS);
});

// Check that the response header was appended correctly.
TestRequestHeaderMapImpl final_expected_response{{":status", "200"},
{"content-type", "text/plain"},
{"content-length", "3"},
{"x-original-response-header", "second value"}};
EXPECT_THAT(&response_headers_, HeaderMapEqualIgnoreOrder(&final_expected_response));

// Continue processing data, encode trailers, and destroy the filter.
Buffer::OwnedImpl resp_data("bar");
EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(resp_data, false));
Buffer::OwnedImpl empty_data;
EXPECT_EQ(FilterDataStatus::Continue, filter_->encodeData(empty_data, true));
EXPECT_EQ(FilterTrailersStatus::Continue, filter_->encodeTrailers(response_trailers_));
filter_->onDestroy();

EXPECT_EQ(1, config_->stats().streams_started_.value());
EXPECT_EQ(2, config_->stats().stream_msgs_sent_.value());
EXPECT_EQ(2, config_->stats().stream_msgs_received_.value());
EXPECT_EQ(1, config_->stats().streams_closed_.value());
}

} // namespace
} // namespace ExternalProcessing
} // namespace HttpFilters
Expand Down
Loading