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()?);
wstd::runtime::spawn(async move {
// If echo copy fails, we can ignore it.
let _ = io::copy(&stream, &stream).await;
})
.detach();
}
Ok(())
}HTTP Client
use std::error::Error;
use wstd::http::{Body, Client, HeaderValue, Request};
#[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(Body::empty())?;
let response = Client::new().send(request).await?;
let content_type = response
.headers()
.get("Content-Type")
.ok_or("response expected to have Content-Type header")?;
assert_eq!(content_type, "application/json; charset=utf-8");
let mut body = response.into_body();
let body_len = body
.content_length()
.ok_or("GET postman-echo.com/get is supposed to provide a content-length")?;
let contents = body.contents().await?;
assert_eq!(
contents.len() as u64,
body_len,
"contents length should match content-length"
);
let val: serde_json::Value = serde_json::from_slice(contents)?;
let body_url = val
.get("url")
.ok_or("body json has url")?
.as_str()
.ok_or("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("body json has headers")?
.get("my-header")
.ok_or("headers contains my-header")?
.as_str()
.ok_or("my-header is a str")?,
"my-value"
);
Ok(())
}HTTP Server
use anyhow::{Context, Result};
use futures_lite::stream::{once_future, unfold};
use http_body_util::{BodyExt, StreamBody};
use std::convert::Infallible;
use wstd::http::body::{Body, Bytes, Frame};
use wstd::http::{Error, HeaderMap, Request, Response, StatusCode};
use wstd::time::{Duration, Instant};
#[wstd::http_server]
async fn main(request: Request<Body>) -> Result<Response<Body>, Error> {
let path = request.uri().path_and_query().unwrap().as_str();
println!("serving {path}");
match path {
"/" => http_home(request).await,
"/wait-response" => http_wait_response(request).await,
"/wait-body" => http_wait_body(request).await,
"/stream-body" => http_stream_body(request).await,
"/echo" => http_echo(request).await,
"/echo-headers" => http_echo_headers(request).await,
"/echo-trailers" => http_echo_trailers(request).await,
"/response-status" => http_response_status(request).await,
"/response-fail" => http_response_fail(request).await,
"/response-body-fail" => http_body_fail(request).await,
_ => http_not_found(request).await,
}
}
async fn http_home(_request: Request<Body>) -> Result<Response<Body>> {
// To send a single string as the response body, use `Responder::respond`.
Ok(Response::new(
"Hello, wasi:http/proxy world!\n".to_owned().into(),
))
}
async fn http_wait_response(_request: Request<Body>) -> Result<Response<Body>> {
// 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();
Ok(Response::new(
format!("slept for {elapsed} millis\n").into(),
))
}
async fn http_wait_body(_request: Request<Body>) -> Result<Response<Body>> {
// Get the time now
let now = Instant::now();
let body = async move {
// 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();
Ok::<_, Infallible>(Bytes::from(format!("slept for {elapsed} millis\n")))
};
Ok(Response::new(Body::from_try_stream(once_future(body))))
}
async fn http_stream_body(_request: Request<Body>) -> Result<Response<Body>> {
// Get the time now
let start = Instant::now();
let body = move |iters: usize| async move {
if iters == 0 {
return None;
}
// Sleep for 0.1 second.
wstd::task::sleep(Duration::from_millis(100)).await;
// Compute how long we slept for.
let elapsed = Instant::now().duration_since(start).as_millis();
Some((
Ok::<_, Infallible>(Bytes::from(format!(
"stream started {elapsed} millis ago\n"
))),
iters - 1,
))
};
Ok(Response::new(Body::from_try_stream(unfold(5, body))))
}
async fn http_echo(request: Request<Body>) -> Result<Response<Body>> {
let (_parts, body) = request.into_parts();
Ok(Response::new(body))
}
async fn http_echo_headers(request: Request<Body>) -> Result<Response<Body>> {
let mut response = Response::builder();
*response.headers_mut().unwrap() = request.into_parts().0.headers;
Ok(response.body("".to_owned().into())?)
}
async fn http_echo_trailers(request: Request<Body>) -> Result<Response<Body>> {
let collected = request.into_body().into_boxed_body().collect().await?;
let trailers = collected.trailers().cloned().unwrap_or_else(|| {
let mut trailers = HeaderMap::new();
trailers.insert("x-no-trailers", "1".parse().unwrap());
trailers
});
let body = StreamBody::new(once_future(async move {
anyhow::Ok(Frame::<Bytes>::trailers(trailers))
}));
Ok(Response::new(Body::from_http_body(body)))
}
async fn http_response_status(request: Request<Body>) -> Result<Response<Body>> {
let status = if let Some(header_val) = request.headers().get("x-response-status") {
header_val
.to_str()
.context("contents of x-response-status")?
.parse::<u16>()
.context("u16 value from x-response-status")?
} else {
500
};
let mut response = Response::builder().status(status);
if status == 302 {
response = response.header("Location", "http://localhost/response-status");
}
Ok(response.body(String::new().into())?)
}
async fn http_response_fail(_request: Request<Body>) -> Result<Response<Body>> {
Err(anyhow::anyhow!("error creating response"))
}
async fn http_body_fail(_request: Request<Body>) -> Result<Response<Body>> {
let body = StreamBody::new(once_future(async move {
Err::<Frame<Bytes>, _>(anyhow::anyhow!("error creating body"))
}));
Ok(Response::new(Body::from_http_body(body)))
}
async fn http_not_found(_request: Request<Body>) -> Result<Response<Body>> {
let response = Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap();
Ok(response)
}§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