Skip to main content

Version: 3.11.0

Use Wasm Plugins in APISIX

APISIX supports WebAssembly (Wasm) plugins developed following the Proxy-Wasm specification, a specification that extends Wasm capabilities to proxies.

There are several benefits using Wasm to develop APISIX plugins:

  • The ability to compile many programming languages to Wasm. This allows you to leverage the capabilities of your existing technology stack when developing APISIX plugins.
  • Running Wasm plugins natively within APISIX but in a separate VM. Even if a plugin crashes, APISIX can continue to operate.
  • The continuous support for Wasm in APISIX. This avoids maintaining multiple external plugin runners for different programming languages.

This guide will help you understand how APISIX supports Wasm and how to develop a sample Wasm plugin in Go using the Proxy-Wasm Go SDK, as well as loading the plugin into APISIX.

How APISIX Supports Wasm

APISIX implements the Proxy-Wasm specification. The specification was initially developed for the Envoy proxy and has evolved to be the standard for writing Wasm plugins for proxies. Any plugin written using the Proxy-Wasm SDKs can be run in APISIX.

APISIX uses the following programming model to work with Wasm plugins. All these interfaces should implemented while writing custom Wasm plugins.


wasm programming model

Each plugin has its own VMContext, which can create multiple PluginContext for each route. For example, each PluginContext corresponds to an instance of a plugin, so if a service is configured with a Wasm plugin and two routes inherit from the service, each route will have its own PluginContext.

Similarly, a PluginContext is the parent of multiple HTTPContext. Each HTTP request that hits the configuration will also have its own HttpContext. For example, if you configure both global rules and route, the HTTP request will have two HTTPContext, one for the PluginContext from global rules and the other one for the PluginContext from route.

Build and Load Wasm Plugins into APISIX

In this section, you will learn how to build a minimal Wasm plugin in Go that logs a warning message in the proxy whenever there is an incoming request, compile the source code to Wasm, and load the Wasm plugin into APISIX.

Prerequisite(s)

  • Install Docker.
  • Install cURL to send requests to the services for validation.
  • Follow the Getting Started tutorial to start a new APISIX instance in Docker.
  • Install Go.
  • Install TinyGo to compile the Go source code into Wasm.
  • Install the Proxy-Wasm Go SDK to develop Proxy-Wasm compliant plugins in Go.

Write Plugin Logics in Go

Create a file with the following code:

main.go
package main

// Import the proxy-wasm-go-sdk package for building Wasm plugins
import (
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
)

func main() {
// Set the VM context to a new instance of the vmContext struct
proxywasm.SetVMContext(&vmContext{})
}

// The vmContext struct represents the VM context
type vmContext struct {
// Embed the DefaultVMContext type from the proxywasm/types package
types.DefaultVMContext
}

// The NewPluginContext function is called when a new plugin context is created
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
// Return a new instance of the pluginContext struct with the given contextID
return &pluginContext{contextID: contextID}
}

// The pluginContext struct represents the plugin context
type pluginContext struct {
// Embed the DefaultPluginContext type from the proxywasm/types package
types.DefaultPluginContext
conf string
contextID uint32
}

// The OnPluginStart function is called when the plugin is started
func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
// Get the plugin configuration data
data, err := proxywasm.GetPluginConfiguration()
if err!= nil {
// Log a critical error if there's an error reading the configuration
proxywasm.LogCriticalf("error reading plugin configuration: %v", err)
return types.OnPluginStartStatusFailed
}
// Store the configuration data in the conf field
ctx.conf = string(data)
// Return a successful status
return types.OnPluginStartStatusOK
}

// The OnPluginDone function is called when the plugin is done
func (ctx *pluginContext) OnPluginDone() bool {
proxywasm.LogInfo("do clean up...")
return true
}

// The NewHttpContext function is called when a new HTTP context is created
func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
// Return a new instance of the httpLifecycle struct with the given contextID and conf
return &httpLifecycle{
pluginCtxID: ctx.contextID,
conf: ctx.conf,
contextID: contextID
}
}

// The httpLifecycle struct represents the HTTP lifecycle
type httpLifecycle struct {
// Embed the DefaultHttpContext type from the proxywasm/types package
types.DefaultHttpContext
pluginCtxID uint32
contextID uint32
conf string
}

