Expand description
An async standard library for Wasm Components and WASI 0.2
This is a minimal async standard library written exclusively to support Wasm Components. It exists primarily to enable people to write async-based applications in Rust before async-std, smol, or tokio land support for Wasm Components and WASI 0.2. Once those runtimes land support, it is recommended users switch to use those instead.
§Examples
TCP echo server
use wstd::io;
use wstd::iter::AsyncIterator;
use wstd::net::TcpListener;
#[wstd::main]
async fn main() -> io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
println!("Listening on {}", listener.local_addr()?);
println!("type `nc localhost 8080` to create a TCP client");
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await {
let stream = stream?;
println!("Accepted from: {}", stream.peer_addr()?);
io::copy(&stream, &stream).await?;
}
Ok(())
}
HTTP Client
use std::error::Error;
use wstd::http::{Body, Client, HeaderValue, Request};
use wstd::io::{empty, AsyncRead};
#[wstd::test]
async fn main() -> Result<(), Box<dyn Error>> {
let request = Request::get("https://postman-echo.com/get")
.header("my-header", HeaderValue::from_str("my-value")?)
.body(empty())?;
let mut response = Client::new().send(request).await?;
let content_type = response
.headers()
.get("Content-Type")
.ok_or_else(|| "response expected to have Content-Type header")?;
assert_eq!(content_type, "application/json; charset=utf-8");
let body = response.body_mut();
let body_len = body
.len()
.ok_or_else(|| "GET postman-echo.com/get is supposed to provide a content-length")?;
let mut body_buf = Vec::new();
body.read_to_end(&mut body_buf).await?;
assert_eq!(
body_buf.len(),
body_len,
"read_to_end length should match content-length"
);
let val: serde_json::Value = serde_json::from_slice(&body_buf)?;
let body_url = val
.get("url")
.ok_or_else(|| "body json has url")?
.as_str()
.ok_or_else(|| "body json url is str")?;
assert!(
body_url.contains("postman-echo.com/get"),
"expected body url to contain the authority and path, got: {body_url}"
);
assert_eq!(
val.get("headers")
.ok_or_else(|| "body json has headers")?
.get("my-header")
.ok_or_else(|| "headers contains my-header")?
.as_str()
.ok_or_else(|| "my-header is a str")?,
"my-value"
);
Ok(())
}
HTTP Server
use wstd::http::body::{BodyForthcoming, IncomingBody, OutgoingBody};
use wstd::http::server::{Finished, Responder};
use wstd::http::{IntoBody, Request, Response, StatusCode};
use wstd::io::{copy, empty, AsyncWrite};
use wstd::time::{Duration, Instant};
#[wstd::http_server]
async fn main(request: Request<IncomingBody>, responder: Responder) -> Finished {
match request.uri().path_and_query().unwrap().as_str() {
"/wait" => http_wait(request, responder).await,
"/echo" => http_echo(request, responder).await,
"/echo-headers" => http_echo_headers(request, responder).await,
"/echo-trailers" => http_echo_trailers(request, responder).await,
"/fail" => http_fail(request, responder).await,
"/bigfail" => http_bigfail(request, responder).await,
"/" => http_home(request, responder).await,
_ => http_not_found(request, responder).await,
}
}
async fn http_home(_request: Request<IncomingBody>, responder: Responder) -> Finished {
// To send a single string as the response body, use `Responder::respond`.
responder
.respond(Response::new("Hello, wasi:http/proxy world!\n".into_body()))
.await
}
async fn http_wait(_request: Request<IncomingBody>, responder: Responder) -> Finished {
// Get the time now
let now = Instant::now();
// Sleep for one second.
wstd::task::sleep(Duration::from_secs(1)).await;
// Compute how long we slept for.
let elapsed = Instant::now().duration_since(now).as_millis();
// To stream data to the response body, use `Responder::start_response`.
let mut body = responder.start_response(Response::new(BodyForthcoming));
let result = body
.write_all(format!("slept for {elapsed} millis\n").as_bytes())
.await;
Finished::finish(body, result, None)
}
async fn http_echo(mut request: Request<IncomingBody>, responder: Responder) -> Finished {
// Stream data from the request body to the response body.
let mut body = responder.start_response(Response::new(BodyForthcoming));
let result = copy(request.body_mut(), &mut body).await;
Finished::finish(body, result, None)
}
async fn http_fail(_request: Request<IncomingBody>, responder: Responder) -> Finished {
let body = responder.start_response(Response::new(BodyForthcoming));
Finished::fail(body)
}
async fn http_bigfail(_request: Request<IncomingBody>, responder: Responder) -> Finished {
async fn write_body(body: &mut OutgoingBody) -> wstd::io::Result<()> {
for _ in 0..0x10 {
body.write_all("big big big big\n".as_bytes()).await?;
}
body.flush().await?;
Ok(())
}
let mut body = responder.start_response(Response::new(BodyForthcoming));
let _ = write_body(&mut body).await;
Finished::fail(body)
}
async fn http_echo_headers(request: Request<IncomingBody>, responder: Responder) -> Finished {
let mut response = Response::builder();
*response.headers_mut().unwrap() = request.into_parts().0.headers;
let response = response.body(empty()).unwrap();
responder.respond(response).await
}
async fn http_echo_trailers(request: Request<IncomingBody>, responder: Responder) -> Finished {
let body = responder.start_response(Response::new(BodyForthcoming));
let (trailers, result) = match request.into_body().finish().await {
Ok(trailers) => (trailers, Ok(())),
Err(err) => (Default::default(), Err(std::io::Error::other(err))),
};
Finished::finish(body, result, trailers)
}
async fn http_not_found(_request: Request<IncomingBody>, responder: Responder) -> Finished {
let response = Response::builder()
.status(StatusCode::NOT_FOUND)
.body(empty())
.unwrap();
responder.respond(response).await
}
§Design Decisions
This library is entirely self-contained. This means that it does not share any traits or types with any other async runtimes. This means we’re trading in some compatibility for ease of maintenance. Because this library is not intended to be maintained in the long term, this seems like the right tradeoff to make.
WASI 0.2 does not yet support multi-threading. For that reason this library
does not provide any multi-threaded primitives, and is free to make liberal
use of Async Functions in Traits since no Send
bounds are required. This
makes for a simpler end-user experience, again at the cost of some
compatibility. Though ultimately we do believe that using Async Functions is
the right foundation for the standard library abstractions - meaning we may
be trading in backward-compatibility for forward-compatibility.
This library also supports slightly more interfaces than the stdlib does.
For example wstd::rand
is a new module that provides access to random
bytes. And wstd::runtime
provides access to async runtime primitives.
These are unique capabilities provided by WASI 0.2, and because this library
is specific to that are exposed from here.
Modules§
- future
- Asynchronous values.
- http
- HTTP networking support
- io
- Async IO abstractions.
- iter
- Composable async iteration.
- net
- Async network abstractions.
- prelude
- rand
- Random number generation.
- runtime
- Async event loop support.
- task
- Types and Traits for working with asynchronous tasks.
- time
- Async time interfaces.
Attribute Macros§
- http_
server - Enables a HTTP server main function, for creating HTTP servers.
- main
- test