Wasm C++ filter

This sandbox demonstrates a basic Envoy Wasm filter written in C++ which injects content into the body of an HTTP response, and adds and updates some headers.

It also takes you through the steps required to build your own C++ Wasm filter, and run it with Envoy.

Step 1: Start all of our containers

First lets start the containers - an Envoy proxy which uses a Wasm Filter, and a backend which echos back our request. The Envoy configuration exposes two listeners, the first one listens in port 8000 which contains the wasm filter in the listener filter chain. The second one listens in port 8001 routing to a cluster containing the wasm filter in the cluster filter chain.

Change to the examples/wasm-cc folder in the Envoy repo, and start the composition:

$ pwd
envoy/examples/wasm-cc
$ docker compose pull
$ docker compose up --build -d
$ docker compose ps

    Name                     Command                State             Ports
-----------------------------------------------------------------------------------------------
wasm_proxy_1         /docker-entrypoint.sh /usr ... Up      10000/tcp, 0.0.0.0:8000->8000/tcp, 0.0.0.0:8001->8001/tcp
wasm_web_service_1   node ./index.js                Up

Step 2: Check web response

The Wasm filter should inject “Hello, world” at the end of the response body when you make a request to the proxy.

$ curl -s http://localhost:8000 | grep "Hello, world"
}Hello, world

The filter also sets the content-type header to text/plain, and adds a custom x-wasm-custom header.

$ curl -v http://localhost:8000 | grep "content-type: "
content-type: text/plain; charset=utf-8

$ curl -v http://localhost:8000 | grep "x-wasm-custom: "
x-wasm-custom: FOO

Similar outputs could be obtained in the second listener routing to the cluster with upstream wasm filter.

$ curl -s http://localhost:8001 | grep "Hello, world"
}Hello, world

$ curl -v http://localhost:8001 | grep "content-type: "
content-type: text/plain; charset=utf-8

$ curl -v http://localhost:8001 | grep "x-wasm-custom: "
x-wasm-custom: FOO

Step 3: Compile the updated filter

There are two source code files provided for the Wasm filter.

envoy_filter_http_wasm_example.cc provides the source code for the included prebuilt binary.

envoy_filter_http_wasm_updated_example.cc makes a few changes to the original.

The following diff shows the changes that have been made:

--- /tmp/tmpexbkc8ft/generated/rst/start/sandboxes/_include/wasm-cc/envoy_filter_http_wasm_example.cc
+++ /tmp/tmpexbkc8ft/generated/rst/start/sandboxes/_include/wasm-cc/envoy_filter_http_wasm_updated_example.cc
@@ -65,8 +65,8 @@
   for (auto& p : pairs) {
     LOG_INFO(std::string(p.first) + std::string(" -> ") + std::string(p.second));
   }
-  addResponseHeader("X-Wasm-custom", "FOO");
-  replaceResponseHeader("content-type", "text/plain; charset=utf-8");
+  addResponseHeader("X-Wasm-custom", "BAR");
+  replaceResponseHeader("content-type", "text/html; charset=utf-8");
   removeResponseHeader("content-length");
   return FilterHeadersStatus::Continue;
 }
@@ -78,9 +78,9 @@
   return FilterDataStatus::Continue;
 }
 
-FilterDataStatus ExampleContext::onResponseBody(size_t body_buffer_length,
+FilterDataStatus ExampleContext::onResponseBody(size_t /* body_buffer_length */,
                                                 bool /* end_of_stream */) {
-  setBuffer(WasmBufferType::HttpResponseBody, 0, body_buffer_length, "Hello, world\n");
+  setBuffer(WasmBufferType::HttpResponseBody, 0, 17, "Hello, Wasm world");
   return FilterDataStatus::Continue;
 }
 

Warning

These instructions for compiling an updated Wasm binary use the envoyproxy/envoy-build-ubuntu image. You will need 4-5GB of disk space to accommodate this image.

Export UID from your host system. This will ensure that the binary created inside the build container has the same permissions as your host user:

$ export UID

Note

The build composition is designed to work in a similar way to the ./ci/run_envoy_docker.sh command in the Envoy repo.

Bazel temporary artefacts are created in /tmp/envoy-docker-build with the uid taken from the UID env var.

Stop the proxy server and compile the Wasm binary with the updated code:

$ docker compose stop proxy
$ docker compose -f docker-compose-wasm.yaml up --remove-orphans wasm_compile_update

The compiled binary should now be in the lib folder.

$ ls -l lib
total 120
-r-xr-xr-x 1 root root 59641 Oct 20 00:00 envoy_filter_http_wasm_example.wasm
-r-xr-xr-x 1 root root 59653 Oct 20 10:16 envoy_filter_http_wasm_updated_example.wasm

Step 4: Edit the Dockerfile and restart the proxy

Edit the Dockerfile-proxy recipe provided in the example to use the updated binary you created in step 3.

Find the COPY line that adds the Wasm binary to the image:

1FROM envoyproxy/envoy:dev
2COPY ./envoy.yaml /etc/envoy.yaml
3COPY ./lib/envoy_filter_http_wasm_example.wasm /lib/envoy_filter_http_wasm_example.wasm
4RUN chmod go+r /etc/envoy.yaml /lib/envoy_filter_http_wasm_example.wasm
5CMD ["/usr/local/bin/envoy", "-c", "/etc/envoy.yaml", "--service-cluster", "proxy"]

Replace this line with the following:

COPY ./lib/envoy_filter_http_wasm_updated_example.wasm /lib/envoy_filter_http_wasm_example.wasm

Now, rebuild and start the proxy container.

$ docker compose up --build -d proxy

Step 5: Check the proxy has been updated

The Wasm filter should instead inject “Hello, Wasm world” at the end of the response body.

$ curl -s http://localhost:8000 | grep "Hello, Wasm world"
}Hello, Wasm world

The content-type and x-wasm-custom headers should also have changed

$ curl -v http://localhost:8000 | grep "content-type: "
content-type: text/html; charset=utf-8

$ curl -v http://localhost:8000 | grep "x-wasm-custom: "
x-wasm-custom: BAR

See also

Envoy Wasm filter

Further information about the Envoy Wasm filter.

Envoy Wasm API(V3)

The Envoy Wasm API - version 3.

Proxy Wasm C++ SDK

WebAssembly for proxies (C++ SDK)