// The OnHttpRequestHeaders function is called when HTTP request headers are received
func (ctx *httpLifecycle) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
// Log a warning message with the plugin context ID, configuration, and HTTP context ID
proxywasm.LogWarnf("run plugin ctx %d with conf %s in http ctx %d", ctx.pluginCtxID, ctx.conf, ctx.contextID)
// Return an action to continue processing the request
return types.ActionContinue
}

The code above implements the OnHttpRequestHeaders method for the httpLifecycle struct. The function is called whenever HTTP request headers are received. See the section below to learn more about the correlation between Proxy-Wasm callback functions and APISIX phases.

Compile the Code into Wasm

Compile the above Go source code into .wasm file:

tinygo build -o log.go.wasm -target=wasi ./main.go

If the compilation is successful, you should see a log.go.wasm file in your current directory.

Copy the File into APISIX Container

Copy log.go.wasm into the /usr/local/bin directory:

docker cp log.go.wasm apisix-quickstart:/usr/local/bin/

If successful, the file should be found at /usr/local/bin/log.go.wasm in the container.

Load the Plugin into APISIX

Update the config.yaml configuration file with the Wasm plugin related information:

docker exec apisix-quickstart /bin/bash -c "echo '
wasm:
plugins:
- name: wasm_log
priority: 7999
file: /usr/local/bin/log.go.wasm
' >> /usr/local/apisix/conf/config.yaml"

name: the name of the Wasm plugin.

priority: the execution priority of the plugin.

file: the absolute path to the Wasm file.

Reload APISIX for configuration changes to take effect:

docker exec apisix-quickstart apisix reload

You should now be able to use the log.go.wasm plugin.

Use the Plugin in APISIX

Create a sample route with the wasm_log plugin:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id": "wasm-log-plugin",
"uri": "/anything",
"plugins": {
"wasm_log": {
"conf": "hello apisix"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

conf: configure a string to be logged by the plugin.

Send a request to the route:

curl -i "http://127.0.0.1:9080/anything"

You should receive an HTTP/1.1 200 OK response.

Navigate to the error log, you should see the following log entry created by the wasm_log plugin:

2024/05/13 08:08:52 [warn] 128#128: *2762 run plugin ctx 1 with conf hello apisix in http ctx 2, client: 172.19.0.1, server: _, request: "GET /anything HTTP/1.1", host: "127.0.0.1:9080"

Proxy-Wasm Callback Functions and APISIX Phases

The following table shows the correspondences between Proxy-Wasm callback functions and APISIX phases.

Proxy-Wasm CallbacksAPISIX Phases
proxy_on_configureRun once when there is not plugin context for the new configuration, such as when the first request hits the route where there is no Wasm plugin configured.
proxy_on_http_request_headersRun in the access or rewrite phase, depending on the configuration of the http_request_phase.
proxy_on_http_request_bodyRun in the same phase as proxy_on_http_request_headers. To run this callback, set the property wasm_process_req_body to non-empty value in proxy_on_http_request_headers.
proxy_on_http_response_headersRun in the header_filter phase.
proxy_on_http_response_bodyRun in the body_filter phase. To run this callback, set the property wasm_process_resp_body to non-empty value in proxy_on_http_response_headers.

Next Steps

The support for Proxy-Wasm APIs is an ongoing effort. To follow on the latest progress, please see wasm-nginx-module.

A few APISIX plugins, such as fault-injection and response-rewrite, are re-implemented in Wasm. Explore the /t/wasm directory to learn more.

A practical use case is to integrate APISIX with Coraza WAF to protect upstream resources. This is done by loading the coraza-proxy-wasm module into APISIX and configure the plugin on APISIX resources. See the integration with Coraza WAF doc.


API7.ai Logo

API Management for Modern Architectures with Edge, API Gateway, Kubernetes, and Service Mesh.

Product

API7 Cloud

SOC2 Type IIISO 27001HIPAAGDPRRed Herring

Copyright © APISEVEN Ltd. 2019 – 2024. Apache, Apache APISIX, APISIX, and associated open source project names are trademarks of the

Apache Software Foundation