Skip to main content

Server

For WasmEdge to become a cloud-native runtime for microservices, it needs to support HTTP servers. By its very nature, the HTTP server is always asynchronous (non-blocking -- so that it can handle concurrent requests). This chapter will cover HTTP servers using popular Rust APIs.

note

Before we start, you need to have Rust and WasmEdge installed. Make sure that you read the special notes on networking apps especially if you are compiling Rust programs on a Mac.

The axum API

The axum crate is the most popular HTTP server framework in the Rust Tokio ecosystem. It is also the web framework for many popular services such as the flows.network serverless platform for workflow functions.

Use the axum API to create an asynchronous HTTP server. Build and run the example in WasmEdge as follows.

git clone https://github.com/WasmEdge/wasmedge_hyper_demo
cd wasmedge_hyper_demo/server-axum

# Build the Rust code
RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release
# Use the AoT compiler for better performance
wasmedge compile target/wasm32-wasi/release/wasmedge_axum_server.wasm wasmedge_axum_server.wasm

# Run the example
wasmedge wasmedge_axum_server.wasm

Then from another terminal, you can request the server. The HTTP server echoes the request data and sends back the response.

$ curl http://localhost:8080/echo -X POST -d "WasmEdge"
WasmEdge

In your Rust application, you will apply a few patches developed by the WasmEdge community to replace POSIX sockets with WasmEdge sockets in standard libraries. With those patches, you can then use the official tokio and axum crates.

[patch.crates-io]
tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" }
socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" }
hyper = { git = "https://github.com/second-state/wasi_hyper.git", branch = "v0.14.x" }

[dependencies]
axum = "0.6"
bytes = "1"
futures-util = "0.3.30"
tokio = { version = "1", features = ["rt", "macros", "net", "time", "io-util"]}

The Rust example code below shows an HTTP server that responds to incoming requests for the / and /echo URL endpoints.

#[tokio::main(flavor = "current_thread")]
async fn main() {
// build our application with a route
let app = Router::new()
.route("/", get(help))
.route("/echo", post(echo));

// run it
let addr = "0.0.0.0:8080";
let tcp_listener = TcpListener::bind(addr).await.unwrap();
println!("listening on {}", addr);
axum::Server::from_tcp(tcp_listener.into_std().unwrap())
.unwrap()
.serve(app.into_make_service())
.await
.unwrap();
}

The echo() function is called when a POST request is received at /echo. The function receives and processes the request body and returns bytes that are sent back as the response message.

async fn echo(mut stream: BodyStream) -> Bytes {
if let Some(Ok(s)) = stream.next().await {
s
} else {
Bytes::new()
}
}

The hyper API

The hyper crate is an excellent library for building HTTP servers using customizable low level APIs. Build and run the example in WasmEdge as follows.

git clone https://github.com/WasmEdge/wasmedge_hyper_demo
cd wasmedge_hyper_demo/server

# Build the Rust code
RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release
# Use the AoT compiler to get better performance
wasmedge compile target/wasm32-wasi/release/wasmedge_hyper_server.wasm wasmedge_hyper_server.wasm

# Run the example
wasmedge wasmedge_hyper_server.wasm

Then from another terminal, you can request the server. The HTTP server echoes the request data and sends back the response.

$ curl http://localhost:8080/echo -X POST -d "WasmEdge"
WasmEdge

In your Rust application, import the hyper and tokio crates, as well as the WasmEdge patches. Just add the following lines to your Cargo.toml.

[patch.crates-io]
tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" }
socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" }
hyper = { git = "https://github.com/second-state/wasi_hyper.git", branch = "v0.14.x" }

[dependencies]
hyper = { version = "0.14", features = ["full"]}
tokio = { version = "1", features = ["rt", "macros", "net", "time", "io-util"]}

The Rust example code below shows an HTTP server that echoes back any incoming request.

async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
match (req.method(), req.uri().path()) {
// Serve some instructions at /
(&Method::GET, "/") => Ok(Response::new(Body::from(
"Try POSTing data to /echo such as: `curl localhost:8080/echo -XPOST -d 'hello world'`",
))),

// Simply echo the body back to the client.
(&Method::POST, "/echo") => Ok(Response::new(req.into_body())),

(&Method::POST, "/echo/reversed") => {
let whole_body = hyper::body::to_bytes(req.into_body()).await?;

let reversed_body = whole_body.iter().rev().cloned().collect::<Vec<u8>>();
Ok(Response::new(Body::from(reversed_body)))
}

// Return the 404 Not Found for other routes.
_ => {
let mut not_found = Response::default();
*not_found.status_mut() = StatusCode::NOT_FOUND;
Ok(not_found)
}
}
}