From 62ba44f3aea5c90181d879cf4374bfc401a2969f Mon Sep 17 00:00:00 2001 From: Jacob Bohanon Date: Tue, 6 Feb 2024 08:14:47 -0500 Subject: [PATCH 1/3] send attributes with first processing message on request & response path, not just with headers messages Signed-off-by: Jacob Bohanon --- .../ext_proc/v3/external_processor.proto | 12 +---- .../filters/http/ext_proc/ext_proc.cc | 49 ++++++++----------- .../filters/http/ext_proc/ext_proc.h | 5 +- .../filters/http/ext_proc/processor_state.h | 25 ++++++++++ 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/api/envoy/service/ext_proc/v3/external_processor.proto b/api/envoy/service/ext_proc/v3/external_processor.proto index 13ef49a54326a..15d0688588659 100644 --- a/api/envoy/service/ext_proc/v3/external_processor.proto +++ b/api/envoy/service/ext_proc/v3/external_processor.proto @@ -113,7 +113,6 @@ message ProcessingRequest { // Dynamic metadata associated with the request. config.core.v3.Metadata metadata_context = 8; - // [#not-implemented-hide:] // The values of properties selected by the ``request_attributes`` // or ``response_attributes`` list in the configuration. Each entry // in the list is populated from the standard @@ -200,6 +199,8 @@ message ProcessingResponse { // This message is sent to the external server when the HTTP request and responses // are first received. message HttpHeaders { + reserved 2; + // The HTTP request headers. All header keys will be // lower-cased, because HTTP header keys are case-insensitive. // The ``headers`` encoding is based on the runtime guard @@ -210,15 +211,6 @@ message HttpHeaders { // :ref:`value ` field. config.core.v3.HeaderMap headers = 1; - // [#not-implemented-hide:] - // TODO(jbohanon) reserve field as part of rework detailed in https://github.com/envoyproxy/envoy/issues/32125 - // The values of properties selected by the ``request_attributes`` - // or ``response_attributes`` list in the configuration. Each entry - // in the list is populated - // from the standard :ref:`attributes ` - // supported across Envoy. - map attributes = 2; - // If true, then there is no message body associated with this // request or response. bool end_of_stream = 3; diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 4b41dbdc57a24..f06f5c66621ca 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -276,8 +276,7 @@ void Filter::onDestroy() { } FilterHeadersStatus Filter::onHeaders(ProcessorState& state, - Http::RequestOrResponseHeaderMap& headers, bool end_stream, - ProtobufWkt::Struct* proto) { + Http::RequestOrResponseHeaderMap& headers, bool end_stream) { switch (openStream()) { case StreamOpenState::Error: return FilterHeadersStatus::StopIteration; @@ -291,14 +290,12 @@ FilterHeadersStatus Filter::onHeaders(ProcessorState& state, state.setHeaders(&headers); state.setHasNoBody(end_stream); ProcessingRequest req; + addAttributes(state, req); addDynamicMetadata(state, req); auto* headers_req = state.mutableHeaders(req); MutationUtils::headersToProto(headers, config_->allowedHeaders(), config_->disallowedHeaders(), *headers_req->mutable_headers()); headers_req->set_end_of_stream(end_stream); - if (proto != nullptr) { - (*headers_req->mutable_attributes())[FilterName] = *proto; - } state.onStartProcessorCall(std::bind(&Filter::onMessageTimeout, this), config_->messageTimeout(), ProcessorState::CallbackState::HeadersCallback); ENVOY_LOG(debug, "Sending headers message"); @@ -317,17 +314,7 @@ FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_st FilterHeadersStatus status = FilterHeadersStatus::Continue; if (decoding_state_.sendHeaders()) { - ProtobufWkt::Struct proto; - - if (config_->expressionManager().hasRequestExpr()) { - auto activation_ptr = Filters::Common::Expr::createActivation( - &config_->expressionManager().localInfo(), decoding_state_.callbacks()->streamInfo(), - &headers, nullptr, nullptr); - proto = config_->expressionManager().evaluateRequestAttributes(*activation_ptr); - } - - status = onHeaders(decoding_state_, headers, end_stream, - config_->expressionManager().hasRequestExpr() ? &proto : nullptr); + status = onHeaders(decoding_state_, headers, end_stream); ENVOY_LOG(trace, "onHeaders returning {}", static_cast(status)); } else { ENVOY_LOG(trace, "decodeHeaders: Skipped header processing"); @@ -590,7 +577,7 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap& FilterTrailersStatus Filter::decodeTrailers(RequestTrailerMap& trailers) { ENVOY_LOG(trace, "decodeTrailers"); const auto status = onTrailers(decoding_state_, trailers); - ENVOY_LOG(trace, "encodeTrailers returning {}", static_cast(status)); + ENVOY_LOG(trace, "decodeTrailers returning {}", static_cast(status)); return status; } @@ -605,17 +592,7 @@ FilterHeadersStatus Filter::encodeHeaders(ResponseHeaderMap& headers, bool end_s FilterHeadersStatus status = FilterHeadersStatus::Continue; if (!processing_complete_ && encoding_state_.sendHeaders()) { - ProtobufWkt::Struct proto; - - if (config_->expressionManager().hasResponseExpr()) { - auto activation_ptr = Filters::Common::Expr::createActivation( - &config_->expressionManager().localInfo(), encoding_state_.callbacks()->streamInfo(), - nullptr, &headers, nullptr); - proto = config_->expressionManager().evaluateResponseAttributes(*activation_ptr); - } - - status = onHeaders(encoding_state_, headers, end_stream, - config_->expressionManager().hasResponseExpr() ? &proto : nullptr); + status = onHeaders(encoding_state_, headers, end_stream); ENVOY_LOG(trace, "onHeaders returns {}", static_cast(status)); } else { ENVOY_LOG(trace, "encodeHeaders: Skipped header processing"); @@ -650,6 +627,7 @@ ProcessingRequest Filter::setupBodyChunk(ProcessorState& state, const Buffer::In bool end_stream) { ENVOY_LOG(debug, "Sending a body chunk of {} bytes, end_stream {}", data.length(), end_stream); ProcessingRequest req; + addAttributes(state, req); addDynamicMetadata(state, req); auto* body_req = state.mutableBody(req); body_req->set_end_of_stream(end_stream); @@ -667,6 +645,7 @@ void Filter::sendBodyChunk(ProcessorState& state, ProcessorState::CallbackState void Filter::sendTrailers(ProcessorState& state, const Http::HeaderMap& trailers) { ProcessingRequest req; + addAttributes(state, req); addDynamicMetadata(state, req); auto* trailers_req = state.mutableTrailers(req); MutationUtils::headersToProto(trailers, config_->allowedHeaders(), config_->disallowedHeaders(), @@ -771,6 +750,20 @@ void Filter::addDynamicMetadata(const ProcessorState& state, ProcessingRequest& *req.mutable_metadata_context() = forwarding_metadata; } +void Filter::addAttributes(const ProcessorState& state, ProcessingRequest& req) { + if (!state.sendAttributes(config_->expressionManager())) { + return; + } + + auto activation_ptr = Filters::Common::Expr::createActivation( + &config_->expressionManager().localInfo(), state.callbacks()->streamInfo(), + state.requestHeaders(), state.responseHeaders(), state.trailers()); + attributes = config_->expressionManager().evaluateRequestAttributes(*activation_ptr); + + state.sentAttributes(true); + (*req.mutable_attributes())[FilterName] = *attributes; +} + void Filter::setDynamicMetadata(Http::StreamFilterCallbacks* cb, const ProcessorState& state, const ProcessingResponse& response) { if (state.untypedReceivingMetadataNamespaces().empty() || !response.has_dynamic_metadata()) { diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 78d97cbe9bab0..707b4842e3a70 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -373,8 +373,7 @@ class Filter : public Logger::Loggable, void sendImmediateResponse(const envoy::service::ext_proc::v3::ImmediateResponse& response); Http::FilterHeadersStatus onHeaders(ProcessorState& state, - Http::RequestOrResponseHeaderMap& headers, bool end_stream, - ProtobufWkt::Struct* proto); + Http::RequestOrResponseHeaderMap& headers, bool end_stream); // Return a pair of whether to terminate returning the current result. std::pair sendStreamChunk(ProcessorState& state); @@ -386,6 +385,8 @@ class Filter : public Logger::Loggable, void setDecoderDynamicMetadata(const envoy::service::ext_proc::v3::ProcessingResponse& response); void addDynamicMetadata(const ProcessorState& state, envoy::service::ext_proc::v3::ProcessingRequest& req); + void addAttributes(const ProcessorState& state, + envoy::service::ext_proc::v3::ProcessingRequest& req); const FilterConfigSharedPtr config_; const ExternalProcessorClientPtr client_; diff --git a/source/extensions/filters/http/ext_proc/processor_state.h b/source/extensions/filters/http/ext_proc/processor_state.h index a3ebe27617f0b..9feda7dad84d3 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.h +++ b/source/extensions/filters/http/ext_proc/processor_state.h @@ -15,6 +15,7 @@ #include "source/common/common/logger.h" #include "absl/status/status.h" +#include "matching_utils.h" namespace Envoy { namespace Extensions { @@ -136,6 +137,9 @@ class ProcessorState : public Logger::Loggable { void setHeaders(Http::RequestOrResponseHeaderMap* headers) { headers_ = headers; } void setTrailers(Http::HeaderMap* trailers) { trailers_ = trailers; } + virtual const Http::RequestOrResponseHeaderMap* requestHeaders() const PURE; + virtual const Http::RequestOrResponseHeaderMap* responseHeaders() const PURE; + const Http::HeaderMap* responseTrailers() const { return trailers_; } void onStartProcessorCall(Event::TimerCb cb, std::chrono::milliseconds timeout, CallbackState callback_state); @@ -202,6 +206,10 @@ class ProcessorState : public Logger::Loggable { virtual Http::StreamFilterCallbacks* callbacks() const PURE; + virtual bool sendAttributes(const ExpressionManager& mgr) const PURE; + + void sentAttributes(bool sent) { attributes_sent_ = sent; } + protected: void setBodyMode( envoy::extensions::filters::http::ext_proc::v3::ProcessingMode_BodySendMode body_mode); @@ -250,6 +258,9 @@ class ProcessorState : public Logger::Loggable { const std::vector* typed_forwarding_namespaces_{}; const std::vector* untyped_receiving_namespaces_{}; + // If true, the attributes for this processing state have already been sent. + bool attributes_sent_{}; + private: virtual void clearRouteCache(const envoy::service::ext_proc::v3::CommonResponse&) {} }; @@ -324,6 +335,13 @@ class DecodingProcessorState : public ProcessorState { Http::StreamFilterCallbacks* callbacks() const override { return decoder_callbacks_; } + bool sendAttributes(const ExpressionManager& mgr) const override { + return !attributes_sent_ && mgr.hasRequestExpr(); + } + + const Http::RequestOrResponseHeaderMap* requestHeaders() const override { return headers_; }; + const Http::RequestOrResponseHeaderMap* responseHeaders() const override { return nullptr; } + private: void setProcessingModeInternal( const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode); @@ -404,6 +422,13 @@ class EncodingProcessorState : public ProcessorState { Http::StreamFilterCallbacks* callbacks() const override { return encoder_callbacks_; } + bool sendAttributes(const ExpressionManager& mgr) const override { + return !attributes_sent_ && mgr.hasResponseExpr(); + } + + const Http::RequestOrResponseHeaderMap* requestHeaders() const override { return nullptr; }; + const Http::RequestOrResponseHeaderMap* responseHeaders() const override { return headers_; } + private: void setProcessingModeInternal( const envoy::extensions::filters::http::ext_proc::v3::ProcessingMode& mode); From 212a876ce1f8fc65c4c0f622c0474860c08179d7 Mon Sep 17 00:00:00 2001 From: Jacob Bohanon Date: Tue, 6 Feb 2024 12:26:11 -0500 Subject: [PATCH 2/3] re-add HttpHeaders attribbutes field as not-implemented and deprecated Signed-off-by: Jacob Bohanon --- api/envoy/service/ext_proc/v3/external_processor.proto | 8 ++++++-- .../filters/http/ext_proc/ext_proc_integration_test.cc | 5 +---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/api/envoy/service/ext_proc/v3/external_processor.proto b/api/envoy/service/ext_proc/v3/external_processor.proto index 15d0688588659..466666cff58c2 100644 --- a/api/envoy/service/ext_proc/v3/external_processor.proto +++ b/api/envoy/service/ext_proc/v3/external_processor.proto @@ -199,8 +199,6 @@ message ProcessingResponse { // This message is sent to the external server when the HTTP request and responses // are first received. message HttpHeaders { - reserved 2; - // The HTTP request headers. All header keys will be // lower-cased, because HTTP header keys are case-insensitive. // The ``headers`` encoding is based on the runtime guard @@ -211,6 +209,12 @@ message HttpHeaders { // :ref:`value ` field. config.core.v3.HeaderMap headers = 1; + // [#not-implemented-hide:] + // This field is deprecated and not implemented. Attributes will be sent in + // the top-level :ref:`attributes attributes = 2; + // If true, then there is no message body associated with this // request or response. bool end_of_stream = 3; diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index d0becb1f2bab0..bf9d9d025b1b0 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -3429,10 +3429,7 @@ TEST_P(ExtProcIntegrationTest, SendAndReceiveDynamicMetadata) { } #if defined(USE_CEL_PARSER) -// Test the filter using the default configuration by connecting to -// an ext_proc server that responds to the request_headers message -// by requesting to modify the request headers. -TEST_P(ExtProcIntegrationTest, GetAndSetRequestResponseAttributes) { +TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SEND); proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SEND); proto_config_.mutable_request_attributes()->Add("request.path"); From d4e3e3fc212ad57c4ab55144fa30ac3fb4c4a6bf Mon Sep 17 00:00:00 2001 From: Jacob Bohanon Date: Wed, 7 Feb 2024 08:00:54 -0500 Subject: [PATCH 3/3] PR feedback and fixing integration test Signed-off-by: Jacob Bohanon --- api/envoy/service/ext_proc/v3/BUILD | 1 + .../ext_proc/v3/external_processor.proto | 4 +- .../filters/http/ext_proc/ext_proc.cc | 7 +++- .../filters/http/ext_proc/processor_state.h | 11 +++-- .../ext_proc/ext_proc_integration_test.cc | 41 +++++++++++++++++-- 5 files changed, 54 insertions(+), 10 deletions(-) diff --git a/api/envoy/service/ext_proc/v3/BUILD b/api/envoy/service/ext_proc/v3/BUILD index 0e337d5c3ed11..37704a3249557 100644 --- a/api/envoy/service/ext_proc/v3/BUILD +++ b/api/envoy/service/ext_proc/v3/BUILD @@ -7,6 +7,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( has_services = True, deps = [ + "//envoy/annotations:pkg", "//envoy/config/core/v3:pkg", "//envoy/extensions/filters/http/ext_proc/v3:pkg", "//envoy/type/v3:pkg", diff --git a/api/envoy/service/ext_proc/v3/external_processor.proto b/api/envoy/service/ext_proc/v3/external_processor.proto index 466666cff58c2..638ffbab19696 100644 --- a/api/envoy/service/ext_proc/v3/external_processor.proto +++ b/api/envoy/service/ext_proc/v3/external_processor.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package envoy.service.ext_proc.v3; +import "envoy/annotations/deprecation.proto"; import "envoy/config/core/v3/base.proto"; import "envoy/extensions/filters/http/ext_proc/v3/processing_mode.proto"; import "envoy/type/v3/http_status.proto"; @@ -213,7 +214,8 @@ message HttpHeaders { // This field is deprecated and not implemented. Attributes will be sent in // the top-level :ref:`attributes attributes = 2; + map attributes = 2 + [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; // If true, then there is no message body associated with this // request or response. diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index f06f5c66621ca..807f9c59c097b 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -312,6 +312,11 @@ FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_st decoding_state_.setCompleteBodyAvailable(true); } + // Set the request headers on decoding and encoding state in case they are + // needed later. + decoding_state_.setRequestHeaders(&headers); + encoding_state_.setRequestHeaders(&headers); + FilterHeadersStatus status = FilterHeadersStatus::Continue; if (decoding_state_.sendHeaders()) { status = onHeaders(decoding_state_, headers, end_stream); @@ -760,7 +765,7 @@ void Filter::addAttributes(const ProcessorState& state, ProcessingRequest& req) state.requestHeaders(), state.responseHeaders(), state.trailers()); attributes = config_->expressionManager().evaluateRequestAttributes(*activation_ptr); - state.sentAttributes(true); + state.setSentAttributes(true); (*req.mutable_attributes())[FilterName] = *attributes; } diff --git a/source/extensions/filters/http/ext_proc/processor_state.h b/source/extensions/filters/http/ext_proc/processor_state.h index 9feda7dad84d3..da6faec33f613 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.h +++ b/source/extensions/filters/http/ext_proc/processor_state.h @@ -135,9 +135,10 @@ class ProcessorState : public Logger::Loggable { return body_mode_; } + void setRequestHeaders(Http::RequestOrResponseHeaderMap* headers) { request_headers_ = headers; } void setHeaders(Http::RequestOrResponseHeaderMap* headers) { headers_ = headers; } void setTrailers(Http::HeaderMap* trailers) { trailers_ = trailers; } - virtual const Http::RequestOrResponseHeaderMap* requestHeaders() const PURE; + const Http::RequestOrResponseHeaderMap* requestHeaders() const { return request_headers_; }; virtual const Http::RequestOrResponseHeaderMap* responseHeaders() const PURE; const Http::HeaderMap* responseTrailers() const { return trailers_; } @@ -208,7 +209,7 @@ class ProcessorState : public Logger::Loggable { virtual bool sendAttributes(const ExpressionManager& mgr) const PURE; - void sentAttributes(bool sent) { attributes_sent_ = sent; } + void setSentAttributes(bool sent) { attributes_sent_ = sent; } protected: void setBodyMode( @@ -244,6 +245,10 @@ class ProcessorState : public Logger::Loggable { // The specific mode for body handling envoy::extensions::filters::http::ext_proc::v3::ProcessingMode_BodySendMode body_mode_; + // The request_headers_ field is guaranteed to hold the pointer to the request + // headers as set in decodeHeaders. This allows both decoding and encoding states + // to have access to the request headers map. + Http::RequestOrResponseHeaderMap* request_headers_ = nullptr; Http::RequestOrResponseHeaderMap* headers_ = nullptr; Http::HeaderMap* trailers_ = nullptr; Event::TimerPtr message_timer_; @@ -339,7 +344,6 @@ class DecodingProcessorState : public ProcessorState { return !attributes_sent_ && mgr.hasRequestExpr(); } - const Http::RequestOrResponseHeaderMap* requestHeaders() const override { return headers_; }; const Http::RequestOrResponseHeaderMap* responseHeaders() const override { return nullptr; } private: @@ -426,7 +430,6 @@ class EncodingProcessorState : public ProcessorState { return !attributes_sent_ && mgr.hasResponseExpr(); } - const Http::RequestOrResponseHeaderMap* requestHeaders() const override { return nullptr; }; const Http::RequestOrResponseHeaderMap* responseHeaders() const override { return headers_; } private: diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index bf9d9d025b1b0..c93e56aa48fb4 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -3431,7 +3431,9 @@ TEST_P(ExtProcIntegrationTest, SendAndReceiveDynamicMetadata) { #if defined(USE_CEL_PARSER) TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SEND); + proto_config_.mutable_processing_mode()->set_request_trailer_mode(ProcessingMode::SEND); proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SEND); + proto_config_.mutable_processing_mode()->set_response_trailer_mode(ProcessingMode::SEND); proto_config_.mutable_request_attributes()->Add("request.path"); proto_config_.mutable_request_attributes()->Add("request.method"); proto_config_.mutable_request_attributes()->Add("request.scheme"); @@ -3442,29 +3444,60 @@ TEST_P(ExtProcIntegrationTest, RequestResponseAttributes) { initializeConfig(); HttpIntegrationTest::initialize(); auto response = sendDownstreamRequest(absl::nullopt); - processRequestHeadersMessage( - *grpc_upstreams_[0], true, [](const HttpHeaders& req, HeadersResponse&) { + + // Handle request headers message. + processGenericMessage( + *grpc_upstreams_[0], true, [](const ProcessingRequest& req, ProcessingResponse&) { + EXPECT_TRUE(req.has_request_headers()); EXPECT_EQ(req.attributes().size(), 1); auto proto_struct = req.attributes().at("envoy.filters.http.ext_proc"); EXPECT_EQ(proto_struct.fields().at("request.path").string_value(), "/"); EXPECT_EQ(proto_struct.fields().at("request.method").string_value(), "GET"); EXPECT_EQ(proto_struct.fields().at("request.scheme").string_value(), "http"); EXPECT_EQ(proto_struct.fields().at("connection.mtls").bool_value(), false); + + // Make sure we are not including any data in the deprecated HttpHeaders.attributes. + EXPECT_TRUE(req.request_headers().attributes().empty()); return true; }); + // Handle request trailers message, making sure we did not send request attributes again. + processGenericMessage(*grpc_upstreams_[0], false, + [](const ProcessingRequest& req, ProcessingResponse&) { + EXPECT_TRUE(req.has_request_trailers()); + EXPECT_TRUE(req.attributes().empty()); + return true; + }); + handleUpstreamRequest(); - processResponseHeadersMessage( - *grpc_upstreams_[0], false, [](const HttpHeaders& req, HeadersResponse&) { + // Handle response headers message. + processGenericMessage( + *grpc_upstreams_[0], false, [](const ProcessingRequest& req, ProcessingResponse&) { + EXPECT_TRUE(req.has_response_headers()); EXPECT_EQ(req.attributes().size(), 1); auto proto_struct = req.attributes().at("envoy.filters.http.ext_proc"); EXPECT_EQ(proto_struct.fields().at("response.code").string_value(), "200"); EXPECT_EQ(proto_struct.fields().at("response.code_details").string_value(), StreamInfo::ResponseCodeDetails::get().ViaUpstream); + + // Make sure we didn't include request attributes in the response-path processing request. + EXPECT_FALSE(proto_struct.fields().contains("request.method")); + + // Make sure we are not including any data in the deprecated HttpHeaders.attributes. + EXPECT_TRUE(req.response_headers().attributes().empty()); return true; }); + // Handle response trailers message, making sure we did not send request or response attributes + // again. + processGenericMessage(*grpc_upstreams_[0], false, + [](const ProcessingRequest& req, ProcessingResponse&) { + EXPECT_TRUE(req.has_response_trailers()); + EXPECT_TRUE(req.attributes().empty()); + return true; + }); + verifyDownstreamResponse(*response, 200); } #endif