WebAssembly hosted Web Server

Aug 1, 2022 • Arne Vogel

We can now host web servers as WebAssembly modules!

As a proof of concept see wasmhosted.arnevogel.com, which is hosted by a WebAssembly module run by wasmtime. Also check out the source code.

How we got here

This year, sock_accept was added to WASI. Because of this, we can now accept connections on pre-opened listening sockets from within WebAssembly. Before sock_accept only sock_recv, sock_send, and sock_shutdown were defined for WASI. Unfortunately, you can’t do much with the three of them alone.

That’s why sock_accept is so exciting. Support for sock_accept has in the meanwhile been added for the Rust stdlib, tokio + mio, and probably a lot more places.

Accepting connections

We can only accept connections on pre-opened listening sockets. This means for our WebAssembly module that we cannot open a socket from inside the module. We have to get the socket from the runtime. Fortunately wasmtime already supports this.

For us, this means that we have to construct our TcpListener from a raw file descriptor given to us by wasmtime:

#[cfg(target_os = "wasi")]
async fn get_tcplistener() -> TcpListener {
    use std::os::wasi::io::FromRawFd;
    let stdlistener = unsafe { std::net::TcpListener::from_raw_fd(4) };
    stdlistener.set_nonblocking(true).unwrap();
    TcpListener::from_std(stdlistener).unwrap()
}

When we run our server with wasmtime we use --tcplisten to tell wasmtime to open up a socket listening on localhost:4000 and to provide this socket to our module.

wasmtime run --tcplisten 127.0.0.1:4000 server.wasm

Actually, for the proof of concept server I also need to specify the directory wasmtime lets the server access --dir=public, but thats besides the point. While we are at it, we also need to patch our wasmtime. There has been an issue with wasmtime where --tcplisten and --dir do not play nicely together. The patch applies this “band-aid fix” to wasmtime to fix this issue.