Minimal Wasm extension¶
We'll create a minimal Wasm extension in this lab and run it locally using Envoy.
We'll start by creating a new folder for our extension, initializing the Go module, and downloading the SDK dependency:
Next, let's create the main.go
file where the code for our Wasm extension will live. We'll start with the minimal code:
Save the above to main.go
.
Let's download the dependencies and then we can build the extension to check everything is good:
# Download the dependencies
go mod tidy
# Build the wasm file
tinygo build -o main.wasm -scheduler=none -target=wasi main.go
The build command should run successfully and generate a file called main.wasm
.
We'll use func-e
to run a local Envoy instance to test our built extension.
First, we need an Envoy config that will configure the extension:
Save the above to envoy.yaml
.
The Envoy configuration sets up a single listener on port 18000 that returns a direct response (HTTP 200) with body hello world
. Inside the http_filters
section, we're configuring the envoy.filters.http.wasm
filter and referencing the local WASM file (main.wasm
) we've built earlier.
Let's run the Envoy with this configuration in the background:
Envoy instance should start without any issues. Once it's started, we can send a request to the port Envoy is listening on (18000
):
[2022-01-25 22:21:53.493][5217][info][wasm] [source/extensions/common/wasm/context.cc:1167] wasm log: NewHttpContext
hello world
The output shows the single log entry coming from the Envoy proxy. This is the LogInfo
function we called in the NewHttpContext
callback. The NewHttpContext
is called for each new HTTP stream. Similarly, a NewTcpContext
method gets called for each new TCP connection.
We can also use the wasm-objdump
tool from the WebAssembly Binary Toolkit to see the functions and how they map to proxy Proxy Wasm ABI:
wasm-objdump output
main.wasm: file format wasm 0x1
Section Details:
Export[31]:
- memory[0] -> "memory"
- func[21] <malloc> -> "malloc"
- func[22] <free> -> "free"
- func[23] <calloc> -> "calloc"
- func[24] <realloc> -> "realloc"
- func[25] <posix_memalign> -> "posix_memalign"
- func[26] <aligned_alloc> -> "aligned_alloc"
- func[27] <malloc_usable_size> -> "malloc_usable_size"
- func[37] <_start> -> "_start"
- func[39] <proxy_on_memory_allocate> -> "proxy_on_memory_allocate"
- func[40] <proxy_on_vm_start> -> "proxy_on_vm_start"
- func[41] <proxy_on_configure> -> "proxy_on_configure"
- func[42] <proxy_on_new_connection> -> "proxy_on_new_connection"
- func[44] <proxy_on_downstream_data> -> "proxy_on_downstream_data"
- func[46] <proxy_on_downstream_connection_close> -> "proxy_on_downstream_connection_close"
- func[48] <proxy_on_upstream_data> -> "proxy_on_upstream_data"
- func[50] <proxy_on_upstream_connection_close> -> "proxy_on_upstream_connection_close"
- func[52] <proxy_on_request_headers> -> "proxy_on_request_headers"
- func[53] <proxy_on_request_body> -> "proxy_on_request_body"
- func[54] <proxy_on_request_trailers> -> "proxy_on_request_trailers"
- func[55] <proxy_on_response_headers> -> "proxy_on_response_headers"
- func[56] <proxy_on_response_body> -> "proxy_on_response_body"
- func[57] <proxy_on_response_trailers> -> "proxy_on_response_trailers"
- func[58] <proxy_on_http_call_response> -> "proxy_on_http_call_response"
- func[59] <proxy_on_context_create> -> "proxy_on_context_create"
- func[61] <proxy_on_log> -> "proxy_on_log"
- func[63] <proxy_on_done> -> "proxy_on_done"
- func[64] <proxy_on_delete> -> "proxy_on_delete"
- func[65] <proxy_on_queue_ready> -> "proxy_on_queue_ready"
- func[66] <proxy_on_tick> -> "proxy_on_tick"
- func[67] <proxy_abi_version_0_2_0> -> "proxy_abi_version_0_2_0"
This shows how we can produce a Proxy Wasm ABI compatible binary without knowing anything about the ABI and just using the SDK and letting the SDK abstract that complexity.
You can stop the Envoy proxy by bringing the process to the foreground with fg
and pressing Ctrl+C to stop it.