Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ jobs:
role-session-name: github-ci

- name: check
run: cargo check --workspace --all --bins --examples
run: |
cargo check -p wstd -p wstd-axum --target wasm32-wasip2 --all-targets
cargo check -p test-programs

- name: wstd tests
run: cargo test -p wstd -p wstd-axum --target wasm32-wasip2 -- --nocapture
Expand All @@ -73,10 +75,12 @@ jobs:
run: cargo fmt --all -- --check

- name: Docs
run: cargo doc
run: cargo doc --target wasm32-wasip2

- name: Clippy
run: cargo clippy --all
run: |
cargo clippy -p wstd -p wstd-axum --target wasm32-wasip2 --all-targets
cargo clippy -p test-programs

verify-publish:
name: Verify publish
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ json = ["dep:serde", "dep:serde_json"]
anyhow.workspace = true
async-task.workspace = true
bytes.workspace = true
cfg-if.workspace = true
futures-lite.workspace = true
http-body-util.workspace = true
http-body.workspace = true
Expand Down Expand Up @@ -71,6 +72,7 @@ anyhow = "1"
async-task = "4.7"
axum = { version = "0.8.6", default-features = false }
bytes = "1.10.1"
cfg-if = "1"
cargo_metadata = "0.22"
clap = { version = "4.5.26", features = ["derive"] }
futures-core = "0.3.19"
Expand Down
4 changes: 4 additions & 0 deletions ci/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,10 @@ fn verify(crates: &[Crate]) {
.arg("--manifest-path")
.arg(&krate.manifest)
.env("CARGO_TARGET_DIR", "./target");
// wstd and wstd-axum only compile for wasm32-wasip2
if krate.name == "wstd" || krate.name == "wstd-axum" {
cmd.arg("--target").arg("wasm32-wasip2");
}
let status = cmd.status().unwrap();
assert!(status.success(), "failed to verify {:?}", &krate.manifest);
let tar = Command::new("tar")
Expand Down
69 changes: 69 additions & 0 deletions src/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! HTTP networking support

pub use http::status::StatusCode;
pub use http::uri::{Authority, PathAndQuery, Uri};

pub use crate::sys::http::client::Client;
pub use crate::sys::http::fields::{HeaderMap, HeaderName, HeaderValue};
pub use crate::sys::http::method::Method;
pub use crate::sys::http::scheme::{InvalidUri, Scheme};
#[doc(inline)]
pub use body::{Body, util::BodyExt};
pub use error::{Error, ErrorCode, Result};
pub use request::Request;
pub use response::Response;

pub mod body {
//! HTTP body types.
pub use crate::sys::http::body::*;
}

pub mod error {
//! The http portion of wstd uses `anyhow::Error` as its `Error` type.
//!
//! There are various concrete error types

pub use crate::http::body::InvalidContentLength;
pub use crate::sys::http::{ErrorCode, HeaderError};
pub use anyhow::Context;
pub use http::header::{InvalidHeaderName, InvalidHeaderValue};
pub use http::method::InvalidMethod;

pub type Error = anyhow::Error;
/// The `http` result type.
pub type Result<T> = std::result::Result<T, Error>;
}

pub mod request {
//! HTTP request types.
pub use crate::sys::http::request::*;
}

pub mod response {
//! HTTP response types.
pub use crate::sys::http::response::*;
}

pub mod server {
//! HTTP servers
//!
//! The WASI HTTP server uses the [typed main] idiom, with a `main` function
//! that takes a [`Request`] and succeeds with a [`Response`], using the
//! [`http_server`] macro:
//!
//! ```no_run
//! use wstd::http::{Request, Response, Body, Error};
//! #[wstd::http_server]
//! async fn main(_request: Request<Body>) -> Result<Response<Body>, Error> {
//! Ok(Response::new("Hello!\n".into()))
//! }
//! ```
//!
//! [typed main]: https://sunfishcode.github.io/typed-main-wasi-presentation/chapter_1.html
//! [`Request`]: crate::http::Request
//! [`Responder`]: crate::http::server::Responder
//! [`Response`]: crate::http::Response
//! [`http_server`]: crate::http_server

pub use crate::sys::http::server::*;
}
13 changes: 0 additions & 13 deletions src/http/error.rs

This file was deleted.

25 changes: 0 additions & 25 deletions src/http/mod.rs

This file was deleted.

5 changes: 1 addition & 4 deletions src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,15 @@ mod cursor;
mod empty;
mod read;
mod seek;
mod stdio;
mod streams;
mod write;

pub use crate::runtime::AsyncPollable;
pub use crate::sys::io::*;
pub use copy::*;
pub use cursor::*;
pub use empty::*;
pub use read::*;
pub use seek::*;
pub use stdio::*;
pub use streams::*;
pub use write::*;

/// The error type for I/O operations.
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
//! These are unique capabilities provided by WASI 0.2, and because this library
//! is specific to that are exposed from here.

#[allow(unreachable_pub)]
mod sys;

pub mod future;
#[macro_use]
pub mod http;
Expand Down
206 changes: 206 additions & 0 deletions src/net.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
//! Async network abstractions.
//!
//! The types here are the crate's public network surface. They are thin
//! facades that delegate to the selected backend's implementation under
//! [`crate::sys::net`]; the portable address-resolution loop in
//! [`TcpStream::connect`] is the one piece written once here rather than per
//! backend. A second backend only has to supply the `sys::net` primitives.

use crate::io::{self, AsyncInputStream, AsyncOutputStream, AsyncRead, AsyncWrite};
use crate::iter::AsyncIterator;
use std::io::ErrorKind;
use std::net::{SocketAddr, ToSocketAddrs};

/// A TCP stream between a local and a remote socket.
pub struct TcpStream {
inner: crate::sys::net::TcpStream,
}

impl TcpStream {
/// Opens a TCP connection to a remote host.
///
/// `addr` is an address of the remote host. Anything which implements the
/// [`ToSocketAddrs`] trait can be supplied as the address. If `addr`
/// yields multiple addresses, connect will be attempted with each of the
/// addresses until a connection is successful. If none of the addresses
/// result in a successful connection, the error returned from the last
/// connection attempt (the last address) is returned.
pub async fn connect(addr: impl ToSocketAddrs) -> io::Result<Self> {
let addrs = addr.to_socket_addrs()?;
let mut last_err = None;
for addr in addrs {
match TcpStream::connect_addr(addr).await {
Ok(stream) => return Ok(stream),
Err(e) => last_err = Some(e),
}
}

Err(last_err.unwrap_or_else(|| {
io::Error::new(ErrorKind::InvalidInput, "could not resolve to any address")
}))
}

/// Establishes a connection to the specified `addr`.
pub async fn connect_addr(addr: SocketAddr) -> io::Result<Self> {
Ok(Self {
inner: crate::sys::net::TcpStream::connect_addr(addr).await?,
})
}

/// Returns the socket address of the remote peer of this TCP connection.
pub fn peer_addr(&self) -> io::Result<String> {
self.inner.peer_addr()
}

/// Splits this stream into a read half and a write half, which can be used
/// to read and write concurrently.
pub fn split(&self) -> (ReadHalf<'_>, WriteHalf<'_>) {
let (read, write) = self.inner.split();
(ReadHalf(read), WriteHalf(write))
}
}

impl AsyncRead for TcpStream {
async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf).await
}

