Code samples and tools for developing Google Cloud Service Extensions WebAssembly (wasm) plugins.
Each sample/recipe has an example plugin written in Rust and C++, and an accompanying unit test that verifies both.
We recommend the following process:
- Write a wasm plugin using the samples and SDKs as a starting point: C++, Go, Rust. See also the best practices.
- Build the plugin.
- Test and benchmark the plugin.
All sample plugins can be built using Bazel. If you prefer to use language-native toolchains, see the SDK-specific instructions:
All languages also support Bazel; we recommend the Bazelisk wrapper which provides support for multiple Bazel versions:
# A target is defined using BUILD files. Dependencies are in WORKSPACE.
$ bazelisk build <target>
# For example, to build a sample in C++ and Rust, from the plugins/ directory:
$ bazelisk build //samples/add_header:plugin_cpp.wasm
$ bazelisk build //samples/add_header:plugin_rust.wasmC++ builds may require a specific toolchain: --config=clang or --config=gcc.
- Write a plugin test file to specify the plugin's functional expectations. This file can either be in text proto format (example) or YAML (example). Consult the plugin tester proto API as needed.
- Add
benchmark: trueto tests that exemplify common wasm operations (example). - Run + Test + Benchmark your wasm plugin as follows!
docker run -it -v $(pwd):/mnt \
us-docker.pkg.dev/service-extensions-samples/plugins/wasm-tester:main \
--proto /mnt/local/path/to/tests.textpb \
--plugin /mnt/local/path/to/plugin.wasmTips:
- When benchmarking and publishing, compile a release (optimized) wasm build.
- Try sending empty or invalid input. Verify your plugin doesn't crash.
- To see plugin-emitted logs on the console, add
--logfile=/dev/stdout. - To see a trace of logs and wasm ABI calls, add
--loglevel=TRACE. - To disable benchmarking for faster iteration, add
--nobench. - To disable unit testing for cleaner output, add
--notest. - To optionally specify plugin config data, add
--config=<path>. - To test memory with high concurrency, add
--num_additional_streams=500.
You can also run tests using Bazel. This is much slower the first time, because this builds both the tester and the V8 runtime from scratch. Use the Docker command above for a better experience.
bazelisk test --config=bench --test_output=all //samples/...The samples folder contains Samples & Recipes to use as a reference for your own plugin. Extend them to fit your particular use case.
- Testing examples: A demonstration of our test framework capabilities (sending inputs and checking results).
- Log each Wasm call: Don't change anything about the traffic (noop plugin). Log each wasm invocation, including lifecycle callbacks.
- Hello World: Immediately response with "Hello World" upon request.
- Add HTTP request headers: Add a header on the client request path.
- Add HTTP response headers: Add a header on the server response path. Also check for existing headers.
- Plugin config with a list of tokens to deny: Deny a request whenever it contains a known bad token. Bad tokens are loaded at plugin initialization time from plugin configuration.
- Log the value of a query parameter: Emit a custom variable to Cloud Logging. Demonstrate a standard way to parse query string values from the request.
- Set or update query parameter: Change the path, and specifically query string values.
- Rewrite the path using regex: Remove a piece of the URI using regex replacement. Demonstrate a standard way to use regular expressions, compiling them at plugin initialization.
- Overwrite HTTP request & response headers: Overwrite a header on both the client request and server response paths.
- Normalize a HTTP header on request: Creates a new HTTP header (client-device-type) to shard requests based on device according to the existence of HTTP Client Hints or User-Agent header values.
- Block request with particular header: Check whether the client's Referer header matches an expected domain. If not, generate a 403 Forbidden response.
- Overwrite origin response error code: Overwrites error code served from origin from 5xx error to 4xx error class.
- Perform a HTTP redirect: Redirect a given URL to another URL.
- Set a cookie for a given client request: Set cookie on HTTP response for a particular client request.
- A/B decisioning based on query param: Showcase A/B testing in action, 50% chance a user is served file A and 50% chance they are served file B.
- Custom error page: For a certain class of origin errors, redirect to a custom error page hosted on GCS.
- Validate client JWT for authorization: Ensures user authentication by verifying an RS256-signed JWT token in the query string and subsequently removing it.
- Check for PII on response: Checks the response HTTP headers and body for the presence of credit card numbers. If found, the initial numbers will be masked.
- Validate client token on query string using HMAC: Check the client request URL for a valid token signed using HMAC.
- Validate client token using HMAC with cookie: Check the client request for a valid token signed using HMAC provided via a cookie.
- Rewrite domains in html response body: Parse
html in response body chunks and replace insances of "foo.com" with
"bar.com" in
<a href=***>. - Add script in html response body: Inject a
<script>at the start of<head>in response body. - Enable reCAPTCHA challenge on response body: Enable reCAPTCHA challenge on response body by injecting script into head tag. Warning: This is not a replacement for official reCAPTCHA documentation.
Service Extension plugins are compiled against the ProxyWasm ABI, described here: https://github.com/proxy-wasm/spec/tree/master
Service Extension plugins currently support a subset of the ProxyWasm spec. Support will grow over time. The current feature set includes:
- Root context lifecycle callbacks (host -> wasm)
- on_context_create
- on_vm_start
- on_configure
- on_done
- on_delete
- Stream context lifecycle callbacks (host -> wasm)
- on_context_create
- on_done
- on_delete
- Stream context HTTP callbacks (host -> wasm)
- on_request_headers
- on_request_body
- on_request_trailers
- on_response_headers
- on_response_body
- on_response_trailers
- Stream context HTTP hostcalls (wasm -> host)
- send_local_response
- get_header_map_value, add_header_map_value, replace_header_map_value, remove_header_map_value
- get_header_map_pairs, set_header_map_pairs
- get_header_map_size
- Other hostcalls (wasm -> host)
- log
- get_current_time_nanoseconds (frozen per stream)
- get_property ("plugin_root_id" only)
- get_buffer_status, get_buffer_bytes, set_buffer_bytes (PluginConfiguration, HttpRequestBody, HttpResponseBody)
In support of unit testing, this repo contains an HttpTest fixture with a
TestWasm host implementation and TestHttpContext stream handler. These
minimal implementations loosely match the GCP Service Extension execution
environment. The contexts implement the ABI / feature set described above
(mainly HTTP headers and logging), but often in a simple way (behaviors may not
match GCP exactly).
This project leverages crate_universe to integrate Cargo with Bazel. In order to add new Rust library dependencies:
- Edit dependencies in Cargo.toml
- Regenerate Bazel targets:
$ CARGO_BAZEL_REPIN=1 bazelisk sync --only=crate_index - Reference libraries as
@crate_index//:<target>
This project leverages gazelle to integrate Go mod with Bazel. In order to add new Go dependencies:
- Instruct Gazelle to generate a repo rule for your dependency using the Go
import URL, for example:
$ bazel run //:gazelle -- update-repos github.com/proxy-wasm/proxy-wasm-go-sdk - Add a import statement to the Go source file
import "github.com/proxy-wasm/proxy-wasm-go-sdk/types". - Run
$ bazel run //:gazelleto add the autogenerated repo rule dependency to the Go file's build file