C4 C 4 Punk Developers

CWIST guide · Intermediate · 10 min read

Running CWIST on WASI 0.2

Compile a CWIST server to wasm32-wasip2 and serve it under wasmtime, with the guest owning its own accept loop.

What you will build

The same server source from the getting-started guide, compiled to a .wasm component and run by wasmtime. The guest owns the accept loop through wasi:sockets, so this is a real server inside the sandbox rather than a handler invoked by a host.

What you will need

  • The WASI SDK (25.0 or newer), for clang with a wasm32-wasip2 target.
  • wasmtime 25 or newer.
  • A CWIST checkout, to build the WASI archive.

Step 1: build the WASI archive

From the CWIST checkout, produce the wasip2 static library. The target is named after the artefact it builds:

make libcwist_wasip2_wasm32-wasip2.a

That leaves libcwist_wasip2_wasm32-wasip2.a in the repository root. It is an ordinary static archive; you link it the way you would link any other.

Step 2: compile your server against it

$WASI_SDK/bin/clang \
  --target=wasm32-wasip2 -std=c17 -O2 \
  -D_WASI_EMULATED_GETPID \
  -I$CWIST/include -I$CWIST/lib/cjson \
  -o server.wasm main.c \
  $CWIST/libcwist_wasip2_wasm32-wasip2.a \
  -lwasi-emulated-pthread -lwasi-emulated-getpid \
  -Wl,--gc-sections -Wl,-z,stack-size=1048576

Two flags are load-bearing. The emulated pthread and getpid libraries stand in for POSIX facilities the sandbox does not provide. The stack size matters because the default wasm stack is small and a template or TLS path will overflow it; a megabyte is a safe starting point.

Step 3: run it

wasmtime run -S preview2=y -S tcp=y -S inherit-network=y server.wasm

Each flag grants one capability: preview2 selects the component model ABI, tcp allows socket use, and inherit-network lets the guest bind on the host's network. Without them the process starts and then fails to listen, which is the sandbox behaving correctly.

curl -i http://localhost:8080/

Step 4: what changes inside the sandbox

  • No filesystem unless you grant one. Pass --dir . if you need one; otherwise embed what you need at build time. This site embeds its templates into the binary for exactly that reason.
  • Threads are limited. Prefer the reactor path; thread-per-connection has nowhere useful to go here.
  • Environment variables are not inherited unless you pass --env.
  • Time and randomness are host-provided capabilities and can be withheld.

Step 5: serving assets without a filesystem

CWIST's asset registry keeps content in memory and serves it at a content-hashed URL, which is what makes the no-filesystem case practical:

cwist_app_asset_add(app, "css/app.css", css, strlen(css),
                    "text/css; charset=utf-8");

const char *url = cwist_app_asset_url(app, "css/app.css");
/* -> /assets/css/app.<hash>.css, immutable for a year */

Changing the bytes changes the URL, so a cache never serves a stale copy and an unchanged file keeps its URL across restarts. Register assets before cwist_app_listen().

Summary

A CWIST server compiles to a WASI 0.2 component with no source changes, keeps its own accept loop, and runs under a capability list you control. Anything it needs from the outside world (sockets, files, environment, clock) is something you granted on the command line.