├── .bazelrc ├── .bazelversion ├── .circleci └── config.yml ├── .clang-format ├── .devcontainer ├── .github ├── dependabot.yml └── workflows │ ├── envoy-sync.yaml │ └── scorecard.yml ├── .gitignore ├── .gitmodules ├── BUILD ├── README.md ├── WORKSPACE ├── bazel └── get_workspace_status ├── ci ├── do_ci.sh └── sync_envoy.sh ├── echo2.cc ├── echo2.h ├── echo2_config.cc ├── echo2_integration_test.cc ├── echo2_server.yaml ├── envoy_binary_test.sh └── http-filter-example ├── BUILD ├── README.md ├── http_filter.cc ├── http_filter.h ├── http_filter.proto ├── http_filter_config.cc └── http_filter_integration_test.cc /.bazelrc: -------------------------------------------------------------------------------- 1 | import %workspace%/envoy/.bazelrc 2 | build --platform_mappings=envoy/bazel/platform_mappings 3 | -------------------------------------------------------------------------------- /.bazelversion: -------------------------------------------------------------------------------- 1 | ./envoy/.bazelversion -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | references: 2 | envoy-build-image: &envoy-build-image 3 | envoyproxy/envoy-build:latest 4 | 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | - image: *envoy-build-image 10 | resource_class: xlarge 11 | steps: 12 | - checkout 13 | - run: git submodule update --init 14 | - run: ./ci/do_ci.sh build 15 | test: 16 | docker: 17 | - image: *envoy-build-image 18 | resource_class: xlarge 19 | steps: 20 | - checkout 21 | - run: git submodule update --init 22 | - run: ./ci/do_ci.sh test 23 | 24 | workflows: 25 | version: 2 26 | all: 27 | jobs: 28 | - build 29 | - test 30 | 31 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -2 4 | ColumnLimit: 100 5 | DerivePointerAlignment: false 6 | PointerAlignment: Left 7 | SortIncludes: false 8 | ... 9 | 10 | -------------------------------------------------------------------------------- /.devcontainer: -------------------------------------------------------------------------------- 1 | envoy/.devcontainer -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.github/workflows/envoy-sync.yaml: -------------------------------------------------------------------------------- 1 | name: Sync Envoy 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | sync: 15 | runs-on: ubuntu-20.04 16 | permissions: 17 | contents: write 18 | if: | 19 | ${{ 20 | !contains(github.actor, '[bot]') 21 | || github.actor == 'sync-envoy[bot]' 22 | }} 23 | steps: 24 | - id: appauth 25 | uses: envoyproxy/toolshed/gh-actions/appauth@80a2b0325d086b0dee546bb89496763cd11c7f8e # actions-v0 26 | with: 27 | key: ${{ secrets.ENVOY_CI_UPDATE_BOT_KEY }} 28 | app_id: ${{ secrets.ENVOY_CI_UPDATE_APP_ID }} 29 | 30 | - name: 'Checkout Repository' 31 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 32 | with: 33 | ref: main 34 | token: ${{ steps.appauth.outputs.value }} 35 | 36 | # Checkout the Envoy repo at latest commit 37 | - name: 'Checkout Repository' 38 | uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 39 | with: 40 | repository: envoyproxy/envoy 41 | fetch-depth: 0 42 | path: upstream 43 | - run: mv upstream ../envoy 44 | 45 | - run: ci/sync_envoy.sh 46 | env: 47 | ENVOY_SRC_DIR: ../envoy 48 | GITHUB_EMAIL: "135279899+update-envoy[bot]@users.noreply.github.com" 49 | GITHUB_NAME: "update-envoy[bot]" 50 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | name: Scorecard supply-chain security 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | branch_protection_rule: 8 | schedule: 9 | - cron: '33 13 * * 5' 10 | push: 11 | branches: 12 | - "main" 13 | 14 | jobs: 15 | analysis: 16 | name: Scorecard analysis 17 | runs-on: ubuntu-22.04 18 | permissions: 19 | security-events: write 20 | id-token: write 21 | 22 | steps: 23 | - name: "Checkout code" 24 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 25 | with: 26 | persist-credentials: false 27 | 28 | - name: "Run analysis" 29 | uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 30 | with: 31 | results_file: results.sarif 32 | results_format: sarif 33 | publish_results: true 34 | 35 | - name: "Upload artifact" 36 | uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 37 | with: 38 | name: SARIF file 39 | path: results.sarif 40 | retention-days: 5 41 | 42 | - name: "Upload to code-scanning" 43 | uses: github/codeql-action/upload-sarif@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 44 | with: 45 | sarif_file: results.sarif 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | clang.bazelrc 3 | user.bazelrc 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "envoy"] 2 | path = envoy 3 | url = https://github.com/envoyproxy/envoy.git 4 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load( 4 | "@envoy//bazel:envoy_build_system.bzl", 5 | "envoy_cc_binary", 6 | "envoy_cc_library", 7 | "envoy_cc_test", 8 | ) 9 | 10 | envoy_cc_binary( 11 | name = "envoy", 12 | repository = "@envoy", 13 | deps = [ 14 | ":echo2_config", 15 | "@envoy//source/exe:envoy_main_entry_lib", 16 | ], 17 | ) 18 | 19 | envoy_cc_library( 20 | name = "echo2_lib", 21 | srcs = ["echo2.cc"], 22 | hdrs = ["echo2.h"], 23 | repository = "@envoy", 24 | deps = [ 25 | "@envoy//envoy/buffer:buffer_interface", 26 | "@envoy//envoy/network:connection_interface", 27 | "@envoy//envoy/network:filter_interface", 28 | "@envoy//source/common/common:assert_lib", 29 | "@envoy//source/common/common:logger_lib", 30 | ], 31 | ) 32 | 33 | envoy_cc_library( 34 | name = "echo2_config", 35 | srcs = ["echo2_config.cc"], 36 | repository = "@envoy", 37 | deps = [ 38 | ":echo2_lib", 39 | "@envoy//envoy/network:filter_interface", 40 | "@envoy//envoy/registry:registry", 41 | "@envoy//envoy/server:filter_config_interface", 42 | ], 43 | ) 44 | 45 | envoy_cc_test( 46 | name = "echo2_integration_test", 47 | srcs = ["echo2_integration_test.cc"], 48 | data = ["echo2_server.yaml"], 49 | repository = "@envoy", 50 | deps = [ 51 | ":echo2_config", 52 | "@envoy//test/integration:integration_lib" 53 | ], 54 | ) 55 | 56 | sh_test( 57 | name = "envoy_binary_test", 58 | srcs = ["envoy_binary_test.sh"], 59 | data = [":envoy"], 60 | ) 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Envoy filter example 2 | 3 | This project demonstrates the linking of additional filters with the Envoy binary. 4 | A new filter `echo2` is introduced, identical modulo renaming to the existing 5 | [`echo`](https://github.com/envoyproxy/envoy/blob/master/source/extensions/filters/network/echo/echo.h) 6 | filter. Integration tests demonstrating the filter's end-to-end behavior are 7 | also provided. 8 | 9 | For an example of additional HTTP filters, see [here](http-filter-example). 10 | 11 | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/envoyproxy/envoy-filter-example/badge)](https://securityscorecards.dev/viewer/?uri=github.com/envoyproxy/envoy-filter-example) 12 | 13 | ## Building 14 | 15 | To build the Envoy static binary: 16 | 17 | 1. `git submodule update --init` 18 | 2. `bazel build //:envoy` 19 | 20 | ## Testing 21 | 22 | To run the `echo2` integration test: 23 | 24 | `bazel test //:echo2_integration_test` 25 | 26 | To run the regular Envoy tests from this project: 27 | 28 | `bazel test @envoy//test/...` 29 | 30 | ## How it works 31 | 32 | The [Envoy repository](https://github.com/envoyproxy/envoy/) is provided as a submodule. 33 | The [`WORKSPACE`](WORKSPACE) file maps the `@envoy` repository to this local path. 34 | 35 | The [`BUILD`](BUILD) file introduces a new Envoy static binary target, `envoy`, 36 | that links together the new filter and `@envoy//source/exe:envoy_main_entry_lib`. The 37 | `echo2` filter registers itself during the static initialization phase of the 38 | Envoy binary as a new filter. 39 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "envoy_filter_example") 2 | 3 | local_repository( 4 | name = "envoy", 5 | path = "{ENVOY_SRCDIR}", 6 | ) 7 | 8 | load("@envoy//bazel:api_binding.bzl", "envoy_api_binding") 9 | 10 | envoy_api_binding() 11 | 12 | load("@envoy//bazel:api_repositories.bzl", "envoy_api_dependencies") 13 | 14 | envoy_api_dependencies() 15 | 16 | load("@envoy//bazel:repositories.bzl", "envoy_dependencies") 17 | 18 | envoy_dependencies() 19 | 20 | load("@envoy//bazel:repositories_extra.bzl", "envoy_dependencies_extra") 21 | 22 | envoy_dependencies_extra() 23 | 24 | load("@envoy//bazel:python_dependencies.bzl", "envoy_python_dependencies") 25 | 26 | envoy_python_dependencies() 27 | 28 | load("@envoy//bazel:dependency_imports.bzl", "envoy_dependency_imports") 29 | 30 | envoy_dependency_imports() 31 | -------------------------------------------------------------------------------- /bazel/get_workspace_status: -------------------------------------------------------------------------------- 1 | ../envoy/bazel/get_workspace_status -------------------------------------------------------------------------------- /ci/do_ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | export PATH=/usr/lib/llvm-8/bin:$PATH 4 | export CC=clang 5 | export CXX=clang++ 6 | export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-7/bin/llvm-symbolizer 7 | echo "$CC/$CXX toolchain configured" 8 | 9 | if [[ -f "${HOME:-/root}/.gitconfig" ]]; then 10 | mv "${HOME:-/root}/.gitconfig" "${HOME:-/root}/.gitconfig_save" 11 | fi 12 | 13 | function do_build () { 14 | bazel build --verbose_failures=true //:envoy 15 | } 16 | 17 | function do_test() { 18 | bazel test --test_output=all --test_env=ENVOY_IP_TEST_VERSIONS=v4only \ 19 | //:echo2_integration_test 20 | } 21 | 22 | case "$1" in 23 | build) 24 | do_build 25 | ;; 26 | test) 27 | do_test 28 | ;; 29 | *) 30 | echo "must be one of [build,test]" 31 | exit 1 32 | ;; 33 | esac 34 | -------------------------------------------------------------------------------- /ci/sync_envoy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Updating Submodule..." 6 | # Update submodule to latest Envoy SHA 7 | ENVOY_SHA=$(git -C "$ENVOY_SRC_DIR" rev-parse HEAD) 8 | CURRENT_SHA="$(git ls-files -s envoy | cut -d' ' -f2)" 9 | COMMITTER_NAME="${GITHUB_NAME:-}" 10 | COMMITTER_EMAIL="${GITHUB_EMAIL:-}" 11 | 12 | 13 | if [[ "$CURRENT_SHA" == "$ENVOY_SHA" ]]; then 14 | echo "Submodule already up to date (${ENVOY_SHA})" 15 | exit 0 16 | fi 17 | 18 | git submodule update --init 19 | git -C envoy/ checkout "$ENVOY_SHA" 20 | 21 | echo "Updating Workspace file." 22 | sed -e "s|{ENVOY_SRC_DIR}|envoy|" "${ENVOY_SRC_DIR}/ci/WORKSPACE.filter.example" > "WORKSPACE" 23 | 24 | echo "Committing, and Pushing..." 25 | git config --global user.email "$COMMITTER_EMAIL" 26 | git config --global user.name "$COMMITTER_NAME" 27 | git commit -a -m "Update Envoy submodule to $ENVOY_SHA" 28 | git push origin main 29 | echo "Done" 30 | -------------------------------------------------------------------------------- /echo2.cc: -------------------------------------------------------------------------------- 1 | #include "echo2.h" 2 | 3 | #include "envoy/buffer/buffer.h" 4 | #include "envoy/network/connection.h" 5 | 6 | #include "source/common/common/assert.h" 7 | 8 | namespace Envoy { 9 | namespace Filter { 10 | 11 | Network::FilterStatus Echo2::onData(Buffer::Instance& data, bool) { 12 | ENVOY_CONN_LOG(trace, "echo: got {} bytes", read_callbacks_->connection(), data.length()); 13 | read_callbacks_->connection().write(data, false); 14 | return Network::FilterStatus::StopIteration; 15 | } 16 | 17 | } // namespace Filter 18 | } // namespace Envoy 19 | -------------------------------------------------------------------------------- /echo2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "envoy/network/filter.h" 4 | 5 | #include "source/common/common/logger.h" 6 | 7 | namespace Envoy { 8 | namespace Filter { 9 | 10 | /** 11 | * Implementation of a basic echo filter. 12 | */ 13 | class Echo2 : public Network::ReadFilter, Logger::Loggable { 14 | public: 15 | // Network::ReadFilter 16 | Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; 17 | Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } 18 | void initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) override { 19 | read_callbacks_ = &callbacks; 20 | } 21 | 22 | private: 23 | Network::ReadFilterCallbacks* read_callbacks_{}; 24 | }; 25 | 26 | } // namespace Filter 27 | } // namespace Envoy 28 | -------------------------------------------------------------------------------- /echo2_config.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "echo2.h" 4 | 5 | #include "envoy/registry/registry.h" 6 | #include "envoy/server/filter_config.h" 7 | 8 | namespace Envoy { 9 | namespace Server { 10 | namespace Configuration { 11 | 12 | /** 13 | * Config registration for the echo2 filter. @see NamedNetworkFilterConfigFactory. 14 | */ 15 | class Echo2ConfigFactory : public NamedNetworkFilterConfigFactory { 16 | public: 17 | Network::FilterFactoryCb createFilterFactoryFromProto(const Protobuf::Message&, 18 | FactoryContext&) override { 19 | return [](Network::FilterManager& filter_manager) -> void { 20 | filter_manager.addReadFilter(Network::ReadFilterSharedPtr{new Filter::Echo2()}); 21 | }; 22 | } 23 | 24 | ProtobufTypes::MessagePtr createEmptyConfigProto() override { 25 | return ProtobufTypes::MessagePtr{new Envoy::ProtobufWkt::Struct()}; 26 | } 27 | 28 | std::string name() const override { return "echo2"; } 29 | 30 | bool isTerminalFilterByProto(const Protobuf::Message&, ServerFactoryContext&) override { return true; } 31 | }; 32 | 33 | /** 34 | * Static registration for the echo2 filter. @see RegisterFactory. 35 | */ 36 | static Registry::RegisterFactory registered_; 37 | 38 | } // namespace Configuration 39 | } // namespace Server 40 | } // namespace Envoy 41 | -------------------------------------------------------------------------------- /echo2_integration_test.cc: -------------------------------------------------------------------------------- 1 | #include "test/integration/integration.h" 2 | #include "test/integration/utility.h" 3 | 4 | namespace Envoy { 5 | class Echo2IntegrationTest : public BaseIntegrationTest, 6 | public testing::TestWithParam { 7 | 8 | std::string echoConfig() { 9 | return TestEnvironment::readFileToStringForTest( 10 | TestEnvironment::runfilesPath("echo2_server.yaml", "envoy_filter_example")); 11 | } 12 | 13 | public: 14 | Echo2IntegrationTest() : BaseIntegrationTest(GetParam(), echoConfig()) {} 15 | /** 16 | * Initializer for an individual integration test. 17 | */ 18 | void SetUp() override { BaseIntegrationTest::initialize(); } 19 | 20 | /** 21 | * Destructor for an individual integration test. 22 | */ 23 | void TearDown() override { 24 | test_server_.reset(); 25 | fake_upstreams_.clear(); 26 | } 27 | }; 28 | 29 | INSTANTIATE_TEST_SUITE_P(IpVersions, Echo2IntegrationTest, 30 | testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); 31 | 32 | TEST_P(Echo2IntegrationTest, Echo) { 33 | std::string response; 34 | auto connection = createConnectionDriver( 35 | lookupPort("listener_0"), "hello", 36 | [&](Network::ClientConnection& conn, const Buffer::Instance& data) -> void { 37 | response.append(data.toString()); 38 | conn.close(Network::ConnectionCloseType::FlushWrite); 39 | }); 40 | 41 | connection->run(); 42 | EXPECT_EQ("hello", response); 43 | } 44 | } // namespace Envoy 45 | -------------------------------------------------------------------------------- /echo2_server.yaml: -------------------------------------------------------------------------------- 1 | admin: 2 | access_log_path: /dev/null 3 | address: 4 | socket_address: 5 | address: 127.0.0.1 6 | port_value: 0 7 | static_resources: 8 | clusters: 9 | name: cluster_0 10 | connect_timeout: 0.25s 11 | load_assignment: 12 | cluster_name: cluster_0 13 | endpoints: 14 | - lb_endpoints: 15 | - endpoint: 16 | address: 17 | socket_address: 18 | address: 127.0.0.1 19 | port_value: 0 20 | listeners: 21 | name: listener_0 22 | address: 23 | socket_address: 24 | address: 127.0.0.1 25 | port_value: 0 26 | filter_chains: 27 | - filters: 28 | - name: echo2 29 | typed_config: 30 | "@type": type.googleapis.com/google.protobuf.Struct 31 | -------------------------------------------------------------------------------- /envoy_binary_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | 4 | set -e 5 | 6 | # Just test that the binary was produced and can be executed. 7 | # envoy --help will give a success return code if working. 8 | envoy --help 9 | 10 | echo "PASS" 11 | -------------------------------------------------------------------------------- /http-filter-example/BUILD: -------------------------------------------------------------------------------- 1 | load( 2 | "@envoy//bazel:envoy_build_system.bzl", 3 | "envoy_cc_binary", 4 | "envoy_cc_library", 5 | "envoy_cc_test", 6 | ) 7 | load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") 8 | 9 | package(default_visibility = ["//visibility:public"]) 10 | 11 | envoy_cc_binary( 12 | name = "envoy", 13 | repository = "@envoy", 14 | deps = [ 15 | ":http_filter_config", 16 | "@envoy//source/exe:envoy_main_entry_lib", 17 | ], 18 | ) 19 | 20 | api_proto_package() 21 | 22 | envoy_cc_library( 23 | name = "http_filter_lib", 24 | srcs = ["http_filter.cc"], 25 | hdrs = ["http_filter.h"], 26 | repository = "@envoy", 27 | deps = [ 28 | ":pkg_cc_proto", 29 | "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", 30 | ], 31 | ) 32 | 33 | envoy_cc_library( 34 | name = "http_filter_config", 35 | srcs = ["http_filter_config.cc"], 36 | repository = "@envoy", 37 | deps = [ 38 | ":http_filter_lib", 39 | "@envoy//envoy/server:filter_config_interface", 40 | ], 41 | ) 42 | 43 | envoy_cc_test( 44 | name = "http_filter_integration_test", 45 | srcs = ["http_filter_integration_test.cc"], 46 | repository = "@envoy", 47 | deps = [ 48 | ":http_filter_config", 49 | "@envoy//test/integration:http_integration_lib", 50 | ], 51 | ) 52 | -------------------------------------------------------------------------------- /http-filter-example/README.md: -------------------------------------------------------------------------------- 1 | # Envoy filter example 2 | 3 | This project demonstrates the linking of additional HTTP filters with the Envoy binary. 4 | A new filter `sample` which adds a HTTP header is introduced. 5 | Integration tests demonstrating the filter's end-to-end behavior are 6 | also provided. 7 | 8 | ## Building 9 | 10 | To build the Envoy static binary: 11 | 12 | 1. `git submodule update --init` 13 | 2. `bazel build //http-filter-example:envoy` 14 | 15 | ## Testing 16 | 17 | To run the `sample` integration test: 18 | 19 | `bazel test //http-filter-example:http_filter_integration_test` 20 | 21 | ## How it works 22 | 23 | See the [network filter example](../README.md#how-it-works). 24 | 25 | ## How to write and use an HTTP filter 26 | 27 | - The main task is to write a class that implements the interface 28 | [`Envoy::Http::StreamDecoderFilter`][StreamDecoderFilter] as in 29 | [`http_filter.h`](http_filter.h) and [`http_filter.cc`](http_filter.cc), 30 | which contains functions that handle http headers, data, and trailers. 31 | Note that this is an example of decoder filters, 32 | and to write encoder filters or decoder/encoder filters 33 | you need to implement 34 | [`Envoy::Http::StreamEncoderFilter`][StreamEncoderFilter] or 35 | [`Envoy::Http::StreamFilter`][StreamFilter] instead. 36 | - You also need a class that implements 37 | `Envoy::Server::Configuration::NamedHttpFilterConfigFactory` 38 | to enable the Envoy binary to find your filter, 39 | as in [`http_filter_config.cc`](http_filter_config.cc). 40 | It should be linked to the Envoy binary by modifying [`BUILD`][BUILD] file. 41 | - Finally, you need to modify the Envoy config file to add your filter to the 42 | filter chain for a particular HTTP route configuration. For instance, if you 43 | wanted to change [the front-proxy example][front-envoy.yaml] to chain our 44 | `sample` filter, you'd need to modify its config to look like 45 | 46 | ```yaml 47 | http_filters: 48 | - name: sample # before envoy.router because order matters! 49 | typed_config: 50 | "@type": type.googleapis.com/sample.Decoder 51 | key: via 52 | val: sample-filter 53 | - name: envoy.router 54 | typed_config: {} 55 | ``` 56 | 57 | 58 | [StreamDecoderFilter]: https://github.com/envoyproxy/envoy/blob/b2610c84aeb1f75c804d67effcb40592d790e0f1/include/envoy/http/filter.h#L300 59 | [StreamEncoderFilter]: https://github.com/envoyproxy/envoy/blob/b2610c84aeb1f75c804d67effcb40592d790e0f1/include/envoy/http/filter.h#L413 60 | [StreamFilter]: https://github.com/envoyproxy/envoy/blob/b2610c84aeb1f75c804d67effcb40592d790e0f1/include/envoy/http/filter.h#L462 61 | [BUILD]: https://github.com/envoyproxy/envoy-filter-example/blob/d76d3096c4cbd647d26b44b3f801c3afbc81d3e2/http-filter-example/BUILD#L15-L18 62 | [front-envoy.yaml]: https://github.com/envoyproxy/envoy/blob/b2610c84aeb1f75c804d67effcb40592d790e0f1/examples/front-proxy/front-envoy.yaml#L28 63 | -------------------------------------------------------------------------------- /http-filter-example/http_filter.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "http_filter.h" 4 | 5 | #include "envoy/server/filter_config.h" 6 | 7 | namespace Envoy { 8 | namespace Http { 9 | 10 | HttpSampleDecoderFilterConfig::HttpSampleDecoderFilterConfig( 11 | const sample::Decoder& proto_config) 12 | : key_(proto_config.key()), val_(proto_config.val()) {} 13 | 14 | HttpSampleDecoderFilter::HttpSampleDecoderFilter(HttpSampleDecoderFilterConfigSharedPtr config) 15 | : config_(config) {} 16 | 17 | HttpSampleDecoderFilter::~HttpSampleDecoderFilter() {} 18 | 19 | void HttpSampleDecoderFilter::onDestroy() {} 20 | 21 | const LowerCaseString HttpSampleDecoderFilter::headerKey() const { 22 | return LowerCaseString(config_->key()); 23 | } 24 | 25 | const std::string HttpSampleDecoderFilter::headerValue() const { 26 | return config_->val(); 27 | } 28 | 29 | FilterHeadersStatus HttpSampleDecoderFilter::decodeHeaders(RequestHeaderMap& headers, bool) { 30 | // add a header 31 | headers.addCopy(headerKey(), headerValue()); 32 | 33 | return FilterHeadersStatus::Continue; 34 | } 35 | 36 | FilterDataStatus HttpSampleDecoderFilter::decodeData(Buffer::Instance&, bool) { 37 | return FilterDataStatus::Continue; 38 | } 39 | 40 | void HttpSampleDecoderFilter::setDecoderFilterCallbacks(StreamDecoderFilterCallbacks& callbacks) { 41 | decoder_callbacks_ = &callbacks; 42 | } 43 | 44 | } // namespace Http 45 | } // namespace Envoy 46 | -------------------------------------------------------------------------------- /http-filter-example/http_filter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "source/extensions/filters/http/common/pass_through_filter.h" 6 | 7 | #include "http-filter-example/http_filter.pb.h" 8 | 9 | namespace Envoy { 10 | namespace Http { 11 | 12 | class HttpSampleDecoderFilterConfig { 13 | public: 14 | HttpSampleDecoderFilterConfig(const sample::Decoder& proto_config); 15 | 16 | const std::string& key() const { return key_; } 17 | const std::string& val() const { return val_; } 18 | 19 | private: 20 | const std::string key_; 21 | const std::string val_; 22 | }; 23 | 24 | using HttpSampleDecoderFilterConfigSharedPtr = std::shared_ptr; 25 | 26 | class HttpSampleDecoderFilter : public PassThroughDecoderFilter { 27 | public: 28 | HttpSampleDecoderFilter(HttpSampleDecoderFilterConfigSharedPtr); 29 | ~HttpSampleDecoderFilter(); 30 | 31 | // Http::StreamFilterBase 32 | void onDestroy() override; 33 | 34 | // Http::StreamDecoderFilter 35 | FilterHeadersStatus decodeHeaders(RequestHeaderMap&, bool) override; 36 | FilterDataStatus decodeData(Buffer::Instance&, bool) override; 37 | void setDecoderFilterCallbacks(StreamDecoderFilterCallbacks&) override; 38 | 39 | private: 40 | const HttpSampleDecoderFilterConfigSharedPtr config_; 41 | StreamDecoderFilterCallbacks* decoder_callbacks_; 42 | 43 | const LowerCaseString headerKey() const; 44 | const std::string headerValue() const; 45 | }; 46 | 47 | } // namespace Http 48 | } // namespace Envoy 49 | -------------------------------------------------------------------------------- /http-filter-example/http_filter.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package sample; 4 | 5 | import "validate/validate.proto"; 6 | 7 | message Decoder { 8 | string key = 1 [(validate.rules).string.min_bytes = 1]; 9 | string val = 2 [(validate.rules).string.min_bytes = 1]; 10 | } 11 | -------------------------------------------------------------------------------- /http-filter-example/http_filter_config.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "envoy/registry/registry.h" 4 | #include "envoy/server/filter_config.h" 5 | 6 | #include "http-filter-example/http_filter.pb.h" 7 | #include "http-filter-example/http_filter.pb.validate.h" 8 | #include "http_filter.h" 9 | 10 | namespace Envoy { 11 | namespace Server { 12 | namespace Configuration { 13 | 14 | class HttpSampleDecoderFilterConfigFactory : public NamedHttpFilterConfigFactory { 15 | public: 16 | Http::FilterFactoryCb createFilterFactoryFromProto(const Protobuf::Message& proto_config, 17 | const std::string&, 18 | FactoryContext& context) override { 19 | 20 | return createFilter(Envoy::MessageUtil::downcastAndValidate( 21 | proto_config, context.messageValidationVisitor()), 22 | context); 23 | } 24 | 25 | /** 26 | * Return the Protobuf Message that represents your config incase you have config proto 27 | */ 28 | ProtobufTypes::MessagePtr createEmptyConfigProto() override { 29 | return ProtobufTypes::MessagePtr{new sample::Decoder()}; 30 | } 31 | 32 | std::string name() const override { return "sample"; } 33 | 34 | private: 35 | Http::FilterFactoryCb createFilter(const sample::Decoder& proto_config, FactoryContext&) { 36 | Http::HttpSampleDecoderFilterConfigSharedPtr config = 37 | std::make_shared( 38 | Http::HttpSampleDecoderFilterConfig(proto_config)); 39 | 40 | return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { 41 | auto filter = new Http::HttpSampleDecoderFilter(config); 42 | callbacks.addStreamDecoderFilter(Http::StreamDecoderFilterSharedPtr{filter}); 43 | }; 44 | } 45 | }; 46 | 47 | /** 48 | * Static registration for this sample filter. @see RegisterFactory. 49 | */ 50 | static Registry::RegisterFactory 51 | register_; 52 | 53 | } // namespace Configuration 54 | } // namespace Server 55 | } // namespace Envoy 56 | -------------------------------------------------------------------------------- /http-filter-example/http_filter_integration_test.cc: -------------------------------------------------------------------------------- 1 | #include "test/integration/http_integration.h" 2 | 3 | namespace Envoy { 4 | class HttpFilterSampleIntegrationTest : public HttpIntegrationTest, 5 | public testing::TestWithParam { 6 | public: 7 | HttpFilterSampleIntegrationTest() 8 | : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, GetParam()) {} 9 | /** 10 | * Initializer for an individual integration test. 11 | */ 12 | void SetUp() override { initialize(); } 13 | 14 | void initialize() override { 15 | config_helper_.prependFilter( 16 | "{ name: sample, typed_config: { \"@type\": type.googleapis.com/sample.Decoder, key: via, " 17 | "val: sample-filter } }"); 18 | HttpIntegrationTest::initialize(); 19 | } 20 | }; 21 | 22 | INSTANTIATE_TEST_SUITE_P(IpVersions, HttpFilterSampleIntegrationTest, 23 | testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); 24 | 25 | TEST_P(HttpFilterSampleIntegrationTest, Test1) { 26 | Http::TestRequestHeaderMapImpl headers{ 27 | {":method", "GET"}, {":path", "/"}, {":authority", "host"}}; 28 | Http::TestRequestHeaderMapImpl response_headers{ 29 | {":status", "200"}}; 30 | 31 | IntegrationCodecClientPtr codec_client; 32 | FakeHttpConnectionPtr fake_upstream_connection; 33 | FakeStreamPtr request_stream; 34 | 35 | codec_client = makeHttpConnection(lookupPort("http")); 36 | auto response = codec_client->makeHeaderOnlyRequest(headers); 37 | ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection)); 38 | ASSERT_TRUE(fake_upstream_connection->waitForNewStream(*dispatcher_, request_stream)); 39 | ASSERT_TRUE(request_stream->waitForEndStream(*dispatcher_)); 40 | request_stream->encodeHeaders(response_headers, true); 41 | ASSERT_TRUE(response->waitForEndStream()); 42 | 43 | EXPECT_EQ( 44 | "sample-filter", 45 | request_stream->headers().get(Http::LowerCaseString("via"))[0]->value().getStringView()); 46 | 47 | codec_client->close(); 48 | } 49 | } // namespace Envoy 50 | --------------------------------------------------------------------------------