diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0b48256663bf..4da54c63662a 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -324,6 +324,10 @@ new_features: - area: original_dst change: | added support for the internal listener address recovery using the original destination listener filter. +- area: filters + change: | + added filters to update the filter state for :ref:`the HTTP requests ` and + :ref:`the TCP connections `. deprecated: diff --git a/source/extensions/filters/http/set_filter_state/config.cc b/source/extensions/filters/http/set_filter_state/config.cc index 7eedad94a6bd..1e2b7dfd8886 100644 --- a/source/extensions/filters/http/set_filter_state/config.cc +++ b/source/extensions/filters/http/set_filter_state/config.cc @@ -24,21 +24,16 @@ Http::FilterHeadersStatus SetFilterState::decodeHeaders(Http::RequestHeaderMap& Http::FilterFactoryCb SetFilterStateConfig::createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::set_filter_state::v3::Config& proto_config, - const std::string&, Server::Configuration::FactoryContext& context) { - auto filter_config(std::make_shared( - proto_config.on_request_headers(), StreamInfo::FilterState::LifeSpan::FilterChain, context)); - return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { - callbacks.addStreamDecoderFilter( - Http::StreamDecoderFilterSharedPtr{new SetFilterState(filter_config)}); - }; + const std::string& stat_prefix, Server::Configuration::FactoryContext& context) { + return createFilterFactoryFromProtoWithServerContextTyped(proto_config, stat_prefix, + context.getServerFactoryContext()); } Http::FilterFactoryCb SetFilterStateConfig::createFilterFactoryFromProtoWithServerContextTyped( const envoy::extensions::filters::http::set_filter_state::v3::Config& proto_config, const std::string&, Server::Configuration::ServerFactoryContext& context) { - auto filter_config(std::make_shared( - proto_config.on_request_headers(), StreamInfo::FilterState::LifeSpan::FilterChain, context)); - + const auto filter_config = std::make_shared( + proto_config.on_request_headers(), StreamInfo::FilterState::LifeSpan::FilterChain, context); return [filter_config](Http::FilterChainFactoryCallbacks& callbacks) -> void { callbacks.addStreamDecoderFilter( Http::StreamDecoderFilterSharedPtr{new SetFilterState(filter_config)}); diff --git a/test/extensions/filters/http/set_filter_state/BUILD b/test/extensions/filters/http/set_filter_state/BUILD new file mode 100644 index 000000000000..a3c19b44b22e --- /dev/null +++ b/test/extensions/filters/http/set_filter_state/BUILD @@ -0,0 +1,28 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "integration_test", + srcs = [ + "integration_test.cc", + ], + extension_names = ["envoy.filters.http.set_filter_state"], + deps = [ + "//source/common/router:string_accessor_lib", + "//source/extensions/filters/http/set_filter_state:config", + "//test/mocks/http:http_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/stream_info:stream_info_mocks", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/filters/http/set_filter_state/integration_test.cc b/test/extensions/filters/http/set_filter_state/integration_test.cc new file mode 100644 index 000000000000..9b2afacec56f --- /dev/null +++ b/test/extensions/filters/http/set_filter_state/integration_test.cc @@ -0,0 +1,73 @@ +#include + +#include "source/common/protobuf/protobuf.h" +#include "source/common/router/string_accessor_impl.h" +#include "source/extensions/filters/http/set_filter_state/config.h" + +#include "test/mocks/http/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace SetFilterState { + +class ObjectFooFactory : public StreamInfo::FilterState::ObjectFactory { +public: + std::string name() const override { return "foo"; } + std::unique_ptr + createFromBytes(absl::string_view data) const override { + return std::make_unique(data); + } +}; + +REGISTER_FACTORY(ObjectFooFactory, StreamInfo::FilterState::ObjectFactory); + +class SetMetadataIntegrationTest : public testing::Test { +public: + SetMetadataIntegrationTest() = default; + + void runFilter(const std::string& yaml_config) { + envoy::extensions::filters::http::set_filter_state::v3::Config proto_config; + TestUtility::loadFromYaml(yaml_config, proto_config); + auto config = std::make_shared( + proto_config.on_request_headers(), StreamInfo::FilterState::LifeSpan::FilterChain, + context_); + auto filter = std::make_shared(config); + NiceMock decoder_callbacks; + filter->setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(info_)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter->decodeHeaders(headers_, true)); + } + + NiceMock context_; + Http::TestRequestHeaderMapImpl headers_{{"test-header", "test-value"}}; + NiceMock info_; +}; + +TEST_F(SetMetadataIntegrationTest, FromHeader) { + const std::string yaml_config = R"EOF( + on_request_headers: + - object_key: foo + format_string: + text_format_source: + inline_string: "%REQ(test-header)%" + )EOF"; + runFilter(yaml_config); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(foo->serializeAsString(), "test-value"); +} + +} // namespace SetFilterState +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/set_filter_state/BUILD b/test/extensions/filters/network/set_filter_state/BUILD new file mode 100644 index 000000000000..369daf997088 --- /dev/null +++ b/test/extensions/filters/network/set_filter_state/BUILD @@ -0,0 +1,29 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "integration_test", + srcs = [ + "integration_test.cc", + ], + extension_names = ["envoy.filters.network.set_filter_state"], + deps = [ + "//source/common/router:string_accessor_lib", + "//source/extensions/filters/network/set_filter_state:config", + "//test/mocks/network:network_mocks", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/stream_info:stream_info_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/filters/network/set_filter_state/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/filters/network/set_filter_state/integration_test.cc b/test/extensions/filters/network/set_filter_state/integration_test.cc new file mode 100644 index 000000000000..85b59bf6e608 --- /dev/null +++ b/test/extensions/filters/network/set_filter_state/integration_test.cc @@ -0,0 +1,74 @@ +#include + +#include "envoy/extensions/filters/network/set_filter_state/v3/set_filter_state.pb.h" +#include "envoy/extensions/filters/network/set_filter_state/v3/set_filter_state.pb.validate.h" + +#include "source/common/protobuf/protobuf.h" +#include "source/common/router/string_accessor_impl.h" +#include "source/extensions/filters/network/set_filter_state/config.h" + +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace SetFilterState { + +class ObjectFooFactory : public StreamInfo::FilterState::ObjectFactory { +public: + std::string name() const override { return "foo"; } + std::unique_ptr + createFromBytes(absl::string_view data) const override { + return std::make_unique(data); + } +}; + +REGISTER_FACTORY(ObjectFooFactory, StreamInfo::FilterState::ObjectFactory); + +class SetMetadataIntegrationTest : public testing::Test { +public: + SetMetadataIntegrationTest() = default; + + void runFilter(const std::string& yaml_config) { + envoy::extensions::filters::network::set_filter_state::v3::Config proto_config; + TestUtility::loadFromYaml(yaml_config, proto_config); + auto config = std::make_shared( + proto_config.on_new_connection(), StreamInfo::FilterState::LifeSpan::Connection, context_); + auto filter = std::make_shared(config); + NiceMock callbacks; + filter->initializeReadFilterCallbacks(callbacks); + EXPECT_CALL(callbacks.connection_, streamInfo()).WillRepeatedly(ReturnRef(info_)); + EXPECT_EQ(Network::FilterStatus::Continue, filter->onNewConnection()); + } + + NiceMock context_; + NiceMock info_; +}; + +TEST_F(SetMetadataIntegrationTest, FromHeader) { + const std::string yaml_config = R"EOF( + on_new_connection: + - object_key: foo + format_string: + text_format_source: + inline_string: "bar" + )EOF"; + runFilter(yaml_config); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(foo->serializeAsString(), "bar"); +} + +} // namespace SetFilterState +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy