diff --git a/include/envoy/http/filter.h b/include/envoy/http/filter.h index ecb39f22dcb0..b9acc44a4f71 100644 --- a/include/envoy/http/filter.h +++ b/include/envoy/http/filter.h @@ -590,6 +590,12 @@ class StreamFilterBase { * onDestroy(). */ virtual void onDestroy() PURE; + + /** + * Called when a match result occurs that isn't handled by the filter manager. + * @param action the resulting match action + */ + virtual void onMatchCallback(const Matcher::Action&) {} }; /** diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index bde33e6d4520..6caf33f7536f 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -244,7 +244,7 @@ void ActiveStreamFilterBase::clearRouteCache() { parent_.filter_manager_callbacks_.clearRouteCache(); } -void ActiveStreamFilterBase::evaluateMatchTreeWithNewData( +void FilterMatchState::evaluateMatchTreeWithNewData( std::function update_func) { if (match_tree_evaluated_ || !matching_data_) { return; @@ -258,7 +258,9 @@ void ActiveStreamFilterBase::evaluateMatchTreeWithNewData( if (match_tree_evaluated_ && match_result.result_) { if (SkipAction().typeUrl() == match_result.result_->typeUrl()) { - skip_ = true; + skip_filter_ = true; + } else { + filter_->onMatchCallback(*match_result.result_); } } } @@ -418,11 +420,18 @@ void ActiveStreamDecoderFilter::requestDataTooLarge() { } } -void FilterManager::addStreamDecoderFilterWorker( - StreamDecoderFilterSharedPtr filter, Matcher::MatchTreeSharedPtr matcher, - HttpMatchingDataImplSharedPtr matching_data, bool dual_filter) { - ActiveStreamDecoderFilterPtr wrapper(new ActiveStreamDecoderFilter( - *this, filter, std::move(matcher), std::move(matching_data), dual_filter)); +void FilterManager::addStreamDecoderFilterWorker(StreamDecoderFilterSharedPtr filter, + FilterMatchStateSharedPtr match_state, + bool dual_filter) { + ActiveStreamDecoderFilterPtr wrapper( + new ActiveStreamDecoderFilter(*this, filter, match_state, dual_filter)); + + // If we're a dual handling filter, have the encoding wrapper be the only thing registering itself + // as the handling filter. + if (match_state) { + match_state->filter_ = filter.get(); + } + filter->setDecoderFilterCallbacks(*wrapper); // Note: configured decoder filters are appended to decoder_filters_. // This means that if filters are configured in the following order (assume all three filters are @@ -435,11 +444,16 @@ void FilterManager::addStreamDecoderFilterWorker( LinkedList::moveIntoListBack(std::move(wrapper), decoder_filters_); } -void FilterManager::addStreamEncoderFilterWorker( - StreamEncoderFilterSharedPtr filter, Matcher::MatchTreeSharedPtr match_tree, - HttpMatchingDataImplSharedPtr matching_data, bool dual_filter) { - ActiveStreamEncoderFilterPtr wrapper(new ActiveStreamEncoderFilter( - *this, filter, std::move(match_tree), std::move(matching_data), dual_filter)); +void FilterManager::addStreamEncoderFilterWorker(StreamEncoderFilterSharedPtr filter, + FilterMatchStateSharedPtr match_state, + bool dual_filter) { + ActiveStreamEncoderFilterPtr wrapper( + new ActiveStreamEncoderFilter(*this, filter, match_state, dual_filter)); + + if (match_state) { + match_state->filter_ = filter.get(); + } + filter->setEncoderFilterCallbacks(*wrapper); // Note: configured encoder filters are prepended to encoder_filters_. // This means that if filters are configured in the following order (assume all three filters are @@ -477,9 +491,12 @@ void FilterManager::decodeHeaders(ActiveStreamDecoderFilter* filter, RequestHead std::list::iterator continue_data_entry = decoder_filters_.end(); for (; entry != decoder_filters_.end(); entry++) { - (*entry)->evaluateMatchTreeWithNewData( - [&](auto& matching_data) { matching_data.onRequestHeaders(headers); }); - if ((*entry)->skip_) { + if ((*entry)->filter_match_state_) { + (*entry)->filter_match_state_->evaluateMatchTreeWithNewData( + [&](auto& matching_data) { matching_data.onRequestHeaders(headers); }); + } + + if ((*entry)->skipFilter()) { continue; } @@ -563,7 +580,7 @@ void FilterManager::decodeData(ActiveStreamDecoderFilter* filter, Buffer::Instan commonDecodePrefix(filter, filter_iteration_start_state); for (; entry != decoder_filters_.end(); entry++) { - if ((*entry)->skip_) { + if ((*entry)->skipFilter()) { continue; } // If the filter pointed by entry has stopped for all frame types, return now. @@ -697,7 +714,7 @@ void FilterManager::decodeTrailers(ActiveStreamDecoderFilter* filter, RequestTra commonDecodePrefix(filter, FilterIterationStartState::CanStartFromCurrent); for (; entry != decoder_filters_.end(); entry++) { - if ((*entry)->skip_) { + if ((*entry)->skipFilter()) { continue; } @@ -729,7 +746,7 @@ void FilterManager::decodeMetadata(ActiveStreamDecoderFilter* filter, MetadataMa commonDecodePrefix(filter, FilterIterationStartState::CanStartFromCurrent); for (; entry != decoder_filters_.end(); entry++) { - if ((*entry)->skip_) { + if ((*entry)->skipFilter()) { continue; } // If the filter pointed by entry has stopped for all frame type, stores metadata and returns. @@ -939,7 +956,7 @@ void FilterManager::encode100ContinueHeaders(ActiveStreamEncoderFilter* filter, std::list::iterator entry = commonEncodePrefix(filter, false, FilterIterationStartState::AlwaysStartFromNext); for (; entry != encoder_filters_.end(); entry++) { - if ((*entry)->skip_) { + if ((*entry)->skipFilter()) { continue; } @@ -984,9 +1001,11 @@ void FilterManager::encodeHeaders(ActiveStreamEncoderFilter* filter, ResponseHea std::list::iterator continue_data_entry = encoder_filters_.end(); for (; entry != encoder_filters_.end(); entry++) { - (*entry)->evaluateMatchTreeWithNewData( - [&headers](auto& matching_data) { matching_data.onResponseHeaders(headers); }); - if ((*entry)->skip_) { + if ((*entry)->filter_match_state_) { + (*entry)->filter_match_state_->evaluateMatchTreeWithNewData( + [&headers](auto& matching_data) { matching_data.onResponseHeaders(headers); }); + } + if ((*entry)->skipFilter()) { continue; } ASSERT(!(state_.filter_call_state_ & FilterCallState::EncodeHeaders)); @@ -1043,7 +1062,7 @@ void FilterManager::encodeMetadata(ActiveStreamEncoderFilter* filter, commonEncodePrefix(filter, false, FilterIterationStartState::CanStartFromCurrent); for (; entry != encoder_filters_.end(); entry++) { - if ((*entry)->skip_) { + if ((*entry)->skipFilter()) { continue; } // If the filter pointed by entry has stopped for all frame type, stores metadata and returns. @@ -1114,7 +1133,7 @@ void FilterManager::encodeData(ActiveStreamEncoderFilter* filter, Buffer::Instan const bool trailers_exists_at_start = filter_manager_callbacks_.responseTrailers().has_value(); for (; entry != encoder_filters_.end(); entry++) { - if ((*entry)->skip_) { + if ((*entry)->skipFilter()) { continue; } // If the filter pointed by entry has stopped for all frame type, return now. diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index 06b9e4ee65ce..80845e0f9375 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -152,6 +152,37 @@ class HttpResponseHeadersDataInputFactory class SkipAction : public Matcher::ActionBase< envoy::extensions::filters::common::matcher::action::v3::SkipFilter> {}; +struct ActiveStreamFilterBase; + +/** + * Manages the shared match state between one or two filters. + * The need for this class comes from the fact that a single instantiated filter can be wrapped by + * two different ActiveStreamFilters, one for encoding and one for decoding. Certain match actions + * should be made visible to both wrappers (e.g. the skip action), while other actions should be + * sent to the underlying filter exactly once. + */ +class FilterMatchState { +public: + FilterMatchState(Matcher::MatchTreeSharedPtr match_tree, + HttpMatchingDataImplSharedPtr matching_data) + : match_tree_(std::move(match_tree)), matching_data_(std::move(matching_data)), + match_tree_evaluated_(false), skip_filter_(false) {} + + void evaluateMatchTreeWithNewData(std::function update_func); + + StreamFilterBase* filter_{}; + + bool skipFilter() const { return skip_filter_; } + +private: + Matcher::MatchTreeSharedPtr match_tree_; + HttpMatchingDataImplSharedPtr matching_data_; + bool match_tree_evaluated_ : 1; + bool skip_filter_ : 1; +}; + +using FilterMatchStateSharedPtr = std::shared_ptr; + class SkipActionFactory : public Matcher::ActionFactory { public: std::string name() const override { return "skip"; } @@ -162,6 +193,7 @@ class SkipActionFactory : public Matcher::ActionFactory { return std::make_unique(); } }; + /** * Base class wrapper for both stream encoder and decoder filters. * @@ -172,14 +204,11 @@ class SkipActionFactory : public Matcher::ActionFactory { struct ActiveStreamFilterBase : public virtual StreamFilterCallbacks, Logger::Loggable { ActiveStreamFilterBase(FilterManager& parent, bool dual_filter, - Matcher::MatchTreeSharedPtr match_tree, - HttpMatchingDataImplSharedPtr matching_data) + FilterMatchStateSharedPtr match_state) : parent_(parent), iteration_state_(IterationState::Continue), - match_tree_(std::move(match_tree)), matching_data_(std::move(matching_data)), - iterate_from_current_filter_(false), headers_continued_(false), - continue_headers_continued_(false), end_stream_(false), dual_filter_(dual_filter), - decode_headers_called_(false), encode_headers_called_(false), match_tree_evaluated_(false), - skip_(false) {} + filter_match_state_(std::move(match_state)), iterate_from_current_filter_(false), + headers_continued_(false), continue_headers_continued_(false), end_stream_(false), + dual_filter_(dual_filter), decode_headers_called_(false), encode_headers_called_(false) {} // Functions in the following block are called after the filter finishes processing // corresponding data. Those functions handle state updates and data storage (if needed) @@ -212,6 +241,8 @@ struct ActiveStreamFilterBase : public virtual StreamFilterCallbacks, // TODO(soya3129): make this pure when adding impl to encoder filter. virtual void handleMetadataAfterHeadersCallback() PURE; + virtual void onMatchCallback(const Matcher::Action& action) PURE; + // Http::StreamFilterCallbacks const Network::Connection* connection() override; Event::Dispatcher& dispatcher() override; @@ -248,8 +279,7 @@ struct ActiveStreamFilterBase : public virtual StreamFilterCallbacks, } return saved_response_metadata_.get(); } - - void evaluateMatchTreeWithNewData(std::function update_func); + bool skipFilter() const { return filter_match_state_ && filter_match_state_->skipFilter(); } // A vector to save metadata when the current filter's [de|en]codeMetadata() can not be called, // either because [de|en]codeHeaders() of the current filter returns StopAllIteration or because @@ -269,9 +299,7 @@ struct ActiveStreamFilterBase : public virtual StreamFilterCallbacks, FilterManager& parent_; IterationState iteration_state_; - Matcher::MatchTreeSharedPtr match_tree_; - HttpMatchingDataImplSharedPtr matching_data_; - + FilterMatchStateSharedPtr filter_match_state_; // If the filter resumes iteration from a StopAllBuffer/Watermark state, the current filter // hasn't parsed data and trailers. As a result, the filter iteration should start with the // current filter instead of the next one. If true, filter iteration starts with the current @@ -284,8 +312,8 @@ struct ActiveStreamFilterBase : public virtual StreamFilterCallbacks, const bool dual_filter_ : 1; bool decode_headers_called_ : 1; bool encode_headers_called_ : 1; - bool match_tree_evaluated_ : 1; - bool skip_ : 1; + + friend FilterMatchState; }; /** @@ -295,11 +323,8 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, public StreamDecoderFilterCallbacks, LinkedObject { ActiveStreamDecoderFilter(FilterManager& parent, StreamDecoderFilterSharedPtr filter, - Matcher::MatchTreeSharedPtr match_tree, - HttpMatchingDataImplSharedPtr matching_data, bool dual_filter) - : ActiveStreamFilterBase(parent, dual_filter, std::move(match_tree), - std::move(matching_data)), - handle_(filter) {} + FilterMatchStateSharedPtr match_state, bool dual_filter) + : ActiveStreamFilterBase(parent, dual_filter, std::move(match_state)), handle_(filter) {} // ActiveStreamFilterBase bool canContinue() override; @@ -321,6 +346,9 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, void drainSavedRequestMetadata(); // This function is called after the filter calls decodeHeaders() to drain accumulated metadata. void handleMetadataAfterHeadersCallback() override; + void onMatchCallback(const Matcher::Action& action) override { + handle_->onMatchCallback(std::move(action)); + } // Http::StreamDecoderFilterCallbacks void addDecodedData(Buffer::Instance& data, bool streaming) override; @@ -387,11 +415,8 @@ struct ActiveStreamEncoderFilter : public ActiveStreamFilterBase, public StreamEncoderFilterCallbacks, LinkedObject { ActiveStreamEncoderFilter(FilterManager& parent, StreamEncoderFilterSharedPtr filter, - Matcher::MatchTreeSharedPtr match_tree, - HttpMatchingDataImplSharedPtr matching_data, bool dual_filter) - : ActiveStreamFilterBase(parent, dual_filter, std::move(match_tree), - std::move(matching_data)), - handle_(filter) {} + FilterMatchStateSharedPtr match_state, bool dual_filter) + : ActiveStreamFilterBase(parent, dual_filter, std::move(match_state)), handle_(filter) {} // ActiveStreamFilterBase bool canContinue() override { return true; } @@ -404,6 +429,7 @@ struct ActiveStreamEncoderFilter : public ActiveStreamFilterBase, void doData(bool end_stream) override; void drainSavedResponseMetadata(); void handleMetadataAfterHeadersCallback() override; + void onMatchCallback(const Matcher::Action& action) override { handle_->onMatchCallback(action); } void doMetadata() override { if (saved_response_metadata_ != nullptr) { @@ -732,34 +758,40 @@ class FilterManager : public ScopeTrackedObject, // Http::FilterChainFactoryCallbacks void addStreamDecoderFilter(StreamDecoderFilterSharedPtr filter) override { - addStreamDecoderFilterWorker(filter, nullptr, nullptr, false); + addStreamDecoderFilterWorker(filter, nullptr, false); } void addStreamDecoderFilter(StreamDecoderFilterSharedPtr filter, Matcher::MatchTreeSharedPtr match_tree) override { if (match_tree) { - auto matching_data = std::make_shared(); - addStreamDecoderFilterWorker(filter, std::move(match_tree), std::move(matching_data), false); + addStreamDecoderFilterWorker( + filter, + std::make_shared(std::move(match_tree), + std::make_shared()), + false); return; } - addStreamDecoderFilterWorker(filter, nullptr, nullptr, false); + addStreamDecoderFilterWorker(filter, nullptr, false); } void addStreamEncoderFilter(StreamEncoderFilterSharedPtr filter) override { - addStreamEncoderFilterWorker(filter, nullptr, nullptr, false); + addStreamEncoderFilterWorker(filter, nullptr, false); } void addStreamEncoderFilter(StreamEncoderFilterSharedPtr filter, Matcher::MatchTreeSharedPtr match_tree) override { if (match_tree) { - addStreamEncoderFilterWorker(filter, std::move(match_tree), - std::make_shared(), false); + addStreamEncoderFilterWorker( + filter, + std::make_shared(std::move(match_tree), + std::make_shared()), + false); return; } - addStreamEncoderFilterWorker(filter, nullptr, nullptr, false); + addStreamEncoderFilterWorker(filter, nullptr, false); } void addStreamFilter(StreamFilterSharedPtr filter) override { - addStreamDecoderFilterWorker(filter, nullptr, nullptr, true); - addStreamEncoderFilterWorker(filter, nullptr, nullptr, true); + addStreamDecoderFilterWorker(filter, nullptr, true); + addStreamEncoderFilterWorker(filter, nullptr, true); } void addStreamFilter(StreamFilterSharedPtr filter, Matcher::MatchTreeSharedPtr match_tree) override { @@ -768,14 +800,15 @@ class FilterManager : public ScopeTrackedObject, // TODO(snowp): The match tree might be fully evaluated twice, ideally we should expose // the result to both filters after the first match evaluation. if (match_tree) { - auto matching_data = std::make_shared(); - addStreamDecoderFilterWorker(filter, match_tree, matching_data, true); - addStreamEncoderFilterWorker(filter, std::move(match_tree), std::move(matching_data), true); + auto matching_state = std::make_shared( + std::move(match_tree), std::make_shared()); + addStreamDecoderFilterWorker(filter, matching_state, true); + addStreamEncoderFilterWorker(filter, std::move(matching_state), true); return; } - addStreamDecoderFilterWorker(filter, nullptr, nullptr, true); - addStreamEncoderFilterWorker(filter, nullptr, nullptr, true); + addStreamDecoderFilterWorker(filter, nullptr, true); + addStreamEncoderFilterWorker(filter, nullptr, true); } void addAccessLogHandler(AccessLog::InstanceSharedPtr handler) override; @@ -858,11 +891,9 @@ class FilterManager : public ScopeTrackedObject, // TODO(snowp): Make private as filter chain construction is moved into FM. void addStreamDecoderFilterWorker(StreamDecoderFilterSharedPtr filter, - Matcher::MatchTreeSharedPtr match_tree, - HttpMatchingDataImplSharedPtr matching_data, bool dual_filter); + FilterMatchStateSharedPtr match_state, bool dual_filter); void addStreamEncoderFilterWorker(StreamEncoderFilterSharedPtr filter, - Matcher::MatchTreeSharedPtr match_tree, - HttpMatchingDataImplSharedPtr matching_data, bool dual_filter); + FilterMatchStateSharedPtr match_state, bool dual_filter); void disarmRequestTimeout(); diff --git a/test/common/http/filter_manager_test.cc b/test/common/http/filter_manager_test.cc index 85d755e864cb..9e11905482c3 100644 --- a/test/common/http/filter_manager_test.cc +++ b/test/common/http/filter_manager_test.cc @@ -6,6 +6,7 @@ #include "common/http/filter_manager.h" #include "common/matcher/exact_map_matcher.h" +#include "common/matcher/matcher.h" #include "common/stream_info/filter_state_impl.h" #include "common/stream_info/stream_info_impl.h" @@ -155,6 +156,28 @@ Matcher::MatchTreeSharedPtr createRequestMatchingTree() { return tree; } +struct TestAction : Matcher::ActionBase {}; + +Matcher::MatchTreeSharedPtr createRequestMatchingTreeCustomAction() { + auto tree = std::make_shared>( + std::make_unique("match-header"), absl::nullopt); + + tree->addChild("match", Matcher::OnMatch{ + []() { return std::make_unique(); }, nullptr}); + + return tree; +} + +Matcher::MatchTreeSharedPtr createResponseMatchingTreeCustomAction() { + auto tree = std::make_shared>( + std::make_unique("match-header"), absl::nullopt); + + tree->addChild("match", Matcher::OnMatch{ + []() { return std::make_unique(); }, nullptr}); + + return tree; +} + Matcher::MatchTreeSharedPtr createRequestAndResponseMatchingTree() { auto tree = std::make_shared>( std::make_unique("match-header"), absl::nullopt); @@ -249,6 +272,78 @@ TEST_F(FilterManagerTest, MatchTreeSkipActionRequestAndResponseHeaders) { filter_manager_->decodeData(data, true); filter_manager_->destroyFilters(); } + +// Verify that we propagate custom match actions to a decoding filter. +TEST_F(FilterManagerTest, MatchTreeFilterActionDecodingHeaders) { + initialize(); + + std::shared_ptr decoder_filter(new MockStreamDecoderFilter()); + EXPECT_CALL(*decoder_filter, setDecoderFilterCallbacks(_)); + EXPECT_CALL(*decoder_filter, onMatchCallback(_)); + EXPECT_CALL(*decoder_filter, decodeHeaders(_, _)); + EXPECT_CALL(*decoder_filter, decodeComplete()); + EXPECT_CALL(*decoder_filter, onDestroy()); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .WillRepeatedly(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(decoder_filter, createRequestMatchingTreeCustomAction()); + })); + + RequestHeaderMapPtr grpc_headers{ + new TestRequestHeaderMapImpl{{":authority", "host"}, + {":path", "/"}, + {":method", "GET"}, + {"match-header", "match"}, + {"content-type", "application/grpc"}}}; + + ON_CALL(filter_manager_callbacks_, requestHeaders()) + .WillByDefault(Return(makeOptRef(*grpc_headers))); + filter_manager_->createFilterChain(); + + filter_manager_->requestHeadersInitialized(); + filter_manager_->decodeHeaders(*grpc_headers, true); + filter_manager_->destroyFilters(); +} + +// Verify that we propagate custom match actions exactly once to a dual filter. +TEST_F(FilterManagerTest, MatchTreeFilterActionDualFilter) { + initialize(); + + std::shared_ptr filter(new MockStreamFilter()); + EXPECT_CALL(*filter, setDecoderFilterCallbacks(_)); + EXPECT_CALL(*filter, setEncoderFilterCallbacks(_)); + EXPECT_CALL(*filter, decodeHeaders(_, true)) + .WillOnce(Invoke([&](auto&, bool) -> FilterHeadersStatus { + ResponseHeaderMapPtr headers{new TestResponseHeaderMapImpl{ + {":status", "200"}, {"match-header", "match"}, {"content-type", "application/grpc"}}}; + filter->decoder_callbacks_->encodeHeaders(std::move(headers), true, "details"); + + return FilterHeadersStatus::StopIteration; + })); + EXPECT_CALL(*filter, onDestroy()); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .WillRepeatedly(Invoke([&](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(filter, createResponseMatchingTreeCustomAction()); + })); + + RequestHeaderMapPtr grpc_headers{ + new TestRequestHeaderMapImpl{{":authority", "host"}, + {":path", "/"}, + {":method", "GET"}, + {"match-header", "match"}, + {"content-type", "application/grpc"}}}; + + ON_CALL(filter_manager_callbacks_, requestHeaders()) + .WillByDefault(Return(makeOptRef(*grpc_headers))); + filter_manager_->createFilterChain(); + + filter_manager_->requestHeadersInitialized(); + EXPECT_CALL(*filter, encodeHeaders(_, true)); + EXPECT_CALL(*filter, onMatchCallback(_)); + filter_manager_->decodeHeaders(*grpc_headers, true); + filter_manager_->destroyFilters(); +} } // namespace } // namespace Http } // namespace Envoy \ No newline at end of file diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index d85065dc37bd..378aa1c61a2b 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -321,6 +321,7 @@ class MockStreamDecoderFilter : public StreamDecoderFilter { // Http::StreamFilterBase MOCK_METHOD(void, onStreamComplete, ()); MOCK_METHOD(void, onDestroy, ()); + MOCK_METHOD(void, onMatchCallback, (const Matcher::Action&)); // Http::StreamDecoderFilter MOCK_METHOD(FilterHeadersStatus, decodeHeaders, (RequestHeaderMap & headers, bool end_stream)); @@ -346,6 +347,7 @@ class MockStreamEncoderFilter : public StreamEncoderFilter { // Http::StreamFilterBase MOCK_METHOD(void, onStreamComplete, ()); MOCK_METHOD(void, onDestroy, ()); + MOCK_METHOD(void, onMatchCallback, (const Matcher::Action&)); // Http::MockStreamEncoderFilter MOCK_METHOD(FilterHeadersStatus, encode100ContinueHeaders, (ResponseHeaderMap & headers)); @@ -367,6 +369,7 @@ class MockStreamFilter : public StreamFilter { // Http::StreamFilterBase MOCK_METHOD(void, onStreamComplete, ()); MOCK_METHOD(void, onDestroy, ()); + MOCK_METHOD(void, onMatchCallback, (const Matcher::Action&)); // Http::StreamDecoderFilter MOCK_METHOD(FilterHeadersStatus, decodeHeaders, (RequestHeaderMap & headers, bool end_stream));