From f48dfbfa31bf7c04529a7d98c0649166efac1a92 Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Sun, 9 Jul 2023 02:09:11 +0000 Subject: [PATCH 1/9] Move current README.md to docs/building.md A subsequent commit will add a short top-level README.md that references the build instructions as well as API documentation. Signed-off-by: Michael Warres --- README.md => docs/building.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.md => docs/building.md (100%) diff --git a/README.md b/docs/building.md similarity index 100% rename from README.md rename to docs/building.md From b3b6cd4a547ca803a4603591e1626e1435bc2f79 Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Sun, 23 Jul 2023 19:22:10 +0000 Subject: [PATCH 2/9] Add new README and API overview pages. Signed-off-by: Michael Warres --- README.md | 35 ++ docs/api_overview.md | 454 ++++++++++++++++ docs/building.md | 67 ++- docs/wasm_context.svg | 1 - docs/wasm_filter.md | 1180 ----------------------------------------- 5 files changed, 527 insertions(+), 1210 deletions(-) create mode 100644 README.md create mode 100644 docs/api_overview.md delete mode 100644 docs/wasm_context.svg delete mode 100644 docs/wasm_filter.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f985658 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Proxy-Wasm C++ SDK + +[![Build Status][build-badge]][build-link] +[![Apache 2.0 License][license-badge]][license-link] + +Proxy-Wasm is a specification and supporting framework for using +[WebAssembly](https://webassembly.org) (Wasm) to extend the functionality of +network proxies. It enables developers to write custom logic (plugins) that are +compiled to Wasm modules and then loaded and executed by the proxy. + +Proxy-Wasm consists of multiple parts: + +* An [ABI](https://github.com/proxy-wasm/spec) that specifies the low-level + interface between network proxies and Wasm virtual machines that run the + plugins. +* [Host implementations](https://github.com/proxy-wasm/spec#host-environments) + of the ABI, provided by network proxies. +* [Language-specific SDKs](https://github.com/proxy-wasm/spec#sdks) that layer + on top of the ABI, providing a more natural and programmer-friendly API for + invoking and implementing Proxy-Wasm functions and callbacks. + +This repository provides the C++ SDK. + +## Getting started + +* Read the [API overview](docs/api_overview.md) to learn about [Proxy-Wasm + concepts] and how they are represented in the C++ SDK. +* View an [example plugin](example/http_wasm_example.cc). +* Refer to [API documentation](docs/api_overview.md#codemap). +* [Build](docs/building.md) plugin code. + +[build-badge]: https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/workflows/C++/badge.svg?branch=master +[build-link]: https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/actions?query=workflow%3AC%2B%2B+branch%3Amaster +[license-badge]: https://img.shields.io/github/license/proxy-wasm/proxy-wasm-cpp-sdk +[license-link]: https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/blob/master/LICENSE diff --git a/docs/api_overview.md b/docs/api_overview.md new file mode 100644 index 0000000..d0f692f --- /dev/null +++ b/docs/api_overview.md @@ -0,0 +1,454 @@ +# Proxy-Wasm C++ SDK API Overview + +This page explains the main concepts and structure of the C++ SDK API. For +detailed API semantics, refer to the doc comments in the header files listed in +the [codemap](#codemap) section, in particular [proxy_wasm_api.h]. + +## Concepts and terminology + +* **Plugin**: a unit of code for customizing network proxy + functionality. Plugins can be written in a variety of programming languages + (in this case, C++), and are compiled to Wasm modules. + +* **Wasm VM**: the virtual machine that runs plugins. A network proxy may + internally instantiate multiple Wasm VMs to run plugins, for example one per + worker thread. A given Wasm VM can host multiple plugins at the same time. + +* **Hostcalls and callbacks**: *callbacks* are the entry points to the plugin: + they are functions defined by the plugin that the proxy invokes when events of + interest (e.g. plugin startup, receipt of a new incoming HTTP request) + occur. *Hostcalls* are function calls that the plugin can make to the host + (the network proxy) to query additional state or perform actions, such as + reading and modifying HTTP response headers. + +* **Contexts**: objects that are associated with the current plugin, or the + current "stream" (either HTTP/gRPC request or, if L4 proxying, TCP stream). In + the C++ SDK, Contexts serve multiple purposes: + + * They are the receivers of Proxy-Wasm callbacks: for example, when HTTP + request headers are received, that is represented as a call to the + `onRequestHeaders` method of a Context object. Callbacks are declared as + virtual methods on a `Context` base class, which plugin code can override. + * They provide the interface for most hostcalls, particularly those associated + with the current stream. The hostcalls are defined as methods on `Context` + base classes, which plugin code can call. + + There are two types of Contexts: *Root Contexts* and *Stream Contexts* + (sometimes just called Contexts). Root Contexts are represented by the + `RootContext` class, and are associated with plugins as a whole--i.e. within a + given Wasm VM, there is one Root Context per plugin. Stream Contexts are + represented by the `Context` class, and are associated with individual + streams--i.e. incoming HTTP/gRPC calls, or TCP streams. + + In the C++ SDK programming model, plugins are implemented by subclassing + `RootContext` and/or `Context`, and providing implementations of their various + callback methods. Plugins can also define fields in these subclasses to keep + per-plugin or per-request state. A plugin's `RootContext` object lives for the + full duration that the plugin runs in its Wasm VM, while each stream `Context` + object lives for the duration of the HTTP/gRPC request with which it is + associated. + +## Registering Contexts + +`RegisterContextFactory` is the mechanism for bootstrapping plugin code. Plugin +code instantiates a `RegisterContextFactory` in a static +variable. `RegisterContextFactory` takes as constructor params `std::function`s +for creating new root context and stream context instances. The plugin can use +these to create and return instances of its own subclasses of `RootContext` and +`Context`. For example: + +```c++ +static RegisterContextFactory register_ExampleContext( + CONTEXT_FACTORY(ExampleContext), ROOT_FACTORY(ExampleRootContext), + "my_root_id"); +``` + +`ROOT_FACTORY` and `CONTEXT_FACTORY` are convenience macros for lambdas that +create instances of the specified `RootContext` and `Context` subclasses. + +## `RootContext` + +`RootContext` instances are associated with the plugin as a whole. Accordingly, +they receive callbacks corresponding to different events in the lifecycle of a +plugin: + +* `validateConfiguration`: May be called to validate the configuration data that + will be passed to a plugin on startup, e.g. in the control plane. +* `onStart`: called on plugin start. +* `onConfigure`: called on plugin start, and any time configuration subsequently + changes. +* `onDone`: called when the plugin is being shut down. +* `onDelete`: called after the plugin has acknowledged shutdown is complete, as + indication to release resources. + +`RootContexts` can also receive other callbacks for events that are not strictly +tied to an incoming stream: + +* `onTick`: called when a timer fires. See [Timers]. +* `onQueueReady`: called when data arrives on a SharedQueue. See [Shared + queues]. + +`RootContext` defines methods for initiating outbound HTTP and gRPC callouts: + +* `httpCall`: initiates an HTTP callout (i.e. outbound HTTP request). See [HTTP + callouts]. +* `grpcSimpleCall`: initiates a gRPC callout (i.e. outbound gRPC call). See + [gRPC callouts]. +* `grpcCallHandler`: initiates a gRPC callout (i.e. outbound gRPC call). See + [gRPC callouts]. +* `grpcStreamHandler`: initiates a streaming gRPC callout (i.e. outbound gRPC + call). See [gRPC callouts]. + +For API details, see doc comments for the `RootContext` class in +[proxy_wasm_api.h]. + +## `Context` + +`Context` instances are associated with individual streams--i.e. incoming HTTP +or gRPC requests, or TCP streams if the network proxy is acting as an L4 +proxy. `Context` instances receive callbacks corresponding to different events +in the lifecycle of a stream: + +* `onCreate`: called when handling of a new stream starts. +* `onDone`: called when the host is done processing the stream. +* `onLog`: called after the host is done processing the stream, if the plugin is + being used for access logging. +* `onDelete`: called after the plugin has completed all processing related to + the stream, as indication to release resources. + +`Context` instances also receive callbacks corresponding to stream events: + +* `onRequestHeaders`: called when HTTP or gRPC request headers are received. +* `onRequestBody`: called when HTTP or gRPC request body data is received. +* `onRequestTrailers`: called when HTTP or gRPC request trailers are received. +* `onResponseHeaders`: called when HTTP or gRPC response headers are received. +* `onResponseBody`: called when HTTP or gRPC response body data is received. +* `onResponseTrailers`: called when HTTP or gRPC response trailers are received. +* `onNewConnection`: called when a new connection is established. +* `onDownstreamData`: called when a new chunk of data is received from + downstream over a connection. +* `onUpstreamData`: called when a new chunk of data is received from upstream + over a connection. + +For API details, see doc comments for the `Context` class in [proxy_wasm_api.h]. + +## Buffers + +Many plugin operations require passing data buffers between the host and the +plugin. Because this incurs some expense in copying data into or out of the Wasm +VM's linear address space, data is only fetched or passed when explicitly +requested via one of the following hostcalls: + +* `getBufferBytes`: retrieves bytes from a given buffer. +* `getBufferStatus`: returns whether a given buffer is available, and if + so, what its size is +* `setBuffer`: writes bytes into a given buffer. + +With each of these calls, buffer data is held by an instance of the `WasmData` +class, which represents a span of bytes. The buffer to use as a source or +destination of data is indicated by the `WasmBufferType` enum. For example, the +`WasmBufferType::HttpResponseBody` enum value indicates the buffer used for +passing or receiving HTTP response body data. + +Certain buffer types are only accessible during certain callbacks. For example, +the `WasmBufferType::HttpResponseBody` buffer is only accessible during +`Context::onResponseBody` and `Context::onLog` callbacks. See doc comments for +individual callbacks in [proxy_wasm_api.h] for which buffers are accessible in +each callback. Documentation for the `WasmData` class is also in +[proxy_wasm_api.h]. + +## Handling HTTP and gRPC requests + +Plugins that handle HTTP and gRPC requests do so by overriding one or more of +the HTTP-related callback methods declared by the `Context` class (see the +[Context] section for a list). For example, a plugin that needs to modify HTTP +request headers can do so by overriding the `Context::onRequestHeader` +method. The subclass implementation of `onRequestHeader` can use hostcalls to +fetch and/or mutate HTTP headers. + +Hostcalls for accessing and modifying HTTP headers are listed below: + +* `addHeaderMapValue`: adds a header field. +* `getHeaderMapValue`: returns the current value of a header field. +* `replaceHeaderMapValue`: replaces an existing header field. +* `removeHeaderMapValue`: removes a header field. +* `getHeaderMapPairs`: returns all header fields. +* `setHeaderMapPairs`: sets all header fields. +* `getHeaderMapSize`: returns the current number of header fields. + +Each of the above hostcalls takes a `WasmHeaderMapType` that indicates the type +of headers targeted by the operation, e.g. `RequestHeaders`, `ResponseTrailers`, +etc. For convenience, the C++ SDK also provides functions that are specific to a +particular header type. For example: + +* `addRequestHeader`: adds a request header field. +* `getRequestHeader`: returns the value of a request header field. +* `replaceRequestHeader`: replaces a request header field. +* `removeRequestHeader`: removes a request header field. +* `getRequestHeaderPairs`: returns all request header fields. +* `setRequestHeaderPairs`: sets all request header fields. +* `getRequestHeaderSize`: returns the current number of request header fields. + +A similar set of methods is provided for each of: request trailers, response +headers, and response trailers. + +gRPC requests received by the proxy are dispatched to the plugin using the same +`Context` callback methods as HTTP requests. Plugin code can determine whether +an incoming request is gRPC in the same way that network proxies do: by checking +if the ":method" pseudoheader has value "POST" and the "content-type" header has +value "application/grpc". + +Plugin callbacks can access the request URL via the ":method", ":scheme", +":authority", and ":path" pseudo-headers defined in [RFC 9113 section +8.3.1](https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1). They can +access HTTP response status via the ":status" pseudo-header defined in [RFC 9113 +section 8.3.2](https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.2). + +For API details, see doc comments accompanying the functions in +[proxy_wasm_api.h]. + +## Handling TCP streams + +Plugins that handle TCP (or TCP-like) connections do so by overriding one or +more of the connection-related callback methods declared by the `Context` class +(see the [Context] section for a list). For example, a plugin that wanted to +inspect data sent by the upstream client could override the +`Context::onUpstreamData` method. + +To access the actual data being proxied, plugin code would use the +buffer-related hostcalls described in [Buffers], specifying +`NetworkUpstreamData` as the `WasmBufferType`. + +## Timers + +A plugin can schedule a periodic timer by calling the low-level +`proxy_set_tick_period_milliseconds` hostcall directly. The host will then call +`onTick` on the `RootContext` object periodically at the specified interval. + +## HTTP callouts + +Plugins can initiate outbound HTTP requests by invoking +`RootContext::httpCall`. This method initiates an HTTP request, taking as a +parameter a `std::function` to invoke when the request completes (or fails). + +`RootContext::httpCall` wraps a lower-level hostcall, `makeHttpCall`, that uses +token values to match HTTP request and corresponding response callback. + +For details, see doc comments for these functions in [proxy_wasm_api.h]. + +## gRPC callouts + +Plugins can initiate outbound gRPC requests by invoking one of the following +hostcalls: + +* `grpcSimpleCall`: initiates a gRPC call, taking a `std::function` to invoke + upon call response or failure. +* `grpcCallHandler`: initiates a gRPC call, taking a `GrpcCallHandlerBase` + object to call as various parts of the gRPC response are received. +* `grpcStreamHandler`: initiates a streaming gRPC call, taking a + `GrpcStreamHandlerBase` object to call as new response data arrives over the + gRPC stream. The `GrpcStreamHandlerBase` object can also be used to send + additional data over the streaming gRPC call. + +These `RootContext` methods wrap lower-level hostcalls that use token values to +match gRPC events to a given gRPC call: + +* `grpcCall`: initiates a non-streaming gRPC call. +* `grpcStream`: initiates a streaming gRPC call. + +The gRPC hostcalls listed above use the following supporting types as +parameters: + +* `GrpcCallHandlerBase`: base class for handler objects that receive + callbacks on events for a non-streaming gRPC call. +* `GrpcCallHandler`: subclass of GrpcCallHandlerBase that is templated by + protobuf message type. +* `GrpcStreamHandlerBase`: base class for handler objects that receive + callbacks on events for a streaming gRPC call. +* `GrpcStreamHandler`: subclass of GrpcStreamHandlerBase that is templated by + protobuf message type. + +Plugin code can subclass the appropriate type and override event handling +methods to process gRPC call responses. + +For details of the gRPC hostcalls and their associated types, see doc comments +for these functions and types in [proxy_wasm_api.h]. + +## Shared data + +Proxy-Wasm includes a concept of a shared key-value store that can be accessed +by multiple plugin instances. The C++ SDK provides this access via the following +hostcalls: + +* `getSharedData`: reads the current value associated with a given key, + returning it via out-param. +* `getSharedDataValue`: reads the current value associated with a given key, + returning it via return value. +* `setSharedData`: sets the value associated with a given key. + +In addition to a key param that specifies which entry in the key-value store to +read or write, these methods also accept and return a CAS (compare-and-swap) +value that can be used for atomic updates. For details, see doc comments +accompanying the functions in [proxy_wasm_api.h]. + +## Shared queues + +Proxy-Wasm includes a concept of shared queues that can be used for +communication between multiple plugin instances. Shared queues are FIFO queues +that can have multiple producers and consumers. + +The following hostcalls operate on shared queues: + +* `registerSharedQueue`: establishes a shared queue under a given name. +* `resolveSharedQueue`: resolves an existing shared queue to an ID that can be + used for enqueueing or dequeueing items to/from the queue. +* `enqueueSharedQueue`: writes a chunk of data onto the shared queue. +* `dequeueSharedQueue`: consumes a chunk of data from the shared queue. + +The `RootContext::onQueueReady` callback informs a plugin when there is data +that is available to be consumed from a shared queue. + +For API details, see doc comments in [proxy_wasm_api.h]. + +## Logging + +Plugins can emit log messages for the host proxy to incorporate into its own +logging or otherwise record. Each logging operation takes a log level and string +message. The following logging macros log messages at various log levels, in +increasing order of severity, with file and function name automatically +prepended to the log message: + +* `LOG_TRACE` +* `LOG_DEBUG` +* `LOG_INFO` +* `LOG_WARN` +* `LOG_ERROR` +* `LOG_CRITICAL` + +Additionally, there is a LOG macro that accepts log level as a +parameter. These macros layer on top of hostcall functions: + +* `logTrace` +* `logDebug` +* `logInfo` +* `logWarn` +* `logError` +* `logCritical` + +All macros and hostcalls above allow plugin execution to continue. There +is one further logging hostcall that terminates plugin execution: + +* `logAbort`: logs at Critical level, then aborts the plugin + +For API details, see associated doc comments in [proxy_wasm_api.h]. + +## Metrics + +Plugins can register and update metrics, to be exported by the host proxy. Each +metric can be one of the following types, represented by the `MetricType` enum: + +* `MetricType::Counter`: numeric value representing a cumulative count of some event, which + monotonically increases over time. +* `MetricType::Gauge`: numeric value representing a current snapshot of some quantity, which + may take arbitrary values over time. +* `MetricType::Histogram`: a bucketed distribution of values. + +The following low-level hostcalls support defining and updating metrics: + +* `defineMetric`: registers a metric under a given name. +* `incrementMetric`: increments a metric counter. +* `recordMetric`: sets a metric counter. +* `getMetric`: reads a metric counter. + +On top of these base hostcalls, the C++ SDK provides convenience classes +for representing and updating metrics: + +- `Counter` +- `Gauge` +- `Histogram` +- `Metric`: all of the above + +Metrics can be subdivided by tags, using similar structure to [Envoy +statistics](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/observability/statistics). + +## Example + +Some of the concepts and APIs described above are illustrated by the example +plugin code in [http_wasm_example.cc]. + +First, the example plugin defines subclasses of `RootContext` and `Context` +that override callbacks of interest: + +```c++ +class ExampleRootContext : public RootContext { +public: + explicit ExampleRootContext(uint32_t id, std::string_view root_id) : RootContext(id, root_id) {} + bool onStart(size_t) override; + bool onConfigure(size_t) override; + // ... +}; + +class ExampleContext : public Context { +public: + explicit ExampleContext(uint32_t id, RootContext *root) : Context(id, root) {} + void onCreate() override; + FilterHeadersStatus onRequestHeaders(uint32_t headers, bool end_of_stream) override; + // ... +}; +``` + +`RegisterContextFactory` registers these classes for creation: + +```c++ +static RegisterContextFactory register_ExampleContext(CONTEXT_FACTORY(ExampleContext), + ROOT_FACTORY(ExampleRootContext), + "my_root_id"); +``` + +The implementations of callback methods use hostcalls to access and +mutate request or response state: + +```c++ +FilterHeadersStatus ExampleContext::onResponseHeaders(uint32_t, bool) { + LOG_DEBUG(std::string("onResponseHeaders ") + std::to_string(id())); + auto result = getResponseHeaderPairs(); + auto pairs = result->pairs(); + LOG_INFO(std::string("headers: ") + std::to_string(pairs.size())); + 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"); + removeResponseHeader("content-length"); + return FilterHeadersStatus::Continue; +} +``` + +## Codemap + +The main files containing the Proxy-Wasm C++ SDK API and implementation are +listed below: + +* [proxy_wasm_api.h]: main SDK API definition and implementation +* [proxy_wasm_common.h](../proxy_wasm_common.h): supporting types for the API +* [proxy_wasm_enums.h](../proxy_wasm_enums.h): supporting enums for the API +* [proxy_wasm_externs.h](../proxy_wasm_externs.h): declarations for ABI-level + hostcalls and callbacks +* [proxy_wasm_intrinsics.js](../proxy_wasm_intrinsics.js): list of Proxy-Wasm + ABI hostcalls, for use by [Emscripten](https://emscripten.org) +* [proxy_wasm_intrinsics.proto](../proxy_wasm_intrinsics.proto): protobuf types + needed for gRPC calls +* [proxy_wasm_intrinsics.h](../proxy_wasm_intrinsics.h): combined header file + that includes all other header files +* [proxy_wasm_intrinsics.cc](../proxy_wasm_intrinsics.cc): implementation of + dispatch from ABI-level Proxy-Wasm callbacks to [Context] and [RootContext] + callback methods. + +[Context]: #context +[RootContext]: #rootcontext +[Timers]: #timers +[Shared queues]: #shared-queues +[HTTP callouts]: #http-callouts +[gRPC callouts]: #grpc-callouts +[proxy_wasm_api.h]: ../proxy_wasm_api.h +[http_wasm_example.cc]: ../example/http_wasm_example.cc diff --git a/docs/building.md b/docs/building.md index bafb41b..1262ba5 100644 --- a/docs/building.md +++ b/docs/building.md @@ -1,13 +1,12 @@ -# WebAssembly for Proxies (C++ SDK) +# Proxy-Wasm C++ SDK Build Instructions -[![Build Status][build-badge]][build-link] -[![Apache 2.0 License][license-badge]][license-link] - -The SDK has dependencies on specific versions of the C++ WebAssembly toolchain Emscripten (https://emscripten.org) and the protobuf library, therefor use of a Docker image is recommended. +The C++ SDK has dependencies on specific versions of the C++ WebAssembly +toolchain [Emscripten](https://emscripten.org) and the protobuf library, +therefore use of a Docker image is recommended. ## Docker -A Dockerfile for the C++ SDK is provided in Dockerfile-sdk. +A Dockerfile for the C++ SDK is provided in [Dockerfile-sdk](Dockerfile-sdk). It can built in this directory by: @@ -15,7 +14,7 @@ It can built in this directory by: docker build -t wasmsdk:v2 -f Dockerfile-sdk . ``` -The docker image can be used for compiling wasm files. +The docker image can be used for compiling C++ plugin code into Wasm modules. ### Creating a project for use with the Docker build image @@ -66,8 +65,9 @@ docker run -v $PWD:/work -w /work wasmsdk:v2 /build_wasm.sh ### Caching the standard libraries -The first time that emscripten runs it will generate the standard libraries. To cache these in the docker image, -after the first successful compilation (e.g myproject.cc above), commit the image with the standard libraries: +The first time that emscripten runs it will generate the standard libraries. To +cache these in the docker image, after the first successful compilation (e.g +myproject.cc above), commit the image with the standard libraries: ```bash docker commit `docker ps -l | grep wasmsdk:v2 | awk '{print $1}'` wasmsdk:v2 @@ -77,9 +77,12 @@ This will save time on subsequent compiles. ### Using the SDK from a newer/specific version of Envoy -To use a newer/specific version of the SDK (e.g. from the version of Enovy you are going to deploy the WebAssembly module to) bind that volume and use it in the Makefile. +To use a newer/specific version of the SDK (e.g. from the version of Envoy you +are going to deploy the WebAssembly module to), bind that volume and use it in +the Makefile. -Here is an example Makefile referencing the SDK at ../envoy/api/wasm/cpp and mounted as 'sdk' in the /work directory: +Here is an example Makefile referencing the SDK at `../envoy/api/wasm/cpp` and +mounted as `sdk` in the `/work` directory: ```makefile PROXY_WASM_CPP_SDK=/work/sdk @@ -89,15 +92,19 @@ all: myproject.wasm include ${PROXY_WASM_CPP_SDK}/Makefile.base_lite ``` -Run docker pointing to Envoy sources in a directory parallel (at the same level) as your project directory: +Run docker pointing to Envoy sources in a directory parallel (at the same level) +as your project directory: ```bash docker run -v $PWD:/work -v $PWD/../envoy/api/wasm/cpp:/work/sdk -w /work wasmsdk:v2 bash /build_wasm.sh ``` -### Using abseil form the image +### Using Abseil from the Docker image -Abseil (optionally) is built in /root/abseil and can be used. Note that the abseil containers (e.g. absl::flat\_hash\_set) exercise many syscalls which are not supported. Consequantally individual files should be pulled in which are relatively self contained (e.g. strings). Example customized Makefile: +Abseil (optionally) is built in /root/abseil and can be used. Note that the +Abseil containers (e.g. `absl::flat_hash_set`) exercise many syscalls which are +not supported. Consequentially individual files should be pulled in which are +relatively self contained (e.g. `strings`). Example customized Makefile: ```makefile PROXY_WASM_CPP_SDK=/sdk @@ -113,7 +120,8 @@ all: plugin.wasm em++ --no-entry -s EXPORTED_FUNCTIONS=['_malloc'] --std=c++17 -O3 -flto -I${CPP_API} -I${CPP_API}/google/protobuf -I/usr/local/include -I${ABSL} --js-library ${CPP_API}/proxy_wasm_intrinsics.js ${ABSL_CPP} $*.cc ${CPP_API}/proxy_wasm_intrinsics.pb.cc ${CPP_CONTEXT_LIB} ${CPP_API}/libprotobuf.a -o $*.wasm ``` -Precompiled abseil libraries are also available, so the above can also be done as: +Precompiled Abseil libraries are also available, so the above can also be done +as: ```makefile PROXY_WASM_CPP_SDK=/sdk @@ -131,7 +139,8 @@ all: plugin.wasm ### Ownership of the resulting .wasm files -The compiled files may be owned by root. To chown them add the follow lines to the Makefile and docker invocation: +The compiled files may be owned by root. To chown them, add the follow lines to +the Makefile and docker invocation: ```makefile PROXY_WASM_CPP_SDK=/sdk @@ -149,13 +158,17 @@ Invocation file (e.g. build.sh): docker run -e uid="$(id -u)" -e gid="$(id -g)" -v $PWD:/work -w /work wasmsdk:v2 /build_wasm.sh ``` -## Dependencies for building WASM modules: +## Dependencies for building Wasm modules: -If you do not wish to use the Docker file, the dependencies can be installed by script (sdk\_container.sh), or by hand. +If you do not wish to use the Docker image, the dependencies can be installed by +script (sdk\_container.sh), or by hand. ### protobuf v3.9.1 -You must install the version of protobuf on your build system that matches the libprotobuf.a files (without any patches) so that the generated code matches the .a library. Currently this is based on tag v3.9.1 of https://github.com/protocolbuffers/protobuf. +You must install the version of protobuf on your build system that matches the +libprotobuf.a files (without any patches) so that the generated code matches the +.a library. Currently this is based on tag v3.9.1 of +https://github.com/protocolbuffers/protobuf. ```bash git clone https://github.com/protocolbuffers/protobuf @@ -169,7 +182,7 @@ make check sudo make install ``` -### emscripten +### Emscripten ```bash git clone https://github.com/emscripten-core/emsdk.git @@ -178,7 +191,7 @@ cd emsdk ./emsdk install 3.1.7 ./emsdk activate 3.1.7 -source ./emsdk\_env.sh +source ./emsdk_env.sh ``` It is possible later versions will work, e.g. @@ -193,7 +206,9 @@ However 3.1.7 is known to work. ### Rebuilding the libprotobuf.a files -If want to rebuild the libprotobuf.a files or use a different version see the instructions at https://github.com/kwonoj/protobuf-wasm. Commit 4bba8b2f38b5004f87489642b6ca4525ae72fe7f works for protobuf v3.9.x. +If want to rebuild the libprotobuf.a files or use a different version see the +instructions at https://github.com/kwonoj/protobuf-wasm. Commit +4bba8b2f38b5004f87489642b6ca4525ae72fe7f works for protobuf v3.9.x. ```bash git clone https://github.com/protocolbuffers/protobuf protobuf-wasm @@ -220,10 +235,4 @@ make sudo make install ``` -Note: ensure /usr/local/bin is in your path - - -[build-badge]: https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/workflows/C++/badge.svg?branch=master -[build-link]: https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/actions?query=workflow%3AC%2B%2B+branch%3Amaster -[license-badge]: https://img.shields.io/github/license/proxy-wasm/proxy-wasm-cpp-sdk -[license-link]: https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/blob/master/LICENSE +Note: ensure /usr/local/bin is in your path. diff --git a/docs/wasm_context.svg b/docs/wasm_context.svg deleted file mode 100644 index 1d1d2b5..0000000 --- a/docs/wasm_context.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/wasm_filter.md b/docs/wasm_filter.md deleted file mode 100644 index 7c370a3..0000000 --- a/docs/wasm_filter.md +++ /dev/null @@ -1,1180 +0,0 @@ -WebAssembly -=========== - -Overview --------- - -TODO: add text about high level design principal and feature set. - -This [video talk](https://youtu.be/XdWmm_mtVXI) is a great introduction -about architecture of WebAssembly integration. - -Configuration -------------- - -- v2 API reference (TODO: add link) -- This filter should be configured with name *envoy.wasm*. - -Example -------- - -An example C++ WASM filter could be found -[here](https://github.com/envoyproxy/envoy-wasm/tree/19b9fd9a22e27fcadf61a06bf6aac03b735418e6/examples/wasm). - -To implement a WASM filter: - -- Implement a [root context class](https://github.com/envoyproxy/envoy-wasm/blob/e8bf3ab26069a387f47a483d619221a0c482cd13/examples/wasm/envoy_filter_http_wasm_example.cc#L7) which inherits [base root context class](https://github.com/envoyproxy/envoy-wasm/blob/e8bf3ab26069a387f47a483d619221a0c482cd13/api/wasm/cpp/proxy_wasm_impl.h#L288) -- Implement a [stream context - class](https://github.com/envoyproxy/envoy-wasm/blob/e8bf3ab26069a387f47a483d619221a0c482cd13/examples/wasm/envoy_filter_http_wasm_example.cc#L14) - which inherits the [base context - class](https://github.com/envoyproxy/envoy-wasm/blob/master/api/wasm/cpp/proxy_wasm_impl.h#L314). -- Override [context API](#context-object-api) methods to handle corresponding initialization and stream events - from host. -- [Register](https://github.com/envoyproxy/envoy-wasm/blob/e8bf3ab26069a387f47a483d619221a0c482cd13/examples/wasm/envoy_filter_http_wasm_example.cc#L26) the root context and stream context. - -Context object --------------- - -WASM module is running in a stack-based virtual machine and its memory -is isolated from the host environment. All interactions between host and -WASM module are through functions and callbacks wrapped by context -object. - -At bootstrap time, a root context is created. The root context -has the same lifetime as the VM/runtime instance and acts as a target -for any interactions which happen at initial setup. It is also used for -interactions that outlive a request. - -At request time, a context with incremental is created for -each stream. Stream context has the same lifetime as the stream itself -and acts as a target for interactions that are local to that stream. - -![image](/docs/wasm_context.svg) - -Context object API ------------------- - -### onConfigure - -``` {.sourceCode .cpp} -void onConfigure(std::unique_ptr configuration) -``` - -Called when host loads the WASM module. *configuration* is passed in -using [WasmData](#wasmdata). If the VM that -the module running in has not been configured, onConfigure is called -first with -VM config (TODO: add link), -then a second call will be invoked to pass in -module config (TODO: add link). -*onConfigure* will only be called in -[root context](#context-object). - -If VM is [shared](#vm-sharing) by multiple -filters and has already been configured via other WASM filter in the -chain, onConfigure will only be called once with module config. - -### onStart - -``` {.sourceCode .cpp} -void onStart() -``` - -Called after finishing loading WASM module and before serving any stream -events. *onStart* will only be called in -[root context](#context-object). - -The following methods are called in order during the lifetime of a -stream. - -### onCreate - -``` {.sourceCode .cpp} -void onCreate() -``` - -Called at the beginning of filter chain iteration. Indicates creation of -the new stream context. - -### onRequestHeaders - -``` {.sourceCode .cpp} -void onRequestHeaders() -``` - -Called when headers are decoded. Request Headers could be fetched and -manipulated by -[request header API](#addrequestheader) - -Returns -[FilterHeadersStatus](https://github.com/envoyproxy/envoy/blob/5d3214d4d8e1d77937f0f1278d3ac816d9a3d888/include/envoy/http/filter.h#L27) -to determine how filter chain iteration proceeds. - -### onRequestBody - -``` {.sourceCode .cpp} -FilterDataStatus onRequestBody(size_t body_buffer_length, bool end_of_stream) -``` - -Called when request body is decoded. *body\_buffer\_length* is used to -indicate size of decoded request body. *end\_of\_stream* indicates if -this is the last data frame. Request body could be fetched by -[body API](#body-api). - -Returns -[FilterDataStatus](https://github.com/envoyproxy/envoy/blob/5d3214d4d8e1d77937f0f1278d3ac816d9a3d888/include/envoy/http/filter.h#L66) -to determine how filter chain iteration proceeds. - -### onRequestTrailers - -``` {.sourceCode .cpp} -FilterTrailersStatus onRequestTrailers() -``` - -Called when request trailers are decoded. Request trailers could be -fetched and manipulated by -request [trailer API](#addrequesttrailer). - -Returns -[FilterTrailerStatus](https://github.com/envoyproxy/envoy/blob/5d3214d4d8e1d77937f0f1278d3ac816d9a3d888/include/envoy/http/filter.h#L104) -to determine how filter chain iteration proceeds. - -### onResponseHeaders - -``` {.sourceCode .cpp} -void onResponseHeaders() -``` - -Called when headers are decoded. Response headers could be fetched and -manipulated by -[response header API](#addresponseheader). - -Returns -[FilterHeadersStatus](https://github.com/envoyproxy/envoy/blob/5d3214d4d8e1d77937f0f1278d3ac816d9a3d888/include/envoy/http/filter.h#L27) -to determine how filter chain iteration proceeds. - -### onResponseBody - -``` {.sourceCode .cpp} -FilterDataStatus onResponseBody(size_t body_buffer_length, bool end_of_stream) -``` - -Called when response body is decoded. *body\_buffer\_length* is used to -indicate size of decoded response body. *end\_of\_stream* indicates if -this is the last data frame. Response body could be fetched by -[body API](#body-api). - -Returns -[FilterDataStatus](https://github.com/envoyproxy/envoy/blob/5d3214d4d8e1d77937f0f1278d3ac816d9a3d888/include/envoy/http/filter.h#L66) -to determine how filter chain iteration proceeds. - -### onResponseTrailers - -``` {.sourceCode .cpp} -FilterTrailersStatus onResponseTrailers() -``` - -Called when response trailers are decoded. Response trailers could be -fetched and manipulated -response [trailer API](#addrequesttrailer). - -Returns FilterTrailerStatus -[FilterTrailerStatus](https://github.com/envoyproxy/envoy/blob/5d3214d4d8e1d77937f0f1278d3ac816d9a3d888/include/envoy/http/filter.h#L104) -to determine how filter chain iteration proceeds. - -### onDone - -``` {.sourceCode .cpp} -void onDone() -``` - -Called after stream is ended or reset. All stream info will not be -changed any more and is safe for access logging. - -> **note** -> -> This is called before [onLog](#onlog). - -### onLog - -``` {.sourceCode .cpp} -void onLog() -``` - -Called to log any stream info. Several types of stream info are -available from API: Request headers could be fetched by -[request header API](#addrequestheader) -Response headers could be fetched by -[response header API](#addresponseheader). -Response trailers could be fetched by -response [trailer API](#addrequesttrailer). -Streaminfo could be fetched by -[streaminfo API](#streaminfo-api). - -> **note** -> -> This is called after [onDone])(#ondone). - -### onDelete - -``` {.sourceCode .cpp} -void onDelete() -``` - -Called after logging is done. This call indicates no more handler will -be called on the stream context and it is up for deconstruction, The -stream context needs to make sure all async events are cleaned up, such -as network calls, timers. - -Root context object is used to handle timer event. - -### onTick - -``` {.sourceCode .cpp} -void onTick() -``` - -Called when a timer is set and fired. Timer could be set by -[setTickPeriodMilliseconds](#setTickPeriodMilliseconds). - -The following methods on context object are supported. - -### httpCall - -``` {.sourceCode .cpp} -void httpCall(std::string_view cluster, - const HeaderStringPairs& request_headers, - std::string_view request_body, - const HeaderStringPairs& request_trailers, - uint32_t timeout_milliseconds, - HttpCallCallback callback) -``` - -Makes an HTTP call to an upstream host. - -*cluster* is a string which maps to a configured cluster manager -cluster. *request\_headers* is a vector of key/value pairs to send. Note -that the *:method*, *:path*, and *:authority* headers must be set. -*request\_body* is an optional string of body data to send. timeout is -an integer that specifies the call timeout in milliseconds. -*timeout\_milliseconds* is an unsigned integer as timeout period for the -http call in milliseconds. *callback* is the callback function to be -called when the HTTP request finishes. - -> **note** -> -> If the call outlives the stream context, *httpCall* should be called -> within [root context](#context-object). - -### grpcSimpleCall - -``` {.sourceCode .cpp} -template -void grpcSimpleCall(std::string_view service, - std::string_view service_name, - std::string_view method_name, - const google::protobuf::MessageLite &request, - uint32_t timeout_milliseconds, - std::function success_callback, - std::function failure_callback) -``` - -Makes a unary gRPC call to an upstream host. - -*service* is a serialized proto string of -[gRPC service](https://www.envoyproxy.io/docs/envoy/v1.11.0/api-v2/api/v2/core/grpc_service.proto#core-grpcservice) for gRPC client -initialization. *service\_name* and *method\_name* indicates the target -gRPC service and method name. *request* is a [lite proto -message](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message_lite) -that gRPC service accepts as request. *timeout\_milliseconds* is an -unsigned integer as timeout period for the gRPC call in milliseconds. -*success\_callback* is the callback function that will be called when -gRPC call succeeds. *response* is the returned message from gRPC -service. *failure\_callback* is the callback function that will be -invoked when gRPC call fails. *status* is the returned gRPC status code. -*error\_message* is detailed error message extracted from gRPC response. - -> **note** -> -> if the call outlives the stream context, *grpcSimpleCall* should be -> called within -> [root context](#context-object). - -### grpcCallHandler - -``` {.sourceCode .cpp} -void grpcCallHandler( - std::string_view service, - std::string_view service_name, - std::string_view method_name, - const google::protobuf::MessageLite &request, - uint32_t timeout_milliseconds, - std::unique_ptr handler) -``` - -Makes a unary gRPC call to an upstream host. - -Similar to -[grpcSimpleCall](#grpcsimplecall) -for gRPC client initialization, but uses -[GrpcCallHandler](#GrpcCallHandler-class) as -target for callback and fine grained control on the call. - -### grpcStreamHandler - -``` {.sourceCode .cpp} -void grpcStreamHandler(std::string_view service, - std::string_view service_name, - std::string_view method_name, - std::unique_ptr handler) -``` - -Makes an gRPC stream to an upstream host. - -*service* is a serialized proto string of -[gRPC service](https://www.envoyproxy.io/docs/envoy/v1.11.0/api-v2/api/v2/core/grpc_service.proto#core-grpcservice) for gRPC client -initialization. *service\_name* and *method\_name* indicates the target -gRPC service and method name. *handler* -[GrpcStreamHandler](#grpcstreamhandler-class) -is used to control the stream and as target for gRPC stream callbacks. - -> **note** -> -> if the stream call outlives the per request context, -> *grpcStreamHandler* should be called within -> [root context](#context-object). - -Application log API -------------------- - -### log\* - -``` {.sourceCode .cpp} -void LogTrace(const std::string& logMessage) -void LogDebug(const std::string& logMessage) -void LogInfo(const std::string& logMessage) -void LogWarn(const std::string& logMessage) -void LogError(const std::string& logMessage) -void LogCritical(const std::string& logMessage) -``` - -Logs a message using Envoy's application logging. *logMessage* is a -string to log. - -Header API ----------- - -### addRequestHeader - -``` {.sourceCode .cpp} -void addRequestHeader(std::string_view key, StringView value) -``` - -Adds a new request header with the key and value if header does not -exist, or append the value if header exists. This method is effective -only when called in -[onRequestHeader](#onrequestheaders). - -### replaceRequestHeader - -``` {.sourceCode .cpp} -void replaceRequestHeader(std::string_view key, StringView value) -``` - -Replaces the value of an existing request header with the given key, or -create a new request header with the key and value if not existing. This -method is effective only when called in -[onRequestHeader](#onrequestheaders). - -### removeRequestHeader - -``` {.sourceCode .cpp} -void removeRequestHeader(std::string_view key) -``` - -Removes request header with the given key. No-op if the request header -does not exist. This method is effective only when called in -[onRequestHeader](#onrequestheaders). - -### setRequestHeaderPairs - -``` {.sourceCode .cpp} -void setRequestHeaderPairs(const HeaderStringPairs &pairs) -``` - -Sets request headers with the given header pairs. For each header key -value pair, it acts the same way as replaceRequestHeader. This method is -effective only when called in -[onRequestHeader](#onrequestheaders). - -### getRequestHeader - -``` {.sourceCode .cpp} -WasmDataPtr getRequestHeader(std::string_view key) -``` - -Gets value of header with the given key. Returns empty string if header -does not exist. This method is effective only when called in -[onRequestHeader](#onrequestheaders) -and [onLog](#onlog) - -Returns [WasmData](#wasmdata) pointer which -contains the header value data. - -### getRequestHeaderPairs - -``` {.sourceCode .cpp} -WasmDataPtr getRequestHeaderPairs() -``` - -Gets all header pairs. This method is effective only when called in -[onRequestHeader](#onrequestheaders) -and [onLog](#onlog) - -Returns [WasmData](#wasmdata) pointer which -contains header pairs data. - -### addResponseHeader - -``` {.sourceCode .cpp} -void addResponseHeader(std::string_view key, StringView value) -``` - -Adds a new response header with the key and value if header does not -exist, or append the value if header exists. This method is effective -only when called in -[onResponseHeaders](#onresponseheaders). - -### replaceResponseHeader - -``` {.sourceCode .cpp} -void replaceResponseHeader(std::string_view key, StringView value) -``` - -Replaces the value of an existing response header with the given key, or -create a new response header with the key and value if not existing. -This method is effective only when called in -[onResponseHeaders](#onresponseheaders). - -### removeResponseHeader - -``` {.sourceCode .cpp} -void removeResponseHeader(std::string_view key) -``` - -Removes response header with the given key. No-op if the response header -does not exist. This method is effective only when called in -[onResponseHeaders](#onresponseheaders). - -### setResponseHeaderPairs - -``` {.sourceCode .cpp} -void setResponseHeaderPairs(const HeaderStringPairs &pairs) -``` - -Sets response headers with the given header pairs. For each header key -value pair, it acts the same way as replaceResponseHeader. This method -is effective only when called in -[onResponseHeaders](#onresponseheaders). - -### getResponseHeader - -``` {.sourceCode .cpp} -WasmDataPtr getResponseHeader(std::string_view key) -``` - -Gets value of header with the given key. Returns empty string if header -does not exist. This method is effective only when called in -[onResponseHeaders](#onresponseheaders) -and [onLog](#onlog) - -Returns [WasmData](#wasmdata) pointer which -holds the header value. - -### getResponseHeaderPairs - -``` {.sourceCode .cpp} -WasmDataPtr getResponseHeaderPairs() -``` - -Gets all header pairs. This method is effective only when called in -[onResponseHeaders](#onresponseheaders) -and [onLog](#onlog) - -Returns [WasmData](#wasmdata) pointer which -holds the header pairs. - -### addRequestTrailer - -``` {.sourceCode .cpp} -void addRequestTrailer(std::string_view key, StringView value) -``` - -Adds a new request trailer with the key and value if trailer does not -exist, or append the value if trailer exists. This method is effective -only when called in -[onRequestTrailers](#onrequesttrailers). - -### replaceRequestTrailer - -``` {.sourceCode .cpp} -void replaceRequestTrailer(std::string_view key, StringView value) -``` - -Replaces the value of an existing request trailer with the given key, or -create a new request trailer with the key and value if not existing. -This method is effective only when called in -[onRequestTrailers](#onrequesttrailers). - -### removeRequestTrailer - -``` {.sourceCode .cpp} -void removeRequestTrailer(std::string_view key) -``` - -Removes request trailer with the given key. No-op if the request trailer -does not exist. This method is effective only when called in -[onRequestTrailers](#onrequesttrailers). - -### setRequestTrailerPairs - -``` {.sourceCode .cpp} -void setRequestTrailerPairs(const HeaderStringPairs &pairs) -``` - -Sets request trailers with the given trailer pairs. For each trailer key -value pair,it acts the same way as replaceRequestHeader. This method is -effective only when called in -[onRequestTrailers](#onrequesttrailers). - -### getRequestTrailer - -``` {.sourceCode .cpp} -WasmDataPtr getRequestTrailer(std::string_view key) -``` - -Gets value of trailer with the given key. Returns empty string if -trailer does not exist. This method is effective only when called in -[onRequestTrailers](#onrequesttrailers). - -Returns [WasmData](#wasmdata) pointer which -holds the trailer value. - -### getRequestTrailerPairs - -``` {.sourceCode .cpp} -WasmDataPtr getRequestTrailerPairs() -``` - -Gets all trailer pairs. This method is effective only when called in -[onRequestTrailers](#onrequesttrailers). - -Returns [WasmData](#wasmdata) pointer which -holds the trailer pairs. - -### addResponseTrailer - -``` {.sourceCode .cpp} -void addResponseTrailer(std::string_view key, StringView value) -``` - -Adds a new response trailer with the key and value if trailer does not -exist, or append the value if trailer exists. This method is effective -only when called in -[onResponseTrailers](#onresponsetrailers). - -### replaceResponseTrailer - -``` {.sourceCode .cpp} -void replaceResponseTrailer(std::string_view key, StringView value) -``` - -Replaces the value of an existing response trailer with the given key, -or create a new response trailer with the key and value if not existing. -This method is effective only when called in -[onResponseTrailers](#onresponsetrailers). - -### removeResponseTrailer - -``` {.sourceCode .cpp} -void removeResponseTrailer(std::string_view key) -``` - -Removes response trailer with the given key. No-op if the response -trailer does not exist. This method is effective only when called in -[onResponseTrailers](#onresponsetrailers). - -### setResponseTrailerPairs - -``` {.sourceCode .cpp} -void setResponseTrailerPairs(const TrailerStringPairs &pairs) -``` - -Sets response trailers with the given trailer pairs. For each trailer -key value pair, it acts the same way as replaceResponseTrailer. This -method is effective only when called in -[onResponseTrailers](#onresponsetrailers). - -### getResponseTrailer - -``` {.sourceCode .cpp} -WasmDataPtr getResponseTrailer(std::string_view key) -``` - -Gets value of trailer with the given key. Returns empty string if -trailer does not exist. This method is effective only when called in -[onResponseTrailers](#onresponsetrailers) -and [onLog](#onlog) - -Returns [WasmData](#wasmdata) pointer which -holds the trailer value. - -### getResponseTrailerPairs - -``` {.sourceCode .cpp} -WasmDataPtr getResponseTrailerPairs() -``` - -Gets all trailer pairs. This method is effective only when called in -[onResponseTrailers](#onresponsetrailers) -and [onLog](#onlog) - -Returns [WasmData](#wasmdata) pointer which -holds the trailer pairs. - -Body API --------- - -### getRequestBodyBufferBytes - -``` {.sourceCode .cpp} -WasmDataPtr getRequestBodyBufferBytes(size_t start, size_t length) -``` - -Returns buffered request body. This copies segment of request body. -*start* is an integer and supplies the body buffer start index to copy. -*length* is an integer and supplies the buffer length to copy. This -method is effective when calling from -[onRequestBody](#onrequestbody). - -Returns [WasmData](#wasmdata) pointer which -holds the request body data. - -### getResponseBodyBufferBytes - -``` {.sourceCode .cpp} -WasmDataPtr getResponseBodyBufferBytes(size_t start, size_t length) -``` - -Returns buffered response body. This copies segment of response body. -*start* is an integer and supplies the body buffer start index to copy. -*length* is an integer and supplies the buffer length to copy. This -method is effective when calling from -[onResponseBody](#onresponsebody). - -Returns [WasmData](#wasmdata) pointer which -holds the response body data. - -Metadata API ------------- - -TODO: Add metadata related API - -StreamInfo API --------------- - -### getProtocol - -``` {.sourceCode .cpp} -WasmDataPtr getProtocol(StreamType type) -``` - -Returns the string representation of HTTP protocol used by the current -request. The possible values are: HTTP/1.0, HTTP/1.1, and HTTP/2. *type* -is the stream type with two possible values: StreamType::Request and -StreamType::Response. The string protocol is returned as -[WasmData](#wasmdata). - -Timer API ---------- - -Timer API is used to set a timer and get current timestamp. - -### setTickPeriodMilliseconds - -``` {.sourceCode .cpp} -void setTickPeriodMilliseconds(uint32_t millisecond) -``` - -Set a timer. *millisecond* is tick interval in millisecond. -[onTick](#ontick) -will be invoked when timer fires. - -> **note** -> -> Only one timer could be set per root_id which is vectored to the appropriate RootContext. Any context can call setTickPeriodMilliseconds and the onTick will come on the corresponding RootContext. - -### getCurrentTimeNanoseconds - -``` {.sourceCode .cpp} -uint64 getCurrentTimeNanoseconds() -``` - -Returns timestamp of now in nanosecond precision. - -Stats API ---------- - -The following objects are supported to export stats from WASM module to -host stats sink. - -### Counter - -#### New - -``` {.sourceCode .cpp} -static Counter* New(std::string_view name, MetricTagDescriptor... fieldnames) -``` - -Create a new counter with the given metric name and tag names. Example -code to create a counter metric: - -``` {.sourceCode .cpp} -auto c = Counter::New( - "test_counter", "string_tag", "int_tag", "bool_tag"); -``` - -Returns a pointer to counter object. - -### increment - -``` {.sourceCode .cpp} -void increment(int64_t offset, Tags... tags) -``` - -Increments a counter. *offset* is the value the counter incremented by. -*tags* is a list of tag values to identify a specific counter. Example -code to increment the aforementioned counter: - -``` {.sourceCode .cpp} -c->increment(1, "test_tag", 7, true) -``` - -### get - -``` {.sourceCode .cpp} -uint64_t get(Tags... tags) -``` - -Returns value of a counter. *tags* is a list of tag values to identify a -specific counter. Example code to get value of a counter: - -``` {.sourceCode .cpp} -c->get("test_tag", 7, true); -``` - -### resolve - -``` {.sourceCode .cpp} -SimpleCounter resolve(Tags... f) -``` - -Resolves counter object to a specific counter for a list of tag values. - -Returns a [SimpleCounter](#simplecounter) -resolved from the counter object, so that tag values do not need to be -specified in every increment call. Example code: - -``` {.sourceCode .cpp} -auto simple_counter = c->resolve("test_tag", 7, true); -``` - -### SimpleCounter - -*SimpleCounter* is resolved from a -[Counter](#counter) object with -predetermined tag values. - -### increment - -``` {.sourceCode .cpp} -void increment(int64_t offset) -``` - -Increment a counter. *offset* is the value counter incremented by. - -### get - -``` {.sourceCode .cpp} -uint64_t get() -``` - -Returns current value of a counter. - -### Gauge - -#### New - -``` {.sourceCode .cpp} -static Gauge* New(std::string_view name, MetricTagDescriptor... fieldnames) -``` - -Create a new gauge with the given metric name and tag names. Example -code to create a gauge metric: - -``` {.sourceCode .cpp} -auto c = Gauge::New( - "test_gauge", "string_tag", "int_tag", "bool_tag"); -``` - -Returns a pointer to Gauge object. - -### record - -``` {.sourceCode .cpp} -void record(int64_t offset, Tags... tags) -``` - -Records current value of a gauge. *offset* is the value to set for -current gauge. *tags* is a list of tag values to identify a specific -gauge. Example code to record value of a gauge metric: - -``` {.sourceCode .cpp} -c->record(1, "test_tag", 7, true) -``` - -### get - -``` {.sourceCode .cpp} -uint64_t get(Tags... tags) -``` - -Returns value of a gauge. *tags* is a list of tag values to identify a -specific gauge. Example code to get value of a gauge: - -``` {.sourceCode .cpp} -c->get("test_tag", 7, true); -``` - -### resolve - -``` {.sourceCode .cpp} -SimpleGauge resolve(Tags... f) -``` - -Resolves gauge object to a specific gauge for a list of tag values. - -Returns a [SimpleGauge](#SimpleGauge) -resolved from the gauge object, so that tag values do not need to be -specified in every record call. Example code: - -``` {.sourceCode .cpp} -auto simple_gauge = c->resolve("test_tag", 7, true); -``` - -### SimpleGauge - -*SimpleGauge* is resolved from a -[Gauge](#Gauge) object with predetermined -tag values. - -### record - -``` {.sourceCode .cpp} -void record(int64_t offset) -``` - -Records current value of a gauge. *offset* is the value to set for -current gauge. - -### get - -``` {.sourceCode .cpp} -uint64_t get() -``` - -Returns current value of a gauge. - -### Histogram - -#### New - -``` {.sourceCode .cpp} -static Histogram* New(std::string_view name, MetricTagDescriptor... fieldnames) -``` - -Create a new histogram object with the given metric name and tag names. -Example code to create a histogram metric: - -``` {.sourceCode .cpp} -auto h = Histogram::New( - "test_histogram", "string_tag", "int_tag", "bool_tag"); -``` - -Returns a pointer to Histogram object. - -### record - -``` {.sourceCode .cpp} -void record(int64_t offset, Tags... tags) -``` - -Records a value in histogram stats. *offset* is the value to be -recorded. *tags* is a list of tag values to identify a specific -histogram. Example code to add a new value into histogram: - -``` {.sourceCode .cpp} -h->record(1, "test_tag", 7, true) -``` - -### resolve - -``` {.sourceCode .cpp} -SimpleHistogram resolve(Tags... f) -``` - -Resolves histogram object to a specific histogram for a list of tag -values. - -Returns a -[SimpleHistogram](#SimpleHistogram) -resolved from the histogram object, so that tag values do not need to be -specified in every record call. Example code: - -``` {.sourceCode .cpp} -auto simple_histogram = c->resolve("test_tag", 7, true); -``` - -### SimpleHistogram - -*SimpleHistogram* is resolved from a -[Histogram](#Histogram) object with -predetermined tag values. - -### record - -``` {.sourceCode .cpp} -void record(int64_t offset) -``` - -Records a value in histogram. *offset* is the value to be recorded. - -Data Structure --------------- - -### GrpcCallHandler class - -Base class for gRPC unary call handler. Subclass should specify response -message type and override necessary callbacks. Example code to create a -call handler using *google::protobuf::Empty* as response message. - -``` {.sourceCode .cpp} -class CallHandler : public GrpcCallHandler { - public: - void onSuccess(google::protobuf::Empty&& response) { - /* override onSuccess code */ - } - /* - more callbacks such as onFailure - */ -}; -``` - -To initialize a handler, pass in a pointer to -[context object](#context-object) that -this call should attach to. For example, passing in root context: - -``` {.sourceCode .cpp} -auto handler = std::make_unique(&root_context); -``` - -Note the context object needs to outlive the call. *handler* is also -used for WASM module to interact with the stream, such as canceling the -call. - -#### onSuccess - -``` {.sourceCode .cpp} -void onSuccess(Message&& response) -``` - -Called when the async gRPC request succeeds. No further callbacks will -be invoked. - -#### onFailure - -``` {.sourceCode .cpp} -void onFailure(GrpcStatus status, std::unique_ptr error_message) -``` - -Called when the async gRPC request fails. No further callbacks will be -invoked. *status* is returned grpc status. *error\_message* is the gRPC -status message or empty string if not present. - -#### cancel - -``` {.sourceCode .cpp} -void cancel() -``` - -Signals that the request should be cancelled. No further callbacks will -be invoked. - -### GrpcStreamHandler class - -Base class for gRPC stream handler. Subclass should specify stream -message type and override callbacks. Example code to create a stream -handler using *google::protobuf::Struct* as request message and -*google::protobuf::Any* response message: - -``` {.sourceCode .cpp} -class StreamHandler : public GrpcStreamHandler { - public: - void onReceive(google::protobuf::Any&& message) { - /* override onReceive code */ - } - /* - more callbacks such as onReceiveTrailingMetadata, onReceive, onRemoteClose - */ -}; -``` - -To initialize a handler, pass in a pointer to -[context object](#context-object) that -this stream should attach to. For example, passing in root context: - -``` {.sourceCode .cpp} -auto handler = std::make_unique(&root_context); -``` - -Note the context object needs to outlive the stream. *handler* is also -used for WASM module to interact with the stream, such as sending -message, closing and resetting stream. - -#### send - -``` {.sourceCode .cpp} -void send(const Request& message, bool end_of_stream) -``` - -Sends a request message to the stream. *end\_of\_stream* indicates if -this is the last message to send. With *end\_of\_stream* as true, -callbacks can still occur. - -#### close - -``` {.sourceCode .cpp} -void close() -``` - -Close the stream locally and send an empty DATA frame to the remote. No -further methods may be invoked on the stream object, but callbacks may -still be received until the stream is closed remotely. - -#### reset - -``` {.sourceCode .cpp} -void reset() -``` - -Close the stream locally and remotely (as needed). No further methods -may be invoked on the handler object and no further callbacks will be -invoked. - -#### onReceiveInitialMetadata - -``` {.sourceCode .cpp} -void onReceiveInitialMetadata() -``` - -Called when initial metadata is received. This will be called with empty -metadata on a trailers-only response, followed by -onReceiveTrailingMetadata() with the trailing metadata. . TODO: how to -get initial metadata? - -#### onReceiveTrailingMetadata - -``` {.sourceCode .cpp} -void onReceiveTrailingMetadata() -``` - -Called when trailing metadata is received. This will also be called on -non-Ok grpc-status stream termination. - -#### onReceive - -``` {.sourceCode .cpp} -void onReceive(Response&& message) -``` - -Called when an async gRPC message is received. - -#### onRemoteClose - -``` {.sourceCode .cpp} -void onRemoteClose(GrpcStatus status, std::unique_ptr error_message) -``` - -Called when the remote closes or an error occurs on the gRPC stream. The -stream is considered remotely closed after this invocation and no -further callbacks will be invoked. In addition, no further stream -operations are permitted. *status* is the grpc status, *error\_message* -is the gRPC status error message or empty string if not present. - -### WasmData - -WasmData is used to represent data passed into WASM module from host. It -is like string view, which holds a pointer to start of the data and a -size. It also supports several methods to access the data. - -#### data - -``` {.sourceCode .cpp} -const char* data() -``` - -Returns the start pointer of the data. - -#### view - -``` {.sourceCode .cpp} -std::string_view view() -``` - -Returns data as a string view constructed with the start pointer and the -size. - -#### toString - -``` {.sourceCode .cpp} -std::string toString() -``` - -Returns data as a string by converting the string view to string. - -#### pairs - -``` {.sourceCode .cpp} -std::vector> pairs() -``` - -Returns a vector of string view pair parsed from the data. - -#### proto - -``` {.sourceCode .cpp} -template T proto() -``` - -Returns a proto message parsed from the data based on the specified -proto type. - -Out of tree WASM module ------------------------ - -TODO: add an example about out of tree WASM module example - -VM Sharing ----------- - -TODO: add instruction about vm sharing From 631b9847354d495f64d2713a3ff4d9fb876ae3b9 Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Tue, 1 Aug 2023 02:13:21 +0000 Subject: [PATCH 3/9] Add FFI documentation to api_overview.md Signed-off-by: Michael Warres --- docs/api_overview.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/api_overview.md b/docs/api_overview.md index d0f692f..ed746e4 100644 --- a/docs/api_overview.md +++ b/docs/api_overview.md @@ -74,6 +74,7 @@ plugin: * `validateConfiguration`: May be called to validate the configuration data that will be passed to a plugin on startup, e.g. in the control plane. +* `onCreate`: called when context is created. * `onStart`: called on plugin start. * `onConfigure`: called on plugin start, and any time configuration subsequently changes. @@ -371,6 +372,22 @@ for representing and updating metrics: Metrics can be subdivided by tags, using similar structure to [Envoy statistics](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/observability/statistics). +## Foreign function interface (FFI) + +Hosts can make additional hostcalls available for plugins to call via the +foreign function interface. Plugin code can invoke such hostcalls via the +`proxy_call_foreign_function` hostcall defined in [proxy_wasm_externs.h], which +specifies the foreign hostcall to invoke by a string name. + +Hosts can also invoke callbacks for events outside of those defined by the +Proxy-Wasm spec, which are dispatched to the plugin via calls to +`ContextBase::onForeignFunction`. These calls use an integer function ID to +identify the foreign callback to invoke. + +With both foreign hostcalls and callbacks, the plugin and host must agree +out-of-band on what hostcalls/callbacks will be available. For API details, see +associated doc comments in [proxy_wasm_externs.h] and [proxy_wasm_api.h]. + ## Example Some of the concepts and APIs described above are illustrated by the example @@ -430,10 +447,9 @@ The main files containing the Proxy-Wasm C++ SDK API and implementation are listed below: * [proxy_wasm_api.h]: main SDK API definition and implementation +* [proxy_wasm_externs.h]: declarations for ABI-level hostcalls and callbacks * [proxy_wasm_common.h](../proxy_wasm_common.h): supporting types for the API * [proxy_wasm_enums.h](../proxy_wasm_enums.h): supporting enums for the API -* [proxy_wasm_externs.h](../proxy_wasm_externs.h): declarations for ABI-level - hostcalls and callbacks * [proxy_wasm_intrinsics.js](../proxy_wasm_intrinsics.js): list of Proxy-Wasm ABI hostcalls, for use by [Emscripten](https://emscripten.org) * [proxy_wasm_intrinsics.proto](../proxy_wasm_intrinsics.proto): protobuf types @@ -451,4 +467,5 @@ listed below: [HTTP callouts]: #http-callouts [gRPC callouts]: #grpc-callouts [proxy_wasm_api.h]: ../proxy_wasm_api.h +[proxy_wasm_externs.h]: ../proxy_wasm_externs.h [http_wasm_example.cc]: ../example/http_wasm_example.cc From 9efbdfcb1232e9b8322e26ecd0c66ad62a5a7628 Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Tue, 1 Aug 2023 02:13:46 +0000 Subject: [PATCH 4/9] Add doc comments to header files Signed-off-by: Michael Warres --- proxy_wasm_api.h | 652 +++++++++++++++++++++++++++++++++++++++----- proxy_wasm_common.h | 66 +++-- proxy_wasm_enums.h | 73 ++++- 3 files changed, 697 insertions(+), 94 deletions(-) diff --git a/proxy_wasm_api.h b/proxy_wasm_api.h index 166b49c..fed7bf3 100644 --- a/proxy_wasm_api.h +++ b/proxy_wasm_api.h @@ -34,6 +34,8 @@ #include #include +// Macro to log a message and abort the plugin if the given value is not +// `WasmResult::Ok`. #define CHECK_RESULT(_c) \ do { \ if ((_c) != WasmResult::Ok) { \ @@ -58,6 +60,7 @@ class ProxyException : std::runtime_error { }; #endif +// Functions to log messages at various log levels. inline WasmResult logTrace(std::string_view logMessage) { return proxy_log(LogLevel::trace, logMessage.data(), logMessage.size()); } @@ -76,14 +79,21 @@ inline WasmResult logError(std::string_view logMessage) { inline WasmResult logCritical(std::string_view logMessage) { return proxy_log(LogLevel::critical, logMessage.data(), logMessage.size()); } -inline void logAbort(std::string_view logMessag) { - logCritical(logMessag); + +// Logs a message at `LogLevel::critical` and aborts plugin execution. +inline void logAbort(std::string_view logMessage) { + logCritical(logMessage); abort(); } +// Macro to log a message at the given log level with source file, line number, +// and function name included in the log message. #define LOG(_level, ...) \ log##_level(std::string("[") + __FILE__ + ":" + std::to_string(__LINE__) + \ "]::" + __FUNCTION__ + "() " + __VA_ARGS__) + +// Macros to log messages at various log levels with source file, line number, +// and function name included in the log message. #define LOG_TRACE(...) LOG(Trace, __VA_ARGS__) #define LOG_DEBUG(...) LOG(Debug, __VA_ARGS__) #define LOG_INFO(...) LOG(Info, __VA_ARGS__) @@ -94,13 +104,21 @@ inline void logAbort(std::string_view logMessag) { // Buffers coming into the WASM filter. class WasmData { public: + // Constructs a buffer that owns `size` bytes starting at `data`. WasmData(const char *data, size_t size) : data_(data), size_(size) {} + // Frees buffer data. ~WasmData() { ::free(const_cast(data_)); } + // Returns pointer to the start of the buffer; const char *data() { return data_; } + // Returns the size of the buffer in bytes. size_t size() { return size_; } + // Returns buffer data in the form of a string_view. std::string_view view() { return {data_, size_}; } + // Returns a string copy of buffer data. std::string toString() { return std::string(view()); } + // Returns a series of string pairs decoded from and backed by the buffer. std::vector> pairs(); + // Returns a protobuf of type T parsed from buffer contents. template T proto() { T p; p.ParseFromArray(data_, size_); @@ -138,6 +156,8 @@ inline std::vector> WasmData::pair return result; } +// Returns the number of bytes used by the marshalled representation of +// `result`. template size_t pairsSize(const Pairs &result) { size_t size = 4; // number of headers for (auto &p : result) { @@ -148,6 +168,7 @@ template size_t pairsSize(const Pairs &result) { return size; } +// Marshals `result` to the memory buffer `buffer`. template void marshalPairs(const Pairs &result, char *buffer) { char *b = buffer; *reinterpret_cast(b) = result.size(); @@ -168,6 +189,8 @@ template void marshalPairs(const Pairs &result, char *buffer) { } } +// Marshals `pairs` to a newly allocated memory buffer, returning the address of +// the buffer and its size in bytes via the `ptr` and `size_ptr` out-params. template void exportPairs(const Pairs &pairs, const char **ptr, size_t *size_ptr) { if (pairs.empty()) { *ptr = nullptr; @@ -181,12 +204,14 @@ template void exportPairs(const Pairs &pairs, const char **ptr, *ptr = buffer; } +// Hasher for pairs. struct PairHash { template std::size_t operator()(const std::pair &x) const { return std::hash()(x.first) + std::hash()(x.second); } }; +// Hasher for three-element tuples. struct Tuple3Hash { template std::size_t operator()(const std::tuple &x) const { @@ -195,19 +220,28 @@ struct Tuple3Hash { } }; +// Type for representing HTTP headers or metadata as string pairs. using HeaderStringPairs = std::vector>; +// Superclass for handlers that receive events for unary gRPC callouts initiated +// by `RootContext::grpcCallHandler`. class GrpcCallHandlerBase { public: GrpcCallHandlerBase() {} virtual ~GrpcCallHandlerBase() {} + // Returns the `RootContext` associated with the gRPC call. RootContext *context() { return context_; } + // Cancels the gRPC call. void cancel(); + // Returns the token used to identify the gRPC call. uint32_t token() { return token_; } + // Callback invoked on gRPC call success. `body_size` indicates the size in + // bytes of the gRPC response body. virtual void onSuccess(size_t body_size) = 0; + // Callback invoked on gRPC call failure. `status` conveys gRPC call status. virtual void onFailure(GrpcStatus status) = 0; private: @@ -217,32 +251,54 @@ class GrpcCallHandlerBase { uint32_t token_; }; +// Superclass for handlers that receive events for unary gRPC callouts initiated +// by `RootContext::grpcCallHandler`, templated on protobuf message type. template class GrpcCallHandler : public GrpcCallHandlerBase { public: GrpcCallHandler() : GrpcCallHandlerBase() {} virtual ~GrpcCallHandler() {} + // Callback invoked on gRPC call success. `body_size` indicates the size in + // bytes of the gRPC response body. virtual void onSuccess(size_t body_size) = 0; }; +// Superclass for handlers that receive events for streaming gRPC callouts +// initiated by `RootContext::grpcStreamHandler`. class GrpcStreamHandlerBase { public: GrpcStreamHandlerBase() {} virtual ~GrpcStreamHandlerBase() {} + // Returns the `RootContext` associated with the gRPC call. RootContext *context() { return context_; } - // NB: with end_of_stream == true, callbacks can still occur: reset() to - // prevent further callbacks. + // Sends `message` over the gRPC stream. `end_of_stream` indicates whether + // this is the last message to be written on the stream. + // + // Note that even with `end_of_stream == true`, callbacks can still + // occur. Call `reset` to prevent further callbacks. WasmResult send(std::string_view message, bool end_of_stream); - void close(); // NB: callbacks can still occur: reset() to prevent further - // callbacks. + // Closes the gRPC stream. + // + // Note that callbacks can still occur after this method is called. Call + // `reset` to prevent further callbacks. + void close(); + // Resets the handler and prevents further callbacks. void reset(); + // Returns the token used to identify the gRPC call. uint32_t token() { return token_; } + // Callback invoked when gRPC initial metadata is received. virtual void onReceiveInitialMetadata(uint32_t /* headers */) {} + // Callback invoked when gRPC trailing metadata is recevied. virtual void onReceiveTrailingMetadata(uint32_t /* trailers */) {} + // Callback invoked when gRPC stream data is received. `body_size` gives the + // number of bytes received. The actual bytes can be retrieved by calling + // `getBufferBytes` with `WasmBufferType::GrpcReceiveBuffer`. virtual void onReceive(size_t body_size) = 0; + // Callback invoked when the remote peer closed the gRPC stream. `status` + // gives the gRPC call status. virtual void onRemoteClose(GrpcStatus status) = 0; protected: @@ -256,12 +312,17 @@ class GrpcStreamHandlerBase { uint32_t token_; }; +// Superclass for handlers that receive events for streaming gRPC callouts +// initiated by `RootContext::grpcStreamHandler`, templated on protobuf message +// types. template class GrpcStreamHandler : public GrpcStreamHandlerBase { public: GrpcStreamHandler() : GrpcStreamHandlerBase() {} virtual ~GrpcStreamHandler() {} + // Sends `message` over the gRPC stream. `end_of_stream` indicates whether + // this is the last message to be written on the stream. WasmResult send(const Request &message, bool end_of_stream) { std::string output; if (!message.SerializeToString(&output)) { @@ -272,93 +333,233 @@ class GrpcStreamHandler : public GrpcStreamHandlerBase { return WasmResult::Ok; } + // Callback invoked when gRPC stream data is received. `body_size` gives the + // number of bytes received. The actual bytes can be retrieved by calling + // `getBufferBytes` with `WasmBufferType::GrpcReceiveBuffer`. virtual void onReceive(size_t body_size) = 0; }; // Behavior supported by all contexts. class ContextBase { public: + // Constructs a context identified by `id` in underlying ABI calls. explicit ContextBase(uint32_t id) : id_(id) {} virtual ~ContextBase() {} + // Returns numeric ID that identifies this context in ABI calls. uint32_t id() { return id_; } - // Make this context the effective context for calls out of the VM. + // Makes this context the effective context for calls out of the VM. WasmResult setEffectiveContext(); + // If this context is a root context, return it as a `RootContext`, else + // return nullptr. virtual RootContext *asRoot() { return nullptr; } + + // If this context is a stream context, return it as a `Context`, else return + // nullptr. virtual Context *asContext() { return nullptr; } + // Callback invoked when the context is created. virtual void onCreate() {} + + // Callback invoked when the host is done with a context. Returning true + // indicates that the host can proceed to delete the context and perform other + // cleanup. Returning false indicates that the context is still being used, + // and the plugin will call `RootContext::done` later to inform the host that + // the context can be deleted. virtual bool onDoneBase() = 0; - // Called on Stream Context after onDone when logging is requested or called on Root Context - // if so requested. + + // Callback invoked after the host is done with a context, but before deleting + // it, when either `onDoneBase` has returned true, or `the plugin has called + // RootContext::done`. This callback can be used for generating log entries. virtual void onLog() {} - // Called to indicate that no more calls will come and this context is being - // deleted. + + // Callback invoked when the host is releasing its state associated with the + // context. No further callbacks will be invoked on the context after this + // callback. virtual void onDelete() {} // Called when the stream or VM is being deleted. - // Called when a foreign function event arrives. + + // Callback invoked when a foreign function event + // arrives. `foreign_function_id` indicates the event type, which is some + // value agreed upon out-of-band by host and plugin. `data_size` gives the + // size of argument data for the call, in bytes. This argument data can be + // retrieved by calling `getBufferBytes` with `WasmBufferType::CallData`. virtual void onForeignFunction(uint32_t /* foreign_function_id */, uint32_t /* data_size */) {} - // Return the log level configured for the "wasm" logger in the host + // Returns the host's currently configured log level via the `level` + // out-param. WasmResult getLogLevel(LogLevel *level) { return proxy_get_log_level(level); } + // Type alias for callbacks that are invoked when an outbound HTTP call + // initiated via `RootContext::httpCall` completes. `num_headers` gives the + // number of response header fields, `body_size` gives the size of the + // response body in bytes, and `num_trailers` gives the number of response + // trailer fields. using HttpCallCallback = - std::function; // headers, body_size, trailers + std::function; + + // Type alias for callbacks that are invoked when an outbound gRPC call + // initiated via `RootContext::grpcSimpleCall` completes. `status` gives the + // gRPC call status, and `body_size` gives the size of the gRPC response body + // in bytes. using GrpcSimpleCallCallback = std::function; private: uint32_t id_; }; -// A context unique for each root_id for a use-case (e.g. filter) compiled into -// module. +// Behavior and operations supported by root contexts. class RootContext : public ContextBase { public: + // Constructs a root context identified by `id` in underlying ABI + // calls. `root_id` is a name that can be used to distinguish between + // different root contexts used by a plugin, if there are multiple. RootContext(uint32_t id, std::string_view root_id) : ContextBase(id), root_id_(root_id) {} ~RootContext() {} + // Returns ID that distinguishes this root context from others in use. std::string_view root_id() { return root_id_; } RootContext *asRoot() override { return this; } Context *asContext() override { return nullptr; } - // Can be used to validate the configuration (e.g. in the control plane). - // Returns false if the configuration is invalid. + // Callback that may be invoked (e.g. by the control plane) to validate plugin + // configuration. `configuration_size` gives the size of configuration data in + // bytes, which can be retrieved by calling `getBufferBytes` with + // `WasmBufferType::PluginConfiguration`. Returns true if the configuration is + // valid, or false if invalid. virtual bool validateConfiguration(size_t /* configuration_size */) { return true; } - // Called once when the VM loads and once when each hook loads and whenever - // configuration changes. Returns false if the configuration is invalid. + + // Callback invoked when the plugin is initially loaded, as well as whenever + // configuration changes. `configuration_size` gives the size of configuration + // data in bytes, which can be retrieved by calling `getBufferBytes` with + // `WasmBufferType::PluginConfiguration`. Returns true if the configuration is + // valid, or false if invalid. virtual bool onConfigure(size_t /* configuration_size */) { return true; } - // Called when each hook loads. Returns false if the configuration is - // invalid. + + // Callback invoked when the plugin is started. `vm_configuration_size` gives + // the size of VM configuration data in bytes, which can be retrieved by + // calling `getBufferBytes` with `WasmBufferType::VmConfiguration`. Returns + // true if the configuration is valid, or false if invalid. virtual bool onStart(size_t /* vm_configuration_size */) { return true; } - // Called when the timer goes off. + + // Callback invoked the timer configured by a previous call to + // `proxy_set_tick_period_milliseconds` goes off. virtual void onTick() {} - // Called when data arrives on a SharedQueue. - virtual void onQueueReady(uint32_t /* token */) {} - virtual bool onDone() { return true; } // Called when the VM is being torn down. - void done(); // Report that we are now done following returning false from onDone. + // Callback invoked when data arrives on a shared queue. `token` is a value + // that identifies the shared queue, previously established in a call to + // `registerSharedQueue` or `resolveSharedQueue`. Shared queue data can be + // retrieved by calling `dequeueSharedQueue` with the given `token` value. + virtual void onQueueReady(uint32_t /* token */) {} - // Low level HTTP/gRPC interface. + // Callback invoked when the plugin is being stopped. Returning true indicates + // that the host can proceed to delete the context and perform other + // cleanup. Returning false indicates that the context is still being used, + // and the plugin will call `RootContext::done` later to inform the host that + // the context can be deleted. + virtual bool onDone() { return true; } + + // Informs the host that it is safe to delete this context, following an + // earlier `onDone` callback on it that had returned false. + void done(); + + // Low-level callback invoked when the response for an outbound HTTP call + // initiated via `makeHttpCall` is received, or the HTTP call times + // out. Plugins that use the high-level `RootContext::httpCall` API to perform + // outbound HTTP calls should not override this method, as doing so + // effectively disables the response callback for that method. + // + // `token` associates the response with the corresponding `makeHttpCall` + // call. `headers` is the number of HTTP header fields, or 0 if the request + // timed out. `body_size` is the size of the response body in bytes, and + // `trailers` is the number of HTTP trailer fields. + // + // Header and trailer values can be fetched by calling `getHeaderMapValue` + // with `WasmHeaderMapType::HttpCallResponseHeaders` and + // `WasmHeaderMapType::HttpCallResponseTrailers`, respectively. Response body + // bytes can be fetched by calling `getBufferBytes` with + // `WasmBufferType::HttpCallResponseBody`. virtual void onHttpCallResponse(uint32_t token, uint32_t headers, size_t body_size, uint32_t trailers); + + // Low-level callback invoked when initial metadata for an outbound gRPC call + // initiated via `grpcCall` or `grpcStream` is received. Plugins that use the + // the high-level `RootContext::grpcSimpleCall` or + // `RootContext::grpcCallHandler` API to perform outbound gRPC calls should + // not override this method, as doing so effectively disables the response + // callbacks for those methods. + // + // `token` associates the received metadata with the corresponding `grpcCall` + // or `grpcStream` call. `headers` is the number of metadata fields received, + // which can be retrieved by calling `getHeaderMapValue` with + // `WasmHeaderMapType::GrpcReceiveInitialMetadata`. virtual void onGrpcReceiveInitialMetadata(uint32_t token, uint32_t headers); + + // Low-level callback invoked when trailing metadata for an outbound gRPC call + // initiated via `grpcCall` or `grpcStream` is received. Plugins that use the + // the high-level `RootContext::grpcSimpleCall` or + // `RootContext::grpcCallHandler` API to perform outbound gRPC calls should + // not override this method, as doing so effectively disables the response + // callbacks for those methods. + // + // `token` associates the received metadata with the corresponding `grpcCall` + // or `grpcStream` call. `trailers` is the number of metadata fields received, + // which can be retrieved by calling `getHeaderMapValue` with + // `WasmHeaderMapType::GrpcReceiveTrailingMetadata`. virtual void onGrpcReceiveTrailingMetadata(uint32_t token, uint32_t trailers); + + // Low-level callback invoked when response data for an outbound gRPC call + // initiated via `grpcCall` or `grpcStream` is received. Plugins that use the + // the high-level `RootContext::grpcSimpleCall` or + // `RootContext::grpcCallHandler` API to perform outbound gRPC calls should + // not override this method, as doing so effectively disables the response + // callbacks for those methods. + // + // `token` associates the received metadata with the corresponding `grpcCall` + // or `grpcStream` call. `body_size` is the number of response body bytes + // received, which can be retrieved by calling `getBuffer` with + // `WasmHeaderMapType::GrpcReceiveBuffer`. virtual void onGrpcReceive(uint32_t token, size_t body_size); + + // Low-level callback invoked when an outbound gRPC call initiated via + // `grpcCall` or `grpcStream` terminates. Plugins that use the the high-level + // `RootContext::grpcSimpleCall` or `RootContext::grpcCallHandler` API to + // perform outbound gRPC calls should not override this method, as doing so + // effectively disables the response callbacks for those methods. + // + // `token` associates the received metadata with the corresponding `grpcCall` + // or `grpcStream` call. `status` gives the gRPC status code for the call. virtual void onGrpcClose(uint32_t token, GrpcStatus status); - // Default high level HTTP/gRPC interface. NB: overriding the low level - // interface will disable this interface. Returns false on setup error. + // Initiates an outbound HTTP call to URI `uri`, sending `request_headers`, + // `request_body`, and `request_trailers` as the request headers, body, and + // trailers. `callback` is a callback that is invoked when the HTTP response + // is received, or the request times out. Returns `WasmResult::Ok` if the + // request is successfully sent. WasmResult httpCall(std::string_view uri, const HeaderStringPairs &request_headers, std::string_view request_body, const HeaderStringPairs &request_trailers, uint32_t timeout_milliseconds, HttpCallCallback callback); - // NB: the message is the response if status == OK and an error message - // otherwise. Returns false on setup error. + + // Initiates an outbound unary gRPC call to the service named by `service` and + // `service_name`, invoking method `method_name`. `initial_metadata` and + // `request` specify the initial metadata and request body for the + // call. `callback` is a callback that is invoked when the gRPC response is + // received, or the request times out. Returns `WasmResult::Ok` if the call is + // successfully sent. WasmResult grpcSimpleCall(std::string_view service, std::string_view service_name, std::string_view method_name, const HeaderStringPairs &initial_metadata, std::string_view request, uint32_t timeout_milliseconds, GrpcSimpleCallCallback callback); + + // Initiates an outbound unary gRPC call to the service named by `service` and + // `service_name`, invoking method `method_name`. `initial_metadata` and + // `request` specify the initial metadata and request body for the + // call. `success_callback` is a callback that is invoked when a successful + // gRPC response is received. `failure_callback` is a callback that is invoked + // if the gRPC call fails. Returns `WasmResult::Ok` if the call is + // successfully sent. WasmResult grpcSimpleCall(std::string_view service, std::string_view service_name, std::string_view method_name, const HeaderStringPairs &initial_metadata, std::string_view request, uint32_t timeout_milliseconds, @@ -374,12 +575,25 @@ class RootContext : public ContextBase { return grpcSimpleCall(service, service_name, method_name, initial_metadata, request, timeout_milliseconds, callback); } + + // Initiates an outbound unary gRPC call to the service named by `service` and + // `service_name`, invoking method `method_name`. `initial_metadata` and + // `request` specify the initial metadata and request body for the + // call. `handler` is a handler object that receives callbacks for gRPC call + // events such as success or failure. Returns `WasmResult::Ok` if the call is + // successfully sent. WasmResult grpcCallHandler(std::string_view service, std::string_view service_name, std::string_view method_name, const HeaderStringPairs &initial_metadata, std::string_view request, uint32_t timeout_milliseconds, std::unique_ptr handler); + #ifdef PROXY_WASM_PROTOBUF + // Initiates an outbound unary gRPC call to the service named by `service` and + // `service_name`, invoking method `method_name`. `initial_metadata` and + // `request` specify the initial metadata and request protobuf for the + // call. `callback` is a callback that is invoked when the gRPC response is + // received, or the request times out. Returns `WasmResult::Ok` if the call is successfully sent. WasmResult grpcSimpleCall(std::string_view service, std::string_view service_name, std::string_view method_name, const HeaderStringPairs &initial_metadata, const google::protobuf::MessageLite &request, @@ -391,6 +605,14 @@ class RootContext : public ContextBase { return grpcSimpleCall(service, service_name, method_name, initial_metadata, serialized_request, timeout_milliseconds, callback); } + + // Initiates an outbound unary gRPC call to the service named by `service` and + // `service_name`, invoking method `method_name`. `initial_metadata` and + // `request` specify the initial metadata and request protobuf for the + // call. `success_callback` is a callback that is invoked when a successful + // gRPC response is received. `failure_callback` is a callback that is invoked + // if the gRPC call fails. Returns `WasmResult::Ok` if the call is + // successfully sent. WasmResult grpcSimpleCall(std::string_view service, std::string_view service_name, std::string_view method_name, const HeaderStringPairs &initial_metadata, const google::protobuf::MessageLite &request, @@ -404,7 +626,13 @@ class RootContext : public ContextBase { return grpcSimpleCall(service, service_name, method_name, initial_metadata, serialized_request, timeout_milliseconds, success_callback, failure_callback); } - // Returns false on setup error. + + // Initiates an outbound unary gRPC call to the service named by `service` and + // `service_name`, invoking method `method_name`. `initial_metadata` and + // `request` specify the initial metadata and request protobuf for the + // call. `handler` is a handler object that receives callbacks for gRPC call + // events such as success or failure. Returns `WasmResult::Ok` if the call is + // successfully sent. WasmResult grpcCallHandler(std::string_view service, std::string_view service_name, std::string_view method_name, const HeaderStringPairs &initial_metadata, @@ -419,7 +647,13 @@ class RootContext : public ContextBase { timeout_milliseconds, std::move(handler)); } #endif - // Returns false on setup error. + + // Initiates an outbound streaming gRPC call to the service named by `service` + // and `service_name`, invoking method `method_name`. `initial_metadata` + // specifies the initial metadata for the call. `handler` is a handler object + // that can be used to send messages over the gRPC stream, and receives + // callbacks for gRPC call events such as receiving metadata and response + // messages. Returns `WasmResult::Ok` if the call is successfully sent. WasmResult grpcStreamHandler(std::string_view service, std::string_view service_name, std::string_view method_name, const HeaderStringPairs &initial_metadata, @@ -438,56 +672,158 @@ class RootContext : public ContextBase { std::unordered_map> grpc_streams_; }; +// Returns `RootContext` object for the root context named by `root_id`, or +// nullptr if none is found. RootContext *getRoot(std::string_view root_id); -// Context for a stream. The distinguished context id == 0 is used for -// non-stream calls. +// Behavior and operations supported by stream contexts. class Context : public ContextBase { public: + // Constructs a stream context identified by `id` in underlying ABI calls, + // associated with root context `root`. Context(uint32_t id, RootContext *root) : ContextBase(id), root_(root) {} virtual ~Context() {} + // Returns the root context associated with this stream context. RootContext *root() { return root_; } RootContext *asRoot() override { return nullptr; } Context *asContext() override { return this; } + // Callback invoked when a new TCP(-like) connection is established. Returns + // `FilterStatus` indicating whether processing of the connection should + // continue or pause until a later call to `continueDownstream`. virtual FilterStatus onNewConnection() { return FilterStatus::Continue; } - virtual FilterStatus onDownstreamData(size_t, bool) { return FilterStatus::Continue; } - virtual FilterStatus onUpstreamData(size_t, bool) { return FilterStatus::Continue; } - virtual void onDownstreamConnectionClose(CloseType) {} - virtual void onUpstreamConnectionClose(CloseType) {} - virtual FilterHeadersStatus onRequestHeaders(uint32_t, bool) { + // Callback invoked when new data is received from downstream on a TCP(-like) + // connection. `data_size` gives the number of bytes received, and + // `end_of_stream` indicates whether this is the last data from + // downstream. Data can be retrieved by calling `getBufferBytes` with + // `WasmBufferType::NetworkDownstreamData`. + // + // Returns `FilterStatus` indicating whether processing of the connection + // should continue or pause until a later call to `continueDownstream`. + virtual FilterStatus onDownstreamData(size_t /* data_size */, + bool /* end_of_stream */) { + return FilterStatus::Continue; + } + + // Callback invoked when new data is received from upstream on a TCP(-like) + // connection. `data_size` gives the number of bytes received, and + // `end_of_stream` indicates whether this is the last data from + // downstream. Data can be retrieved by calling `getBufferBytes` with + // `WasmBufferType::NetworkUpstreamData`. + // + // Returns `FilterStatus` indicating whether processing of the connection + // should continue or pause until a later call to `continueUpstream`. + virtual FilterStatus onUpstreamData(size_t /* data_size */, + bool /* end_of_stream */) { + return FilterStatus::Continue; + } + + // Callback invoked when the downstream direction of a TCP(-like) connection + // is closed. `close_type` indicates whether the connection was closed by the + // proxy or the remote peer. + virtual void onDownstreamConnectionClose(CloseType /* close_type */) {} + + // Callback invoked when the upstream direction of a TCP(-like) connection is + // closed. `close_type` indicates whether the connection was closed by the + // proxy or the remote peer. + virtual void onUpstreamConnectionClose(CloseType /* close_type */) {} + + // Callback invoked when HTTP request headers are received. `headers` is the + // number of header fields. `end_of_stream` indicates whether the request ends + // immediately after the headers. Request header values can be accessed and + // manipulated via `*RequestHeader*` hostcalls, or by specifying + // `WasmHeaderMapType::RequestHeaders` in `*HeaderMap*` hostcalls. + // + // Returns `FilterHeadersStatus` indicating whether processing of the + // connection should continue or pause until a later call to + // `continueRequest`. + virtual FilterHeadersStatus onRequestHeaders(uint32_t /* headers */, bool /* end_of_stream */) { return FilterHeadersStatus::Continue; } - virtual FilterMetadataStatus onRequestMetadata(uint32_t) { + + // Callback invoked when request metadata is received. `elements` is the + // number of metadata entries. Metadata values are not currently accessible. + virtual FilterMetadataStatus onRequestMetadata(uint32_t /* elements */) { return FilterMetadataStatus::Continue; } + + // Callback invoked when HTTP request body data is + // received. `body_buffer_length` is the size of body data in + // bytes. `end_of_stream` indicates whether the request ends immediately after + // the request body data. Request body bytes can be retrieved by calling + // `getBufferBytes` with `WasmBufferType::HttpRequestBody`. + // + // Returns `FilterDataStatus` indicating whether processing of the connection + // should continue or pause until a later call to `continueRequest`. virtual FilterDataStatus onRequestBody(size_t /* body_buffer_length */, bool /* end_of_stream */) { return FilterDataStatus::Continue; } - virtual FilterTrailersStatus onRequestTrailers(uint32_t) { + + // Callback invoked when HTTP request trailers are received. `trailers` is the + // number of trailer fields. Request trailer values can be accessed and + // manipulated via `*RequestTrailer*` hostcalls, or by specifying + // `WasmHeaderMapType::RequestTrailers` in `*HeaderMap*` hostcalls. + // + // Returns `FilterTrailersStatus` indicating whether processing of the + // connection should continue or pause until a later call to + // `continueRequest`. + virtual FilterTrailersStatus onRequestTrailers(uint32_t /* trailers */) { return FilterTrailersStatus::Continue; } - virtual FilterHeadersStatus onResponseHeaders(uint32_t, bool) { + + // Callback invoked when HTTP response headers are received. `headers` is the + // number of header fields. `end_of_stream` indicates whether the response + // ends immediately after the headers. Response header values can be accessed + // and manipulated via `*ResponseHeader*` hostcalls, or by specifying + // `WasmHeaderMapType::ResponseHeaders` in `*HeaderMap*` hostcalls. + // + // Returns `FilterHeadersStatus` indicating whether processing of the + // connection should continue or pause until a later call to + // `continueResponse`. + virtual FilterHeadersStatus onResponseHeaders(uint32_t /* headers */, bool /* end_of_stream */) { return FilterHeadersStatus::Continue; } - virtual FilterMetadataStatus onResponseMetadata(uint32_t) { + + // Callback invoked when response metadata is received. `elements` is the + // number of metadata entries. Metadata values are not currently accessible. + virtual FilterMetadataStatus onResponseMetadata(uint32_t /* elements */) { return FilterMetadataStatus::Continue; } + + // Callback invoked when HTTP response body data is + // received. `body_buffer_length` is the size of body data in + // bytes. `end_of_stream` indicates whether the response ends immediately + // after the response body data. Response body bytes can be retrieved by + // calling `getBufferBytes` with `WasmBufferType::HttpResponseBody`. + // + // Returns `FilterDataStatus` indicating whether processing of the connection + // should continue or pause until a later call to `continueResponse`. virtual FilterDataStatus onResponseBody(size_t /* body_buffer_length */, bool /* end_of_stream */) { return FilterDataStatus::Continue; } - virtual FilterTrailersStatus onResponseTrailers(uint32_t) { + + // Callback invoked when HTTP response trailers are received. `trailers` is + // the number of trailer fields. Response trailer values can be accessed and + // manipulated via `*ResponseTrailer*` hostcalls, or by specifying + // `WasmHeaderMapType::ResponseTrailers` in `*HeaderMap*` hostcalls. + // + // Returns `FilterTrailersStatus` indicating whether processing of the + // connection should continue or pause until a later call to + // `continueResponse`. + virtual FilterTrailersStatus onResponseTrailers(uint32_t /* trailers */) { return FilterTrailersStatus::Continue; } - virtual void onDone() {} // Called when the stream has completed. + + // Callback invoked when the stream has completed. + virtual void onDone() {} private: - // For stream Contexts, onDone always returns true. + // For stream contexts, onDone always returns true. bool onDoneBase() override { onDone(); return true; @@ -496,38 +832,62 @@ class Context : public ContextBase { RootContext *root_{}; }; -// Returns nullptr if the Context no longer exists (i.e. the stream has been -// destroyed). +// Returns stream context object associated with `context_id`, or nullptr if +// none exists (e.g. the stream has been destroyed, or the context associated +// with `context_id` is not a stream context). Context *getContext(uint32_t context_id); + +// Returns root context object associated with `context_id`, or nullptr if none +// exists (e.g. the context associated with `context_id` is not a root context). RootContext *getRootContext(uint32_t context_id); + +// Returns stream or root context object associated with `context_id`, or +// nullptr if none exists. ContextBase *getContextBase(uint32_t context_id); +// Factory function type for root contexts. using RootFactory = std::function(uint32_t id, std::string_view root_id)>; + +// Factory function type for stream contexts. using ContextFactory = std::function(uint32_t id, RootContext *root)>; -// Create a factory from a class name. +// Convenience macro to create a root context factory for the `RootContext` +// subclass named by `_c`. #define ROOT_FACTORY(_c) \ [](uint32_t id, std::string_view root_id) -> std::unique_ptr { \ return std::make_unique<_c>(id, root_id); \ } + +// Convenience macro to create a stream context factory for the `RootContext` +// subclass named by `_c`. #define CONTEXT_FACTORY(_c) \ [](uint32_t id, RootContext *root) -> std::unique_ptr { \ return std::make_unique<_c>(id, root); \ } -// Register Context factory. -// e.g. static RegisterContextFactory -// register_MyContext(CONTEXT_FACTORY(MyContext)); +// Struct to instantiate in order to register root context and/or stream context +// factory functions. struct RegisterContextFactory { + // Registers `context_factory` for creating stream contexts and `root_factory` + // for creating a root context for the plugin indicated by `root_id`. RegisterContextFactory(ContextFactory context_factory, RootFactory root_factory, std::string_view root_id = ""); + + // Registers `root_factory` for creating a root context for the plugin + // indicated by `root_id`. explicit RegisterContextFactory(RootFactory root_factory, std::string_view root_id = "") : RegisterContextFactory(nullptr, root_factory, root_id) {} + + // Registers `context_factory` for creating stream contexts for the plugin + // indicated by `root_id`. explicit RegisterContextFactory(ContextFactory context_factory, std::string_view root_id = "") : RegisterContextFactory(context_factory, nullptr, root_id) {} }; +// Returns the status code and status message of the response to an outbound +// HTTP or gRPC call. Can be called from the `RootContext::onHttpCallResponse` +// or `RootContext::onGrpcClose` callbacks. inline std::pair getStatus() { uint32_t code = 0; const char *value_ptr = nullptr; @@ -536,7 +896,8 @@ inline std::pair getStatus() { return std::make_pair(code, std::make_unique(value_ptr, value_size)); } -// Generic selector +// Returns value of the property named by the concatenation of `parts`, or +// nullopt if no property value is found. inline std::optional getProperty(const std::initializer_list &parts) { size_t size = 0; @@ -563,6 +924,8 @@ getProperty(const std::initializer_list &parts) { return std::make_unique(value_ptr, value_size); } +// Returns value of the property named by the concatenation of `parts`, or +// nullopt if no property value is found. template inline std::optional getProperty(const std::vector &parts) { size_t size = 0; for (auto part : parts) { @@ -588,10 +951,13 @@ template inline std::optional getProperty(const std::v return std::make_unique(value_ptr, value_size); } -// Generic property reader for basic types: int64, uint64, double, bool -// Durations are represented as int64 nanoseconds. -// Timestamps are represented as int64 Unix nanoseconds. -// Strings and bytes are represented as std::string. +// Returns value of the property named by the concatenation of `parts` in +// out-param `out`, which can be of type int64, uint64, double, or bool. Returns +// true if the property was successfully fetched and stored in `out`, or false +// otherwise. +// +// Durations are represented as int64 nanoseconds. Timestamps are represented as +// int64 Unix nanoseconds. template inline bool getValue(const std::initializer_list &parts, T *out) { auto buf = getProperty(parts); @@ -602,7 +968,9 @@ inline bool getValue(const std::initializer_list &parts, T *ou return true; } -// Specialization for bytes and string values +// Returns value of the property named by the concatenation of `parts` in string +// out-param `out`. Returns true if the property was successfully fetched and +// stored in `out`, or false otherwise. template <> inline bool getValue(const std::initializer_list &parts, std::string *out) { @@ -614,6 +982,13 @@ inline bool getValue(const std::initializer_list return true; } +// Returns value of the property named by the concatenation of `parts` in +// out-param `out`, which can be of type int64, uint64, double, or bool. Returns +// true if the property was successfully fetched and stored in `out`, or false +// otherwise. +// +// Durations are represented as int64 nanoseconds. Timestamps are represented as +// int64 Unix nanoseconds. template inline bool getValue(const std::vector &parts, T *out) { auto buf = getProperty(parts); if (!buf.has_value() || buf.value()->size() != sizeof(T)) { @@ -623,6 +998,9 @@ template inline bool getValue(const std::vector &par return true; } +// Returns value of the property named by the concatenation of `parts` in string +// out-param `out`. Returns true if the property was successfully fetched and +// stored in `out`, or false otherwise. template <> inline bool getValue(const std::vector &parts, std::string *out) { @@ -634,6 +1012,9 @@ inline bool getValue(const std::vector &p return true; } +// Returns value of the property named by the concatenation of `parts` in string +// out-param `out`. Returns true if the property was successfully fetched and +// stored in `out`, or false otherwise. template <> inline bool getValue(const std::vector &parts, std::string *out) { @@ -645,7 +1026,9 @@ inline bool getValue(const std::vector inline bool getMessageValue(const std::initializer_list &parts, T *value_ptr) { auto buf = getProperty(parts); @@ -659,6 +1042,9 @@ inline bool getMessageValue(const std::initializer_list &parts return value_ptr->ParseFromArray(buf.value()->data(), buf.value()->size()); } +// Returns value of the property named by the concatenation of `parts` in +// protobuf message out-param `out`. Returns true if the property was +// successfully fetched and stored in `out`, or false otherwise. template inline bool getMessageValue(const std::vector &parts, T *value_ptr) { auto buf = getProperty(parts); @@ -672,28 +1058,56 @@ inline bool getMessageValue(const std::vector &parts, T *value_ptr) { return value_ptr->ParseFromArray(buf.value()->data(), buf.value()->size()); } +// Sets property named by `key` to value `value`. Returns `WasmResult::Ok` on +// success, or `WasmResult::NotFound` if no such property exists. inline WasmResult setFilterState(std::string_view key, std::string_view value) { return static_cast( proxy_set_property(key.data(), key.size(), value.data(), value.size())); } +// Sets property named by `key` to string value `s`. Returns `WasmResult::Ok` on +// success, or `WasmResult::NotFound` if no such property exists. inline WasmResult setFilterStateStringValue(std::string_view key, std::string_view s) { return setFilterState(key, s); } -// Continue/Respond/Route +// Resumes processing of a TCP(-like) connection after a previous callback to +// `Context::onDownstreamData` returned `FilterStatus::StopIteration`. inline WasmResult continueDownstream() { return proxy_continue_stream(WasmStreamType::Downstream); } + +// Resumes processing of paused TCP(-like) connection after a previous callback +// to `Context::onUpstreamData` returned `FilterStatus::StopIteration`. inline WasmResult continueUpstream() { return proxy_continue_stream(WasmStreamType::Upstream); } +// Closes TCP(-like) connection in the downstream direction. inline WasmResult closeDownstream() { return proxy_close_stream(WasmStreamType::Downstream); } + +// Closes TCP(-like) connection in the upstream direction. inline WasmResult closeUpstream() { return proxy_close_stream(WasmStreamType::Upstream); } +// Resumes processing of an HTTP request after a previous callback to +// `Context::onRequestHeaders`, `Context::onRequestBody`, or +// `Context::onRequestTrailers` returned a `Filter*Status` value that paused +// request processing. inline WasmResult continueRequest() { return proxy_continue_stream(WasmStreamType::Request); } + +// Resumes processing of an HTTP response after a previous callback to +// `Context::onResponseHeaders`, `Context::onResponseBody`, or +// `Context::onResponseTrailers` returned a `Filter*Status` value that paused +// response processing. inline WasmResult continueResponse() { return proxy_continue_stream(WasmStreamType::Response); } +// Terminates processing of an HTTP request. inline WasmResult closeRequest() { return proxy_close_stream(WasmStreamType::Request); } + +// Terminates processing of an HTTP response. inline WasmResult closeResponse() { return proxy_close_stream(WasmStreamType::Response); } +// Sends an HTTP response. `body` is the response body, `response_code` and +// `response_code_details` specify the response code and details to send, +// `additional_response_headers` gives response headers to send, and +// `grpc_status` specifies a gRPC status to return, if responding to an HTTP +// request that is gRPC. inline WasmResult sendLocalResponse(uint32_t response_code, std::string_view response_code_details, std::string_view body, const HeaderStringPairs &additional_response_headers, @@ -708,7 +1122,11 @@ inline WasmResult sendLocalResponse(uint32_t response_code, std::string_view res return result; } -// SharedData +// Fetches shared value identified by `key`, storing in out-param `value`. If +// `cas` is non-null, it is set to a compare-and-swap value that can be passed +// in a subsequent call to `setSharedData`, for atomic updates. Returns +// `WasmResult::Ok` if the value was successfully fetched, or +// `WasmResult::NotFound` if there is no shared data value for `key`. inline WasmResult getSharedData(std::string_view key, WasmDataPtr *value, uint32_t *cas = nullptr) { uint32_t dummy_cas; const char *value_ptr = nullptr; @@ -723,10 +1141,15 @@ inline WasmResult getSharedData(std::string_view key, WasmDataPtr *value, uint32 return WasmResult::Ok; } +// Sets shared data identified by `key` to value `value` if `cas` is 0 or +// matches the host's current compare-and-swap value for the entry. Returns +// `WasmResult::Ok` if the value was successfully set. inline WasmResult setSharedData(std::string_view key, std::string_view value, uint32_t cas = 0) { return proxy_set_shared_data(key.data(), key.size(), value.data(), value.size(), cas); } +// Returns shared value identified by `key`. If there is no shared data value +// for `key`, aborts the plugin by calling `logAbort`. inline WasmDataPtr getSharedDataValue(std::string_view key, uint32_t *cas = nullptr) { WasmDataPtr data; auto result = getSharedData(key, &data, cas); @@ -736,21 +1159,33 @@ inline WasmDataPtr getSharedDataValue(std::string_view key, uint32_t *cas = null return data; } -// SharedQueue +// Registers a shared queue under the name `queue_name`, setting out-param +// `token` to a value that can be used to enqueue or dequeue items from the +// shared queue. If there is already a shared queue registered under +// `queue_name`, the call opens the existing queue. Returns `WasmResult::Ok` on +// success. inline WasmResult registerSharedQueue(std::string_view queue_name, uint32_t *token) { return proxy_register_shared_queue(queue_name.data(), queue_name.size(), token); } +// Resolves an existing shared queue under the name `queue_name` with VM ID +// `vm_id`, setting out-param `token` to a value that can be used to enqueue or +// dequeue items from the shared queue. Returns `WasmResult::Ok` on success. inline WasmResult resolveSharedQueue(std::string_view vm_id, std::string_view queue_name, uint32_t *token) { return proxy_resolve_shared_queue(vm_id.data(), vm_id.size(), queue_name.data(), queue_name.size(), token); } +// Enqueues `data` on the shared queue indicated by `token`. Returns +// `WasmResult::Ok` on success. inline WasmResult enqueueSharedQueue(uint32_t token, std::string_view data) { return proxy_enqueue_shared_queue(token, data.data(), data.size()); } +// Dequeues an item from the shared queue indicated by `token`, storing it in +// out-param `data`. Returns `WasmResult::Ok` on success, or `WasmResult::Empty` +// if there is no item available to dequeue. inline WasmResult dequeueSharedQueue(uint32_t token, WasmDataPtr *data) { const char *data_ptr = nullptr; size_t data_size = 0; @@ -759,12 +1194,16 @@ inline WasmResult dequeueSharedQueue(uint32_t token, WasmDataPtr *data) { return result; } -// Headers/Trailers +// Adds header field with name `key` and value `value` to the header map +// indicated by `type`. Returns `WasmResult::Ok` on success. inline WasmResult addHeaderMapValue(WasmHeaderMapType type, std::string_view key, std::string_view value) { return proxy_add_header_map_value(type, key.data(), key.size(), value.data(), value.size()); } +// Returns value for the header field named by `key` in the header map indicated +// by `type`, or an empty `WasmData` if no such field is present in the header +// map. inline WasmDataPtr getHeaderMapValue(WasmHeaderMapType type, std::string_view key) { const char *value_ptr = nullptr; size_t value_size = 0; @@ -772,15 +1211,23 @@ inline WasmDataPtr getHeaderMapValue(WasmHeaderMapType type, std::string_view ke return std::make_unique(value_ptr, value_size); } +// Replaces header field with name `key` in the header map indicated by `type, +// setting its value to `value`, or adds the header field if it was not +// previously present. Returns `WasmResult::Ok` on success. inline WasmResult replaceHeaderMapValue(WasmHeaderMapType type, std::string_view key, std::string_view value) { return proxy_replace_header_map_value(type, key.data(), key.size(), value.data(), value.size()); } +// Removes header field with name `key` from the header map indicated by +// `type. Returns `WasmResult::Ok` on success, which includes the case where no +// such header field was present in the header map. inline WasmResult removeHeaderMapValue(WasmHeaderMapType type, std::string_view key) { return proxy_remove_header_map_value(type, key.data(), key.size()); } +// Returns all header fields for the header map indicated by `type`. Header +// fields can be accessed by calling `WasmData::pairs` on the return value. inline WasmDataPtr getHeaderMapPairs(WasmHeaderMapType type) { const char *ptr = nullptr; size_t size = 0; @@ -788,6 +1235,8 @@ inline WasmDataPtr getHeaderMapPairs(WasmHeaderMapType type) { return std::make_unique(ptr, size); } +// Sets all header fields for the header map indicated by `type` to the entries +// contained in `pairs`. Returns `WasmResult::Ok` on success. inline WasmResult setHeaderMapPairs(WasmHeaderMapType type, const HeaderStringPairs &pairs) { const char *ptr = nullptr; size_t size = 0; @@ -797,10 +1246,15 @@ inline WasmResult setHeaderMapPairs(WasmHeaderMapType type, const HeaderStringPa return result; } +// Fetches the total size in bytes of all header fields in the header map +// indicated by `type`, storing the result in out-param `size`. Returns +// `WasmResult::Ok` on success. inline WasmResult getHeaderMapSize(WasmHeaderMapType type, size_t *size) { return proxy_get_header_map_size(type, size); } +// Convenience functions for `*HeaderMap*` hostcalls with +// `WasmHeaderMapType::RequestHeaders`. inline WasmResult addRequestHeader(std::string_view key, std::string_view value) { return addHeaderMapValue(WasmHeaderMapType::RequestHeaders, key, value); } @@ -823,6 +1277,8 @@ inline WasmResult getRequestHeaderSize(size_t *size) { return getHeaderMapSize(WasmHeaderMapType::RequestHeaders, size); } +// Convenience functions for `*HeaderMap*` hostcalls with +// `WasmHeaderMapType::RequestTrailers`. inline WasmResult addRequestTrailer(std::string_view key, std::string_view value) { return addHeaderMapValue(WasmHeaderMapType::RequestTrailers, key, value); } @@ -845,6 +1301,8 @@ inline WasmResult getRequestTrailerSize(size_t *size) { return getHeaderMapSize(WasmHeaderMapType::RequestTrailers, size); } +// Convenience functions for `*HeaderMap*` hostcalls with +// `WasmHeaderMapType::ResponseHeaders`. inline WasmResult addResponseHeader(std::string_view key, std::string_view value) { return addHeaderMapValue(WasmHeaderMapType::ResponseHeaders, key, value); } @@ -867,6 +1325,8 @@ inline WasmResult getResponseHeaderSize(size_t *size) { return getHeaderMapSize(WasmHeaderMapType::ResponseHeaders, size); } +// Convenience functions for `*HeaderMap*` hostcalls with +// `WasmHeaderMapType::ResponseTrailers`. inline WasmResult addResponseTrailer(std::string_view key, std::string_view value) { return addHeaderMapValue(WasmHeaderMapType::ResponseTrailers, key, value); } @@ -889,7 +1349,8 @@ inline WasmResult getResponseTrailerSize(size_t *size) { return getHeaderMapSize(WasmHeaderMapType::ResponseTrailers, size); } -// Buffer +// Returns up to `length` bytes of data starting at offset `start` from the +// buffer of type `type`. Returns an empty `WasmData` on error. inline WasmDataPtr getBufferBytes(WasmBufferType type, size_t start, size_t length) { const char *ptr = nullptr; size_t size = 0; @@ -897,10 +1358,16 @@ inline WasmDataPtr getBufferBytes(WasmBufferType type, size_t start, size_t leng return std::make_unique(ptr, size); } +// Returns the number of bytes available in the buffer of type `type`, storing +// the result in out-param `size`. `flags` is currently ignored. Returns +// `WasmResult::Ok` on success, or `WasmResult::NotFound` if the indicated +// buffer is not currently accessible. inline WasmResult getBufferStatus(WasmBufferType type, size_t *size, uint32_t *flags) { return proxy_get_buffer_status(type, size, flags); } +// Sets `length` bytes from `data` into the buffer of type `type`, starting at +// offset `start` in the buffer. inline WasmResult setBuffer(WasmBufferType type, size_t start, size_t length, std::string_view data, size_t *new_size = nullptr) { auto result = proxy_set_buffer_bytes(type, start, length, data.data(), data.size()); @@ -909,8 +1376,9 @@ inline WasmResult setBuffer(WasmBufferType type, size_t start, size_t length, st return result; } -// HTTP - +// Marshals HTTP header fields in `headers` into a newly allocated memory +// buffer. Returns a pointer to the buffer in out-param `buffer_ptr`, and the +// size of the buffer in out-param `size_ptr`. inline void MakeHeaderStringPairsBuffer(const HeaderStringPairs &headers, void **buffer_ptr, size_t *size_ptr) { if (headers.empty()) { @@ -946,6 +1414,14 @@ inline void MakeHeaderStringPairsBuffer(const HeaderStringPairs &headers, void * *size_ptr = size; } +// Initiates an outbound HTTP call to URI `uri`, sending `request_headers`, +// `request_body`, and `request_trailers` as the request headers, body, and +// trailers. Sets out-param `token_ptr` to a token value that will be passed to +// a corresponding callback to `RootContext::onHttpCallResponse`. Returns +// `WasmResult::Ok` if the request is successfully sent. +// +// This is a low-level API that is less ergonomic than `RootContext::httpCall`, +// which most plugin code should use instead. inline WasmResult makeHttpCall(std::string_view uri, const HeaderStringPairs &request_headers, std::string_view request_body, const HeaderStringPairs &request_trailers, @@ -962,26 +1438,45 @@ inline WasmResult makeHttpCall(std::string_view uri, const HeaderStringPairs &re return result; } -// Low level metrics interface. - +// Defines a metric of type `type` named by `name`, storing in out-param +// `metric_id` an ID that can be used to set, increment, or fetch the metric +// value. Returns `WasmResult::Ok` on success. +// +// This is a low-level API; most plugin code should use the higher-level +// `Counter`, `Gauge`, or `Histogram` metrics utility classes. inline WasmResult defineMetric(MetricType type, std::string_view name, uint32_t *metric_id) { return proxy_define_metric(type, name.data(), name.size(), metric_id); } +// Increments the metric indicated by `metric_id` by `offset`. Returns +// `WasmResult::Ok` on success. +// +// This is a low-level API; most plugin code should use the higher-level +// `Counter`, `Gauge`, or `Histogram` metrics utility classes. inline WasmResult incrementMetric(uint32_t metric_id, int64_t offset) { return proxy_increment_metric(metric_id, offset); } +// Records measurement `value` to the metric indicated by `metric_id`. Returns +// `WasmResult::Ok` on success. +// +// This is a low-level API; most plugin code should use the higher-level +// `Counter`, `Gauge`, or `Histogram` metrics utility classes. inline WasmResult recordMetric(uint32_t metric_id, uint64_t value) { return proxy_record_metric(metric_id, value); } +// Stores the current value of the metric indicated by `metric_id` in out-param +// `value`. Returns `WasmResult::Ok` on success. +// +// This is a low-level API; most plugin code should use the higher-level +// `Counter`, `Gauge`, or `Histogram` metrics utility classes. inline WasmResult getMetric(uint32_t metric_id, uint64_t *value) { return proxy_get_metric(metric_id, value); } -// Higher level metrics interface. - +// Tag that distinguishes one time series of values for a given metric from +// others for the same metric. struct MetricTag { enum class TagType : uint32_t { String = 0, @@ -992,6 +1487,8 @@ struct MetricTag { TagType tagType; }; +// Base class for objects that represent metrics, which provides methods for +// defining metrics. struct MetricBase { MetricBase(MetricType t, const std::string &n) : type(t), name(n) {} MetricBase(MetricType t, const std::string &n, const std::vector &ts) @@ -1016,6 +1513,8 @@ struct MetricBase { std::string nameFromIdSlow(uint32_t id); }; +// Base class for objects that represent metrics, which provides methods for +// updating metric values. struct Metric : public MetricBase { Metric(MetricType t, const std::string &n) : MetricBase(t, n) {} Metric(MetricType t, const std::string &n, const std::vector &ts) @@ -1167,6 +1666,7 @@ template <> inline MetricTag toMetricTag(const MetricTagDescriptor &d) { return {std::string(d.name), MetricTag::TagType::Bool}; } +// Object representing a single counter metric. struct SimpleCounter { SimpleCounter(uint32_t id) : metric_id(id) {} @@ -1183,6 +1683,7 @@ struct SimpleCounter { uint32_t metric_id; }; +// Object representing a single gauge metric. struct SimpleGauge { SimpleGauge(uint32_t id) : metric_id(id) {} @@ -1196,6 +1697,7 @@ struct SimpleGauge { uint32_t metric_id; }; +// Object representing a single histogram metric. struct SimpleHistogram { SimpleHistogram(uint32_t id) : metric_id(id) {} @@ -1204,6 +1706,8 @@ struct SimpleHistogram { uint32_t metric_id; }; +// Counter metric with support for tags. Each unique combination of tag values +// resolves to an independently updatable SimpleCounter. template struct Counter : public MetricBase { static Counter *New(std::string_view name, MetricTagDescriptor... fieldnames); @@ -1253,6 +1757,8 @@ inline Counter *Counter::New(std::string_view name, std::vector({toMetricTag(descriptors)...})); } +// Gauge metric with support for tags. Each unique combination of tag values +// resolves to an independently updatable SimpleGauge. template struct Gauge : public MetricBase { static Gauge *New(std::string_view name, MetricTagDescriptor... fieldnames); @@ -1331,6 +1837,8 @@ template struct Histogram : public MetricBase { : MetricBase(MetricType::Histogram, name, tags) {} }; +// Histogram metric with support for tags. Each unique combination of tag values +// resolves to an independently updatable SimpleHistogram. template inline Histogram *Histogram::New(std::string_view name, MetricTagDescriptor... descriptors) { diff --git a/proxy_wasm_common.h b/proxy_wasm_common.h index dedc6b3..0787cd8 100644 --- a/proxy_wasm_common.h +++ b/proxy_wasm_common.h @@ -24,6 +24,8 @@ #include #include +// Return status of a Proxy-Wasm hostcall. Corresponds to Proxy-Wasm ABI +// `proxy_status_t`. enum class WasmResult : uint32_t { Ok = 0, // The result could not be found, e.g. a provided key did not appear in a @@ -76,37 +78,67 @@ inline std::string toString(WasmResult r) { } #undef _CASE +// HTTP header type. Corresponds to Proxy-Wasm ABI `proxy_header_map_type_t`. enum class WasmHeaderMapType : int32_t { - RequestHeaders = 0, // During the onLog callback these are immutable - RequestTrailers = 1, // During the onLog callback these are immutable - ResponseHeaders = 2, // During the onLog callback these are immutable - ResponseTrailers = 3, // During the onLog callback these are immutable - GrpcReceiveInitialMetadata = 4, // Immutable - GrpcReceiveTrailingMetadata = 5, // Immutable - HttpCallResponseHeaders = 6, // Immutable - HttpCallResponseTrailers = 7, // Immutable + // HTTP request headers. During the `onLog` callback these are immutable. + RequestHeaders = 0, + // HTTP request trailers. During the `onLog` callback these are immutable. + RequestTrailers = 1, + // HTTP response headers. During the `onLog` callback these are immutable. + ResponseHeaders = 2, + // HTTP response trailers. During the `onLog` callback these are immutable. + ResponseTrailers = 3, + // Initial metadata for the response to a gRPC callout. Immutable. + GrpcReceiveInitialMetadata = 4, + // Trailing metadata for the response to a gRPC callout. Immutable. + GrpcReceiveTrailingMetadata = 5, + // HTTP response headers for the response to an HTTP callout. Immutable. + HttpCallResponseHeaders = 6, + // HTTP response trailers for the response to an HTTP callout. Immutable. + HttpCallResponseTrailers = 7, MAX = 7, }; + +// Data buffer types. Corresponds to Proxy-Wasm ABI `proxy_buffer_type_t`. enum class WasmBufferType : int32_t { - HttpRequestBody = 0, // During the onLog callback these are immutable - HttpResponseBody = 1, // During the onLog callback these are immutable - NetworkDownstreamData = 2, // During the onLog callback these are immutable - NetworkUpstreamData = 3, // During the onLog callback these are immutable - HttpCallResponseBody = 4, // Immutable - GrpcReceiveBuffer = 5, // Immutable - VmConfiguration = 6, // Immutable - PluginConfiguration = 7, // Immutable - CallData = 8, // Immutable + // HTTP request body bytes. During the `onLog` callback these are immutable. + HttpRequestBody = 0, + // HTTP response body bytes. During the `onLog` callback these are immutable. + HttpResponseBody = 1, + // Bytes received from downstream TCP (or TCP-like) sender. During the `onLog` + // callback these are immutable. + NetworkDownstreamData = 2, + // Bytes received from upstream TCP (or TCP-like) sender. During the `onLog` + // callback these are immutable. + NetworkUpstreamData = 3, + // HTTP response body for the response to an HTTP callout. Immutable. + HttpCallResponseBody = 4, + // Response data for the response to a gRPC callout. Immutable. + GrpcReceiveBuffer = 5, + // VM configuration data. Immutable. + VmConfiguration = 6, + // Plugin configuration data. Immutable. + PluginConfiguration = 7, + // Foreign function call argument data. Immutable. + CallData = 8, MAX = 8, }; + +// Flags values for `getBufferStatus` hostcall. enum class WasmBufferFlags : int32_t { // These must be powers of 2. EndOfStream = 1, }; + +// Stream type. Corresponds to Proxy-Wasm ABI `proxy_stream_type_t`. enum class WasmStreamType : int32_t { + // HTTP request. Request = 0, + // HTTP response. Response = 1, + // TCP(-like) data from downstream. Downstream = 2, + // TCP(-like) data from upstream. Upstream = 3, MAX = 3, }; diff --git a/proxy_wasm_enums.h b/proxy_wasm_enums.h index 89fa13d..39c9bb9 100644 --- a/proxy_wasm_enums.h +++ b/proxy_wasm_enums.h @@ -23,23 +23,77 @@ #include +// Severity levels for logging operations. enum class LogLevel : int32_t { trace, debug, info, warn, error, critical, Max = critical }; -enum class FilterStatus : int32_t { Continue = 0, StopIteration = 1 }; + +// Enum indicating whether to continue processing of a TCP(-like) stream +// following a callback. +enum class FilterStatus : int32_t { + // The host should continue to process the stream. + Continue = 0, + // The host should suspend further processing of the stream until plugin code + // unpauses the stream via a call to `continueDownstream` or + // `continueUpstream`. + StopIteration = 1 +}; + +// Enum indicating whether to continue processing of a stream following an HTTP +// header-related callback. enum class FilterHeadersStatus : int32_t { + // The host should continue to process the stream. Continue = 0, + // The host should suspend further processing of headers until plugin code + // unpauses the stream via a call to `continueRequest` or `continueResponse`. StopIteration = 1, + // The host should terminate the stream. ContinueAndEndStream = 2, + // The host should suspend further processing of the stream until plugin code + // unpauses the stream via a call to `continueRequest` or `continueResponse`, + // in the meantime buffering all body bytes received. StopAllIterationAndBuffer = 3, + // The host should suspend further processing of the stream including reading + // body data until plugin code unpauses the stream via a call to + // `continueRequest` or `continueResponse`. StopAllIterationAndWatermark = 4, }; -enum class FilterMetadataStatus : int32_t { Continue = 0 }; -enum class FilterTrailersStatus : int32_t { Continue = 0, StopIteration = 1 }; + +// Enum indicating whether to continue processing of a stream following a +// metadata-related callback. +enum class FilterMetadataStatus : int32_t { + // The host should continue to process the stream. + Continue = 0 +}; + +// Enum indicating whether to continue processing of a stream following an HTTP +// trailer-related callback. +enum class FilterTrailersStatus : int32_t { + // The host should continue to process the stream. + Continue = 0, + // The host should suspend further processing of the stream until plugin code + // unpauses the stream via a call to `continueRequest` or `continueResponse`. + StopIteration = 1 +}; + +// Enum indicating whether to continue processing of a stream following an HTTP +// body-related callback. enum class FilterDataStatus : int32_t { + // The host should continue to process the stream. Continue = 0, + // The host should suspend further processing of the stream until plugin code + // unpauses the stream via a call to `continueRequest` or `continueResponse`, + // in the meantime buffering all body bytes received. StopIterationAndBuffer = 1, + // The host should suspend further processing of the stream including reading + // body data until plugin code unpauses the stream via a call to + // `continueRequest` or `continueResponse`. StopIterationAndWatermark = 2, + // The host should suspend further processing of the stream other than + // receiving body data until plugin code unpauses the stream via a call to + // `continueRequest` or `continueResponse`. StopIterationNoBuffer = 3 }; + +// gRPC status codes. enum class GrpcStatus : int32_t { Ok = 0, Canceled = 1, @@ -61,14 +115,23 @@ enum class GrpcStatus : int32_t { MaximumValid = Unauthenticated, InvalidCode = -1 }; + +// Types of metrics. enum class MetricType : int32_t { + // Value representing a cumulative count of some event. Counter = 0, + // Value representing a current snapshot of some quantity. Gauge = 1, + // Bucketed distribution of values. Histogram = 2, Max = 2, }; + +// Enum indicating how a connection was closed. enum class CloseType : int32_t { Unknown = 0, - Local = 1, // Close initiated by the proxy. - Remote = 2, // Close initiated by the peer. + // Close initiated by the proxy. + Local = 1, + // Close initiated by the peer. + Remote = 2, }; From f6d945fdc7b423fc1e844bd0c5ec64708a2cad8a Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Tue, 1 Aug 2023 03:15:51 +0000 Subject: [PATCH 5/9] Fix clang-format Signed-off-by: Michael Warres --- proxy_wasm_api.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/proxy_wasm_api.h b/proxy_wasm_api.h index fed7bf3..495fcfe 100644 --- a/proxy_wasm_api.h +++ b/proxy_wasm_api.h @@ -703,8 +703,7 @@ class Context : public ContextBase { // // Returns `FilterStatus` indicating whether processing of the connection // should continue or pause until a later call to `continueDownstream`. - virtual FilterStatus onDownstreamData(size_t /* data_size */, - bool /* end_of_stream */) { + virtual FilterStatus onDownstreamData(size_t /* data_size */, bool /* end_of_stream */) { return FilterStatus::Continue; } @@ -716,8 +715,7 @@ class Context : public ContextBase { // // Returns `FilterStatus` indicating whether processing of the connection // should continue or pause until a later call to `continueUpstream`. - virtual FilterStatus onUpstreamData(size_t /* data_size */, - bool /* end_of_stream */) { + virtual FilterStatus onUpstreamData(size_t /* data_size */, bool /* end_of_stream */) { return FilterStatus::Continue; } @@ -1583,9 +1581,13 @@ template <> inline std::string toString(std::string t) { return t; } template <> inline std::string toString(bool t) { return t ? "true" : "false"; } -template struct StringToStringView { typedef T type; }; +template struct StringToStringView { + typedef T type; +}; -template <> struct StringToStringView { typedef std::string_view type; }; +template <> struct StringToStringView { + typedef std::string_view type; +}; inline uint32_t MetricBase::resolveFullName(const std::string &n) { auto it = metric_ids.find(n); From 2682cf198ad80931c5687ff2b5cece24b845c30c Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Thu, 27 Jun 2024 19:51:02 +0000 Subject: [PATCH 6/9] Address review comments. Signed-off-by: Michael Warres --- docs/api_overview.md | 35 ++++++++++++++++++----------------- docs/building.md | 10 ---------- proxy_wasm_api.h | 12 +++++++----- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/docs/api_overview.md b/docs/api_overview.md index ed746e4..c0e4759 100644 --- a/docs/api_overview.md +++ b/docs/api_overview.md @@ -59,8 +59,7 @@ these to create and return instances of its own subclasses of `RootContext` and ```c++ static RegisterContextFactory register_ExampleContext( - CONTEXT_FACTORY(ExampleContext), ROOT_FACTORY(ExampleRootContext), - "my_root_id"); + CONTEXT_FACTORY(ExampleContext), ROOT_FACTORY(ExampleRootContext)); ``` `ROOT_FACTORY` and `CONTEXT_FACTORY` are convenience macros for lambdas that @@ -113,11 +112,14 @@ in the lifecycle of a stream: * `onCreate`: called when handling of a new stream starts. * `onDone`: called when the host is done processing the stream. * `onLog`: called after the host is done processing the stream, if the plugin is - being used for access logging. + being used for access logging (for example, in Envoy, as a + [WasmAccessLog](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/access_loggers/wasm/v3/wasm.proto#extensions-access-loggers-wasm-v3-wasmaccesslog) + extension). * `onDelete`: called after the plugin has completed all processing related to the stream, as indication to release resources. -`Context` instances also receive callbacks corresponding to stream events: +`Context` instances also receive callbacks corresponding to stream events. For +HTTP or gRPC streams, these are: * `onRequestHeaders`: called when HTTP or gRPC request headers are received. * `onRequestBody`: called when HTTP or gRPC request body data is received. @@ -125,12 +127,21 @@ in the lifecycle of a stream: * `onResponseHeaders`: called when HTTP or gRPC response headers are received. * `onResponseBody`: called when HTTP or gRPC response body data is received. * `onResponseTrailers`: called when HTTP or gRPC response trailers are received. + +For TCP streams, `Context` instances may receive the following callbacks: + * `onNewConnection`: called when a new connection is established. * `onDownstreamData`: called when a new chunk of data is received from downstream over a connection. * `onUpstreamData`: called when a new chunk of data is received from upstream over a connection. +Callback methods return status enums that indicate whether and how the host +should continue to process the stream. Status enum meanings are specified in the +doc comments in [proxy_wasm_enums.h]. Callbacks can also generate an immediate +local response to an HTTP or gRPC request using the `sendLocalResponse` +hostcall. + For API details, see doc comments for the `Context` class in [proxy_wasm_api.h]. ## Buffers @@ -217,7 +228,7 @@ inspect data sent by the upstream client could override the `Context::onUpstreamData` method. To access the actual data being proxied, plugin code would use the -buffer-related hostcalls described in [Buffers], specifying +buffer-related hostcalls described in [Buffers](#Buffers), specifying `NetworkUpstreamData` as the `WasmBufferType`. ## Timers @@ -326,18 +337,8 @@ prepended to the log message: * `LOG_ERROR` * `LOG_CRITICAL` -Additionally, there is a LOG macro that accepts log level as a -parameter. These macros layer on top of hostcall functions: - -* `logTrace` -* `logDebug` -* `logInfo` -* `logWarn` -* `logError` -* `logCritical` - -All macros and hostcalls above allow plugin execution to continue. There -is one further logging hostcall that terminates plugin execution: +The macros above allow plugin execution to continue. There is also a logging +hostcall that terminates plugin execution: * `logAbort`: logs at Critical level, then aborts the plugin diff --git a/docs/building.md b/docs/building.md index 1262ba5..ea65f11 100644 --- a/docs/building.md +++ b/docs/building.md @@ -225,14 +225,4 @@ cp protobuf-wasm/src/.libs/libprotobuf-lite.a ${CPP_API}/libprotobuf-lite.a cp protobuf-wasm/src/.libs/libprotobuf.a ${CPP_API}/libprotobuf.a ``` -### WAVM binaries - -```bash -git clone git@github.com:WAVM/WAVM.git -cd WAVM -cmake "." -make -sudo make install -``` - Note: ensure /usr/local/bin is in your path. diff --git a/proxy_wasm_api.h b/proxy_wasm_api.h index 495fcfe..f1ffc0d 100644 --- a/proxy_wasm_api.h +++ b/proxy_wasm_api.h @@ -168,18 +168,20 @@ template size_t pairsSize(const Pairs &result) { return size; } -// Marshals `result` to the memory buffer `buffer`. -template void marshalPairs(const Pairs &result, char *buffer) { +// Marshals `pairs` to the memory buffer `buffer`. `Pairs` is a map-like type +// that provides a `size` method and iteration over elements that are +// `std::pair`s of `std::string` or `std::string_view`s. +template void marshalPairs(const Pairs &pairs, char *buffer) { char *b = buffer; - *reinterpret_cast(b) = result.size(); + *reinterpret_cast(b) = pairs.size(); b += sizeof(uint32_t); - for (auto &p : result) { + for (auto &p : pairs) { *reinterpret_cast(b) = p.first.size(); b += sizeof(uint32_t); *reinterpret_cast(b) = p.second.size(); b += sizeof(uint32_t); } - for (auto &p : result) { + for (auto &p : pairs) { memcpy(b, p.first.data(), p.first.size()); b += p.first.size(); *b++ = 0; From 9152b605b7b0c568e5d43a68ec2fd989d65df2de Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Sun, 4 Aug 2024 20:51:17 +0000 Subject: [PATCH 7/9] Address review comments. Signed-off-by: Michael Warres --- README.md | 2 +- docs/building.md | 32 ++++---------------------------- proxy_wasm_enums.h | 6 ++++-- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index f985658..f2eae01 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Proxy-Wasm C++ SDK +# WebAssembly for Proxies (C++ SDK) [![Build Status][build-badge]][build-link] [![Apache 2.0 License][license-badge]][license-link] diff --git a/docs/building.md b/docs/building.md index ea65f11..76f8ec2 100644 --- a/docs/building.md +++ b/docs/building.md @@ -6,9 +6,9 @@ therefore use of a Docker image is recommended. ## Docker -A Dockerfile for the C++ SDK is provided in [Dockerfile-sdk](Dockerfile-sdk). +A Dockerfile for the C++ SDK is provided in [Dockerfile-sdk](../Dockerfile-sdk). -It can built in this directory by: +It can built in this repository's root directory by: ```bash docker build -t wasmsdk:v2 -f Dockerfile-sdk . @@ -75,30 +75,6 @@ docker commit `docker ps -l | grep wasmsdk:v2 | awk '{print $1}'` wasmsdk:v2 This will save time on subsequent compiles. -### Using the SDK from a newer/specific version of Envoy - -To use a newer/specific version of the SDK (e.g. from the version of Envoy you -are going to deploy the WebAssembly module to), bind that volume and use it in -the Makefile. - -Here is an example Makefile referencing the SDK at `../envoy/api/wasm/cpp` and -mounted as `sdk` in the `/work` directory: - -```makefile -PROXY_WASM_CPP_SDK=/work/sdk - -all: myproject.wasm - -include ${PROXY_WASM_CPP_SDK}/Makefile.base_lite -``` - -Run docker pointing to Envoy sources in a directory parallel (at the same level) -as your project directory: - -```bash -docker run -v $PWD:/work -v $PWD/../envoy/api/wasm/cpp:/work/sdk -w /work wasmsdk:v2 bash /build_wasm.sh -``` - ### Using Abseil from the Docker image Abseil (optionally) is built in /root/abseil and can be used. Note that the @@ -206,8 +182,8 @@ However 3.1.7 is known to work. ### Rebuilding the libprotobuf.a files -If want to rebuild the libprotobuf.a files or use a different version see the -instructions at https://github.com/kwonoj/protobuf-wasm. Commit +If want to rebuild the libprotobuf.a files using a version of protobuf prior to +3.15, see the instructions at https://github.com/kwonoj/protobuf-wasm. Commit 4bba8b2f38b5004f87489642b6ca4525ae72fe7f works for protobuf v3.9.x. ```bash diff --git a/proxy_wasm_enums.h b/proxy_wasm_enums.h index 39c9bb9..ca42126 100644 --- a/proxy_wasm_enums.h +++ b/proxy_wasm_enums.h @@ -44,12 +44,14 @@ enum class FilterHeadersStatus : int32_t { Continue = 0, // The host should suspend further processing of headers until plugin code // unpauses the stream via a call to `continueRequest` or `continueResponse`. + // Some host implementations may treat this equivalently to + // `StopAllIterationAndWatermark`. StopIteration = 1, // The host should terminate the stream. ContinueAndEndStream = 2, // The host should suspend further processing of the stream until plugin code // unpauses the stream via a call to `continueRequest` or `continueResponse`, - // in the meantime buffering all body bytes received. + // in the meantime buffering all body bytes received, subject to host limits. StopAllIterationAndBuffer = 3, // The host should suspend further processing of the stream including reading // body data until plugin code unpauses the stream via a call to @@ -81,7 +83,7 @@ enum class FilterDataStatus : int32_t { Continue = 0, // The host should suspend further processing of the stream until plugin code // unpauses the stream via a call to `continueRequest` or `continueResponse`, - // in the meantime buffering all body bytes received. + // in the meantime buffering all body bytes received, subject to host limits. StopIterationAndBuffer = 1, // The host should suspend further processing of the stream including reading // body data until plugin code unpauses the stream via a call to From 33acaf025d70409a96a28131516b8f8334aee615 Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Mon, 5 Aug 2024 03:14:41 +0000 Subject: [PATCH 8/9] Address review comments for api_overview.md Signed-off-by: Michael Warres --- docs/api_overview.md | 82 +++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/docs/api_overview.md b/docs/api_overview.md index c0e4759..f340467 100644 --- a/docs/api_overview.md +++ b/docs/api_overview.md @@ -2,7 +2,7 @@ This page explains the main concepts and structure of the C++ SDK API. For detailed API semantics, refer to the doc comments in the header files listed in -the [codemap](#codemap) section, in particular [proxy_wasm_api.h]. +the [Codemap] section, in particular [proxy_wasm_api.h]. ## Concepts and terminology @@ -33,12 +33,13 @@ the [codemap](#codemap) section, in particular [proxy_wasm_api.h]. with the current stream. The hostcalls are defined as methods on `Context` base classes, which plugin code can call. - There are two types of Contexts: *Root Contexts* and *Stream Contexts* - (sometimes just called Contexts). Root Contexts are represented by the - `RootContext` class, and are associated with plugins as a whole--i.e. within a - given Wasm VM, there is one Root Context per plugin. Stream Contexts are - represented by the `Context` class, and are associated with individual - streams--i.e. incoming HTTP/gRPC calls, or TCP streams. + There are two types of Contexts: *Plugin Contexts* (also known as Root + Contexts) and *Stream Contexts* (sometimes just called Contexts). Plugin + Contexts are represented by the `RootContext` class, and are associated with + plugins as a whole--i.e. within a given Wasm VM, there is one Plugin Context + per plugin. Stream Contexts are represented by the `Context` class, and are + associated with individual streams--i.e. incoming HTTP/gRPC calls, or TCP + streams. In the C++ SDK programming model, plugins are implemented by subclassing `RootContext` and/or `Context`, and providing implementations of their various @@ -53,7 +54,7 @@ the [codemap](#codemap) section, in particular [proxy_wasm_api.h]. `RegisterContextFactory` is the mechanism for bootstrapping plugin code. Plugin code instantiates a `RegisterContextFactory` in a static variable. `RegisterContextFactory` takes as constructor params `std::function`s -for creating new root context and stream context instances. The plugin can use +for creating new plugin context and stream context instances. The plugin can use these to create and return instances of its own subclasses of `RootContext` and `Context`. For example: @@ -112,8 +113,7 @@ in the lifecycle of a stream: * `onCreate`: called when handling of a new stream starts. * `onDone`: called when the host is done processing the stream. * `onLog`: called after the host is done processing the stream, if the plugin is - being used for access logging (for example, in Envoy, as a - [WasmAccessLog](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/access_loggers/wasm/v3/wasm.proto#extensions-access-loggers-wasm-v3-wasmaccesslog) + being used for access logging (for example, in Envoy, as a [WasmAccessLog] extension). * `onDelete`: called after the plugin has completed all processing related to the stream, as indication to release resources. @@ -122,10 +122,12 @@ in the lifecycle of a stream: HTTP or gRPC streams, these are: * `onRequestHeaders`: called when HTTP or gRPC request headers are received. -* `onRequestBody`: called when HTTP or gRPC request body data is received. +* `onRequestBody`: called when a new chunk of HTTP or gRPC request body data is + received. * `onRequestTrailers`: called when HTTP or gRPC request trailers are received. * `onResponseHeaders`: called when HTTP or gRPC response headers are received. -* `onResponseBody`: called when HTTP or gRPC response body data is received. +* `onResponseBody`: called when a new chunk of HTTP or gRPC response body data + is received. * `onResponseTrailers`: called when HTTP or gRPC response trailers are received. For TCP streams, `Context` instances may receive the following callbacks: @@ -169,7 +171,7 @@ individual callbacks in [proxy_wasm_api.h] for which buffers are accessible in each callback. Documentation for the `WasmData` class is also in [proxy_wasm_api.h]. -## Handling HTTP and gRPC requests +## Handling HTTP and gRPC request headers and trailers Plugins that handle HTTP and gRPC requests do so by overriding one or more of the HTTP-related callback methods declared by the `Context` class (see the @@ -207,14 +209,13 @@ headers, and response trailers. gRPC requests received by the proxy are dispatched to the plugin using the same `Context` callback methods as HTTP requests. Plugin code can determine whether an incoming request is gRPC in the same way that network proxies do: by checking -if the ":method" pseudoheader has value "POST" and the "content-type" header has -value "application/grpc". +if the ":method" pseudoheader has value "POST" and the "content-type" header +starts with value "application/grpc". Plugin callbacks can access the request URL via the ":method", ":scheme", ":authority", and ":path" pseudo-headers defined in [RFC 9113 section -8.3.1](https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1). They can -access HTTP response status via the ":status" pseudo-header defined in [RFC 9113 -section 8.3.2](https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.2). +8.3.1]. They can access HTTP response status via the ":status" pseudo-header +defined in [RFC 9113 section 8.3.2]. For API details, see doc comments accompanying the functions in [proxy_wasm_api.h]. @@ -228,7 +229,7 @@ inspect data sent by the upstream client could override the `Context::onUpstreamData` method. To access the actual data being proxied, plugin code would use the -buffer-related hostcalls described in [Buffers](#Buffers), specifying +buffer-related hostcalls described in [Buffers], specifying `NetworkUpstreamData` as the `WasmBufferType`. ## Timers @@ -371,7 +372,7 @@ for representing and updating metrics: - `Metric`: all of the above Metrics can be subdivided by tags, using similar structure to [Envoy -statistics](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/observability/statistics). +statistics]. ## Foreign function interface (FFI) @@ -449,24 +450,35 @@ listed below: * [proxy_wasm_api.h]: main SDK API definition and implementation * [proxy_wasm_externs.h]: declarations for ABI-level hostcalls and callbacks -* [proxy_wasm_common.h](../proxy_wasm_common.h): supporting types for the API -* [proxy_wasm_enums.h](../proxy_wasm_enums.h): supporting enums for the API -* [proxy_wasm_intrinsics.js](../proxy_wasm_intrinsics.js): list of Proxy-Wasm - ABI hostcalls, for use by [Emscripten](https://emscripten.org) -* [proxy_wasm_intrinsics.proto](../proxy_wasm_intrinsics.proto): protobuf types - needed for gRPC calls -* [proxy_wasm_intrinsics.h](../proxy_wasm_intrinsics.h): combined header file - that includes all other header files -* [proxy_wasm_intrinsics.cc](../proxy_wasm_intrinsics.cc): implementation of - dispatch from ABI-level Proxy-Wasm callbacks to [Context] and [RootContext] - callback methods. - +* [proxy_wasm_common.h]: supporting types for the API +* [proxy_wasm_enums.h]: supporting enums for the API +* [proxy_wasm_intrinsics.js]: list of Proxy-Wasm ABI hostcalls, for use by + [Emscripten] +* [proxy_wasm_intrinsics.proto]: protobuf types needed for gRPC calls +* [proxy_wasm_intrinsics.h]: combined header file that includes all other header + files +* [proxy_wasm_intrinsics.cc]: implementation of dispatch from ABI-level + Proxy-Wasm callbacks to [Context] and [RootContext] callback methods. + +[Buffers]: #buffers +[Codemap]: #codemap [Context]: #context +[Emscripten]: https://emscripten.org +[Envoy statistics]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/observability/statistics +[HTTP callouts]: #http-callouts +[RFC 9113 section 8.3.1]: https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1 +[RFC 9113 section 8.3.2]: https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.2 [RootContext]: #rootcontext -[Timers]: #timers [Shared queues]: #shared-queues -[HTTP callouts]: #http-callouts +[Timers]: #timers +[WasmAccessLog]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/access_loggers/wasm/v3/wasm.proto#extensions-access-loggers-wasm-v3-wasmaccesslog [gRPC callouts]: #grpc-callouts +[http_wasm_example.cc]: ../example/http_wasm_example.cc [proxy_wasm_api.h]: ../proxy_wasm_api.h +[proxy_wasm_common.h]: ../proxy_wasm_common.h +[proxy_wasm_enums.h]: ../proxy_wasm_enums.h [proxy_wasm_externs.h]: ../proxy_wasm_externs.h -[http_wasm_example.cc]: ../example/http_wasm_example.cc +[proxy_wasm_intrinsics.cc]: ../proxy_wasm_intrinsics.cc +[proxy_wasm_intrinsics.h]: ../proxy_wasm_intrinsics.h +[proxy_wasm_intrinsics.js]: ../proxy_wasm_intrinsics.js +[proxy_wasm_intrinsics.proto]: ../proxy_wasm_intrinsics.proto From 45150417a86af8a7fb5c3dc502641691ffeb5930 Mon Sep 17 00:00:00 2001 From: Michael Warres Date: Mon, 5 Aug 2024 03:20:21 +0000 Subject: [PATCH 9/9] Fix clang-format Signed-off-by: Michael Warres --- proxy_wasm_api.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/proxy_wasm_api.h b/proxy_wasm_api.h index f1ffc0d..f9d4f9d 100644 --- a/proxy_wasm_api.h +++ b/proxy_wasm_api.h @@ -1583,13 +1583,9 @@ template <> inline std::string toString(std::string t) { return t; } template <> inline std::string toString(bool t) { return t ? "true" : "false"; } -template struct StringToStringView { - typedef T type; -}; +template struct StringToStringView { typedef T type; }; -template <> struct StringToStringView { - typedef std::string_view type; -}; +template <> struct StringToStringView { typedef std::string_view type; }; inline uint32_t MetricBase::resolveFullName(const std::string &n) { auto it = metric_ids.find(n);