Skip to main content

Server-side WASM TCP server in Rust on Docker Desktop

ยท 4 min read

This is a follow-up on a previous post that described how to run a really simple server-side WASM application with the Docker Desktop+WASM Tech Preview. This was your typical Hello, World! example. This time I'm going for a simple echoing TCP server.

Building the web server in Rustโ€‹

Since I'm quite new to Rust, I followed this nice tutorial on how to build a REST API in Rust. It is based on Actix Web and it offers an intuitive way of constructing a REST API.

After finishing the tutorial and some local testing to demonstrate it 'worked on my machine' (you can probably guess now what comes next), I tried to compile it to the wasm32-wasi target:

rwwilden@LAPTOP-FMQ1F4IR:~/projects/rust_wasm_webserver$ cargo build --target wasm32-wasi --release
Downloaded wasm-bindgen-shared v0.2.83
Downloaded 8 crates (420.5 KB) in 0.55s
Compiling autocfg v1.1.0
Compiling cfg-if v1.0.0
Compiling socket2 v0.4.7
error[E0583]: file not found for module `sys`
--> /home/rwwilden/.cargo/registry/src/
124 | mod sys;
| ^^^^^^^^
= help: to create the module `sys`, create file "/home/rwwilden/.cargo/registry/src/" or "/home/rwwilden/.cargo/registry/src/"

error: Socket2 doesn't support the compile target
--> /home/rwwilden/.cargo/registry/src/
127 | compile_error!("Socket2 doesn't support the compile target");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

As you can see, this fails miserably. After a bit of Googling, it turns out that the socket2 crate fails to compile because the sys module is only imported for a unix or windows target. Because of my still limited knowledge of Rust I'm not 100% sure what that means but anything that depends on socket2 won't compile to WASM. These two GitHub issues provide further details.

So we need another way to open a socket via WASI in WASM.

wasmedge_wasi_socket to the rescueโ€‹

The creators of WasmEdge also built a crate called wasmedge_wasi_socket that allows binding to a socket. I'm not sure how that works though. WASI sockets is still in the WASI Feature Proposal state so by no means standardized. But let's give it a try ๐Ÿ™‚

There's a nice tutorial for implementing a simple TCP server that echoes back the request you send it. I just followed along, the source code can be found here. Note that wasmedge_wasi_socket exposes the same types as std::net.

Compiling it to the wasm32-wasi target and a subsequent AOT compile work fine now:

cargo build --target wasm32-wasi --release
wasmedgec target/wasm32-wasi/release/rust_wasm_webserver.wasm rust_wasm_webserver.cwasm

Dockerize and run itโ€‹

Let's define the Dockerfile:

FROM scratch


ENTRYPOINT [ "/rust_wasm_webserver.cwasm" ]

COPY rust_wasm_webserver.cwasm /rust_wasm_webserver.cwasm

and build an image:

docker build -t rust-wasm-webserver .

We should now be able to run the image:

docker run --name rust-wasm-webserver \
--runtime=io.containerd.wasmedge.v1 \
--platform=wasi/wasm32 \
--publish 8000:8000 \

Unfortunately, we get an error message:

Error: Os { code: 6, kind: WouldBlock, message: "Resource temporarily unavailable" }

The reason for this is my attempt to set the non-blocking flag to true when accepting connections. The reason for this is my (until now) limited understanding of non-blocking sockets. This is explained in more detail here and here.

A non-blocking accept returns immediately with an error EAGAIN or EWOULDBLOCK in case there is no client connecting at that time. You actually see a kind: WouldBlock in the error message. You typically handle that by checking for these errors and trying again a little bit later, which is a little bit out-of-scope for this blog post.

When we run the WASM application with the fix, we get the expected output:

Going to bind to port 8000
Bound to port 8000
Accepted client
Accepted client
Accepted client

Requests can be sent via curl:

C:\Users\rwwil>curl -d "Server-side WASM" -X POST
echo: Server-side WASM