fn as_async_input_stream(&self) -> Option<&AsyncInputStream> {
self.inner.as_async_input_stream()
}
}

impl AsyncRead for &TcpStream {
async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut inner = &self.inner;
inner.read(buf).await
}

fn as_async_input_stream(&self) -> Option<&AsyncInputStream> {
self.inner.as_async_input_stream()
}
}

impl AsyncWrite for TcpStream {
async fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.inner.write(buf).await
}

async fn flush(&mut self) -> io::Result<()> {
self.inner.flush().await
}

fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> {
self.inner.as_async_output_stream()
}
}

impl AsyncWrite for &TcpStream {
async fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut inner = &self.inner;
inner.write(buf).await
}

async fn flush(&mut self) -> io::Result<()> {
let mut inner = &self.inner;
inner.flush().await
}

fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> {
self.inner.as_async_output_stream()
}
}

impl Drop for TcpStream {
fn drop(&mut self) {
// The socket shutdown runs when the inner backend handle is dropped.
}
}

/// The read half of a [`TcpStream`], created by [`TcpStream::split`].
pub struct ReadHalf<'a>(crate::sys::net::ReadHalf<'a>);

impl<'a> AsyncRead for ReadHalf<'a> {
async fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf).await
}

fn as_async_input_stream(&self) -> Option<&AsyncInputStream> {
self.0.as_async_input_stream()
}
}

impl<'a> Drop for ReadHalf<'a> {
fn drop(&mut self) {
// The receive-side shutdown runs when the inner backend handle is dropped.
}
}

/// The write half of a [`TcpStream`], created by [`TcpStream::split`].
pub struct WriteHalf<'a>(crate::sys::net::WriteHalf<'a>);

impl<'a> AsyncWrite for WriteHalf<'a> {
async fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf).await
}

async fn flush(&mut self) -> io::Result<()> {
self.0.flush().await
}

fn as_async_output_stream(&self) -> Option<&AsyncOutputStream> {
self.0.as_async_output_stream()
}
}

impl<'a> Drop for WriteHalf<'a> {
fn drop(&mut self) {
// The send-side shutdown runs when the inner backend handle is dropped.
}
}

/// A TCP socket server, listening for connections.
#[derive(Debug)]
pub struct TcpListener {
inner: crate::sys::net::TcpListener,
}

impl TcpListener {
/// Creates a new TcpListener which will be bound to the specified address.
///
/// The returned listener is ready for accepting connections.
pub async fn bind(addr: &str) -> io::Result<Self> {
Ok(Self {
inner: crate::sys::net::TcpListener::bind(addr).await?,
})
}

/// Returns the local socket address of this listener.
pub fn local_addr(&self) -> io::Result<std::net::SocketAddr> {
self.inner.local_addr()
}

/// Returns an iterator over the connections being received on this listener.
pub fn incoming(&self) -> Incoming<'_> {
Incoming {
inner: self.inner.incoming(),
}
}
}

/// An iterator that infinitely accepts connections on a [`TcpListener`].
#[derive(Debug)]
pub struct Incoming<'a> {
inner: crate::sys::net::Incoming<'a>,
}

impl<'a> AsyncIterator for Incoming<'a> {
type Item = io::Result<TcpStream>;

async fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.await
.map(|result| result.map(|inner| TcpStream { inner }))
}
}
Loading
Loading