Browse Source
* feat: align wasi-http with component linker * feat(wasi-http): allow bidirectional stream * feat(wasi-http): clean up children when dropping resource * chore: update based on feedback * chore: replace wasi http context references * chore: fix logical issue with outgoing body stream * chore: use thread-safe reference-counting pointer * chore: cleanup resources using table * fix(wasi-preview1-component-adapter): sync command extended wit * fix(wasi-preview1-component-adapter): sync command extended wit * fix(wasmtime-wasi): sync wit for http types * chore: refactor using wasmtime-wasi crate fix(wasi-http): misconfiguration in wasmtime linkers chore: keep streams details chore: fix wasi http tests * chore: use pollable from wasmtime-wasi * chore: update wasi http linker for module * chore: update test programs for wasi http * fix(wasi-http): ensure proper errors are surfaced * chore: split wasi http tests into individual files * chore: ensure protocol error is mapped correctly * chore: disable temporarily wasi http in wasmtime cli * chore: comment out wasi http in wasmtime cli * chore(ci): ensure wit definitions in sync * feat(wasi-http): generate async host binding * chore: make wasi http tests async * chore: update ci workflow based on suggestion Co-authored-by: Pat Hickey <pat@moreproductive.org> * feat(wasmtime-wasi): update logging world to latest * feat(wasmtime): update proxy world to latest * feat(wasmtime-wasi): add back command extended world * fix(wasi-http): sync wit definitions * chore: update tests with latest wit definitions * Update src/commands/run.rs * Update src/commands/run.rs * Update src/commands/run.rs * Update src/commands/run.rs * Update src/commands/run.rs * Update src/commands/run.rs * Update src/commands/run.rs * Update src/commands/run.rs * Update src/commands/run.rs * chore: fix formatting * Ignore flaky test * chore: fix compilation error for riscv64 arch * Avoid `cp -T` on macos Adding prtest:full to ensure that we've seen a successful build before queuing. * Don't build the wasi-http test programs for the native target * Debug the wit consistency check * Update streams.wit in wasi-http * Mark the component outbound_request_post test flaky * Disable flaky wasi-http-tests on windows only * Use diff instead of rm/cp/git diff * Disable more tests on windows --------- Co-authored-by: Eduardo Rodrigues <eduardomourar@users.noreply.github.com> Co-authored-by: Pat Hickey <pat@moreproductive.org> Co-authored-by: Trevor Elliott <telliott@fastly.com>pull/6858/head
Eduardo de Moura Rodrigues
1 year ago
committed by
GitHub
67 changed files with 4916 additions and 1078 deletions
@ -0,0 +1,96 @@ |
|||
use http_body_util::{combinators::BoxBody, BodyExt, Full}; |
|||
use hyper::{body::Bytes, service::service_fn, Request, Response}; |
|||
use std::{ |
|||
net::{SocketAddr, TcpListener, TcpStream}, |
|||
sync::OnceLock, |
|||
}; |
|||
|
|||
async fn test( |
|||
mut req: Request<hyper::body::Incoming>, |
|||
) -> http::Result<Response<BoxBody<Bytes, std::convert::Infallible>>> { |
|||
let method = req.method().to_string(); |
|||
let body = req.body_mut().collect().await.unwrap(); |
|||
let buf = body.to_bytes(); |
|||
|
|||
Response::builder() |
|||
.status(http::StatusCode::OK) |
|||
.header("x-wasmtime-test-method", method) |
|||
.header("x-wasmtime-test-uri", req.uri().to_string()) |
|||
.body(Full::<Bytes>::from(buf).boxed()) |
|||
} |
|||
|
|||
async fn serve_http1_connection(stream: TcpStream) -> Result<(), hyper::Error> { |
|||
let mut builder = hyper::server::conn::http1::Builder::new(); |
|||
let http = builder.keep_alive(false).pipeline_flush(true); |
|||
stream.set_nonblocking(true).unwrap(); |
|||
let io = tokio::net::TcpStream::from_std(stream).unwrap(); |
|||
http.serve_connection(io, service_fn(test)).await |
|||
} |
|||
|
|||
#[derive(Clone)] |
|||
/// An Executor that uses the tokio runtime.
|
|||
pub struct TokioExecutor; |
|||
|
|||
impl<F> hyper::rt::Executor<F> for TokioExecutor |
|||
where |
|||
F: std::future::Future + Send + 'static, |
|||
F::Output: Send + 'static, |
|||
{ |
|||
fn execute(&self, fut: F) { |
|||
tokio::task::spawn(fut); |
|||
} |
|||
} |
|||
|
|||
async fn serve_http2_connection(stream: TcpStream) -> Result<(), hyper::Error> { |
|||
let mut builder = hyper::server::conn::http2::Builder::new(TokioExecutor); |
|||
let http = builder.max_concurrent_streams(20); |
|||
let io = tokio::net::TcpStream::from_std(stream).unwrap(); |
|||
http.serve_connection(io, service_fn(test)).await |
|||
} |
|||
|
|||
pub async fn setup_http1( |
|||
future: impl std::future::Future<Output = anyhow::Result<()>>, |
|||
) -> Result<(), anyhow::Error> { |
|||
static CELL_HTTP1: OnceLock<TcpListener> = OnceLock::new(); |
|||
let listener = CELL_HTTP1.get_or_init(|| { |
|||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); |
|||
TcpListener::bind(addr).unwrap() |
|||
}); |
|||
|
|||
let thread = tokio::task::spawn(async move { |
|||
let (stream, _) = listener.accept().unwrap(); |
|||
let conn = serve_http1_connection(stream).await; |
|||
if let Err(err) = conn { |
|||
eprintln!("Error serving connection: {:?}", err); |
|||
} |
|||
}); |
|||
|
|||
let (future_result, thread_result) = tokio::join!(future, thread); |
|||
future_result?; |
|||
thread_result.unwrap(); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
pub async fn setup_http2( |
|||
future: impl std::future::Future<Output = anyhow::Result<()>>, |
|||
) -> anyhow::Result<()> { |
|||
static CELL_HTTP2: OnceLock<TcpListener> = OnceLock::new(); |
|||
let listener = CELL_HTTP2.get_or_init(|| { |
|||
let addr = SocketAddr::from(([127, 0, 0, 1], 3001)); |
|||
TcpListener::bind(addr).unwrap() |
|||
}); |
|||
let thread = tokio::task::spawn(async move { |
|||
let (stream, _) = listener.accept().unwrap(); |
|||
let conn = serve_http2_connection(stream).await; |
|||
if let Err(err) = conn { |
|||
eprintln!("Error serving connection: {:?}", err); |
|||
} |
|||
}); |
|||
|
|||
let (future_result, thread_result) = tokio::join!(future, thread); |
|||
future_result?; |
|||
thread_result.unwrap(); |
|||
|
|||
Ok(()) |
|||
} |
@ -0,0 +1,140 @@ |
|||
#![cfg(all(feature = "test_programs", not(skip_wasi_http_tests)))] |
|||
use wasmtime::{ |
|||
component::{Component, Linker}, |
|||
Config, Engine, Store, |
|||
}; |
|||
use wasmtime_wasi::preview2::{ |
|||
command::{add_to_linker, Command}, |
|||
Table, WasiCtx, WasiCtxBuilder, WasiView, |
|||
}; |
|||
use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; |
|||
|
|||
use test_programs::http_server::{setup_http1, setup_http2}; |
|||
|
|||
lazy_static::lazy_static! { |
|||
static ref ENGINE: Engine = { |
|||
let mut config = Config::new(); |
|||
config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); |
|||
config.wasm_component_model(true); |
|||
config.async_support(true); |
|||
let engine = Engine::new(&config).unwrap(); |
|||
engine |
|||
}; |
|||
} |
|||
// uses ENGINE, creates a fn get_module(&str) -> Module
|
|||
include!(concat!(env!("OUT_DIR"), "/wasi_http_tests_components.rs")); |
|||
|
|||
struct Ctx { |
|||
table: Table, |
|||
wasi: WasiCtx, |
|||
http: WasiHttpCtx, |
|||
} |
|||
|
|||
impl WasiView for Ctx { |
|||
fn table(&self) -> &Table { |
|||
&self.table |
|||
} |
|||
fn table_mut(&mut self) -> &mut Table { |
|||
&mut self.table |
|||
} |
|||
fn ctx(&self) -> &WasiCtx { |
|||
&self.wasi |
|||
} |
|||
fn ctx_mut(&mut self) -> &mut WasiCtx { |
|||
&mut self.wasi |
|||
} |
|||
} |
|||
|
|||
impl WasiHttpView for Ctx { |
|||
fn http_ctx(&self) -> &WasiHttpCtx { |
|||
&self.http |
|||
} |
|||
fn http_ctx_mut(&mut self) -> &mut WasiHttpCtx { |
|||
&mut self.http |
|||
} |
|||
} |
|||
|
|||
async fn instantiate_component( |
|||
component: Component, |
|||
ctx: Ctx, |
|||
) -> Result<(Store<Ctx>, Command), anyhow::Error> { |
|||
let mut linker = Linker::new(&ENGINE); |
|||
add_to_linker(&mut linker)?; |
|||
wasmtime_wasi_http::add_to_component_linker(&mut linker)?; |
|||
|
|||
let mut store = Store::new(&ENGINE, ctx); |
|||
|
|||
let (command, _instance) = Command::instantiate_async(&mut store, &component, &linker).await?; |
|||
Ok((store, command)) |
|||
} |
|||
|
|||
async fn run(name: &str) -> anyhow::Result<()> { |
|||
let mut table = Table::new(); |
|||
let component = get_component(name); |
|||
|
|||
// Create our wasi context.
|
|||
let wasi = WasiCtxBuilder::new() |
|||
.inherit_stdio() |
|||
.arg(name) |
|||
.build(&mut table)?; |
|||
let http = WasiHttpCtx::new(); |
|||
|
|||
let (mut store, command) = instantiate_component(component, Ctx { table, wasi, http }).await?; |
|||
command |
|||
.wasi_cli_run() |
|||
.call_run(&mut store) |
|||
.await |
|||
.map_err(|e| anyhow::anyhow!("wasm failed with {e:?}"))? |
|||
.map_err(|e| anyhow::anyhow!("command returned with failing exit status {e:?}")) |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_get() { |
|||
setup_http1(run("outbound_request_get")).await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
#[ignore = "test is currently flaky in ci and needs to be debugged"] |
|||
async fn outbound_request_post() { |
|||
setup_http1(run("outbound_request_post")).await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_put() { |
|||
setup_http1(run("outbound_request_put")).await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
#[cfg_attr(
|
|||
windows, |
|||
ignore = "test is currently flaky in ci and needs to be debugged" |
|||
)] |
|||
async fn outbound_request_invalid_version() { |
|||
setup_http2(run("outbound_request_invalid_version")) |
|||
.await |
|||
.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_unknown_method() { |
|||
run("outbound_request_unknown_method").await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_unsupported_scheme() { |
|||
run("outbound_request_unsupported_scheme").await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_invalid_port() { |
|||
run("outbound_request_invalid_port").await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
#[cfg_attr(
|
|||
windows, |
|||
ignore = "test is currently flaky in ci and needs to be debugged" |
|||
)] |
|||
async fn outbound_request_invalid_dnsname() { |
|||
run("outbound_request_invalid_dnsname").await.unwrap(); |
|||
} |
@ -0,0 +1,153 @@ |
|||
#![cfg(all(feature = "test_programs", not(skip_wasi_http_tests)))] |
|||
use wasmtime::{Config, Engine, Func, Linker, Module, Store}; |
|||
use wasmtime_wasi::preview2::{ |
|||
preview1::{WasiPreview1Adapter, WasiPreview1View}, |
|||
Table, WasiCtx, WasiCtxBuilder, WasiView, |
|||
}; |
|||
use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; |
|||
|
|||
use test_programs::http_server::{setup_http1, setup_http2}; |
|||
|
|||
lazy_static::lazy_static! { |
|||
static ref ENGINE: Engine = { |
|||
let mut config = Config::new(); |
|||
config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); |
|||
config.wasm_component_model(true); |
|||
config.async_support(true); |
|||
|
|||
let engine = Engine::new(&config).unwrap(); |
|||
engine |
|||
}; |
|||
} |
|||
// uses ENGINE, creates a fn get_module(&str) -> Module
|
|||
include!(concat!(env!("OUT_DIR"), "/wasi_http_tests_modules.rs")); |
|||
|
|||
struct Ctx { |
|||
table: Table, |
|||
wasi: WasiCtx, |
|||
adapter: WasiPreview1Adapter, |
|||
http: WasiHttpCtx, |
|||
} |
|||
|
|||
impl WasiView for Ctx { |
|||
fn table(&self) -> &Table { |
|||
&self.table |
|||
} |
|||
fn table_mut(&mut self) -> &mut Table { |
|||
&mut self.table |
|||
} |
|||
fn ctx(&self) -> &WasiCtx { |
|||
&self.wasi |
|||
} |
|||
fn ctx_mut(&mut self) -> &mut WasiCtx { |
|||
&mut self.wasi |
|||
} |
|||
} |
|||
impl WasiPreview1View for Ctx { |
|||
fn adapter(&self) -> &WasiPreview1Adapter { |
|||
&self.adapter |
|||
} |
|||
fn adapter_mut(&mut self) -> &mut WasiPreview1Adapter { |
|||
&mut self.adapter |
|||
} |
|||
} |
|||
impl WasiHttpView for Ctx { |
|||
fn http_ctx(&self) -> &WasiHttpCtx { |
|||
&self.http |
|||
} |
|||
fn http_ctx_mut(&mut self) -> &mut WasiHttpCtx { |
|||
&mut self.http |
|||
} |
|||
} |
|||
|
|||
async fn instantiate_module(module: Module, ctx: Ctx) -> Result<(Store<Ctx>, Func), anyhow::Error> { |
|||
let mut linker = Linker::new(&ENGINE); |
|||
wasmtime_wasi_http::add_to_linker(&mut linker)?; |
|||
wasmtime_wasi::preview2::preview1::add_to_linker(&mut linker)?; |
|||
|
|||
let mut store = Store::new(&ENGINE, ctx); |
|||
|
|||
let instance = linker.instantiate_async(&mut store, &module).await?; |
|||
let command = instance.get_func(&mut store, "wasi:cli/run#run").unwrap(); |
|||
Ok((store, command)) |
|||
} |
|||
|
|||
async fn run(name: &str) -> anyhow::Result<()> { |
|||
let mut table = Table::new(); |
|||
let module = get_module(name); |
|||
|
|||
// Create our wasi context.
|
|||
let wasi = WasiCtxBuilder::new() |
|||
.inherit_stdio() |
|||
.arg(name) |
|||
.build(&mut table)?; |
|||
let http = WasiHttpCtx::new(); |
|||
|
|||
let adapter = WasiPreview1Adapter::new(); |
|||
|
|||
let (mut store, command) = instantiate_module( |
|||
module, |
|||
Ctx { |
|||
table, |
|||
wasi, |
|||
http, |
|||
adapter, |
|||
}, |
|||
) |
|||
.await?; |
|||
command |
|||
.call_async(&mut store, &[], &mut [wasmtime::Val::null()]) |
|||
.await |
|||
.map_err(|e| anyhow::anyhow!("command returned with failing exit status {e:?}")) |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_get() { |
|||
setup_http1(run("outbound_request_get")).await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
#[ignore = "test is currently flaky in ci and needs to be debugged"] |
|||
async fn outbound_request_post() { |
|||
setup_http1(run("outbound_request_post")).await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_put() { |
|||
setup_http1(run("outbound_request_put")).await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
#[cfg_attr(
|
|||
windows, |
|||
ignore = "test is currently flaky in ci and needs to be debugged" |
|||
)] |
|||
async fn outbound_request_invalid_version() { |
|||
setup_http2(run("outbound_request_invalid_version")) |
|||
.await |
|||
.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_unknown_method() { |
|||
run("outbound_request_unknown_method").await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_unsupported_scheme() { |
|||
run("outbound_request_unsupported_scheme").await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
async fn outbound_request_invalid_port() { |
|||
run("outbound_request_invalid_port").await.unwrap(); |
|||
} |
|||
|
|||
#[test_log::test(tokio::test(flavor = "multi_thread"))] |
|||
#[cfg_attr(
|
|||
windows, |
|||
ignore = "test is currently flaky in ci and needs to be debugged" |
|||
)] |
|||
async fn outbound_request_invalid_dnsname() { |
|||
run("outbound_request_invalid_dnsname").await.unwrap(); |
|||
} |
@ -1,97 +0,0 @@ |
|||
#![cfg(all(feature = "test_programs", not(skip_wasi_http_tests)))] |
|||
use wasmtime::{Config, Engine, Linker, Store}; |
|||
use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; |
|||
use wasmtime_wasi_http::WasiHttp; |
|||
|
|||
use http_body_util::combinators::BoxBody; |
|||
use http_body_util::BodyExt; |
|||
use hyper::server::conn::http1; |
|||
use hyper::{body::Bytes, service::service_fn, Request, Response}; |
|||
use std::{error::Error, net::SocketAddr}; |
|||
use tokio::net::TcpListener; |
|||
|
|||
lazy_static::lazy_static! { |
|||
static ref ENGINE: Engine = { |
|||
let mut config = Config::new(); |
|||
config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); |
|||
let engine = Engine::new(&config).unwrap(); |
|||
engine |
|||
}; |
|||
} |
|||
// uses ENGINE, creates a fn get_module(&str) -> Module
|
|||
include!(concat!(env!("OUT_DIR"), "/wasi_http_tests_modules.rs")); |
|||
|
|||
async fn test( |
|||
req: Request<hyper::body::Incoming>, |
|||
) -> http::Result<Response<BoxBody<Bytes, hyper::Error>>> { |
|||
let method = req.method().to_string(); |
|||
Response::builder() |
|||
.status(http::StatusCode::OK) |
|||
.header("x-wasmtime-test-method", method) |
|||
.header("x-wasmtime-test-uri", req.uri().to_string()) |
|||
.body(req.into_body().boxed()) |
|||
} |
|||
|
|||
async fn async_run_serve() -> Result<(), Box<dyn Error + Send + Sync>> { |
|||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); |
|||
|
|||
let listener = TcpListener::bind(addr).await?; |
|||
|
|||
loop { |
|||
let (stream, _) = listener.accept().await?; |
|||
|
|||
tokio::task::spawn(async move { |
|||
if let Err(err) = http1::Builder::new() |
|||
.serve_connection(stream, service_fn(test)) |
|||
.await |
|||
{ |
|||
println!("Error serving connection: {:?}", err); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
fn run_server() -> Result<(), Box<dyn Error + Send + Sync>> { |
|||
let rt = tokio::runtime::Runtime::new()?; |
|||
let _ent = rt.enter(); |
|||
|
|||
rt.block_on(async_run_serve())?; |
|||
Ok(()) |
|||
} |
|||
|
|||
pub fn run(name: &str) -> anyhow::Result<()> { |
|||
let _thread = std::thread::spawn(|| { |
|||
run_server().unwrap(); |
|||
}); |
|||
|
|||
let module = get_module(name); |
|||
let mut linker = Linker::new(&ENGINE); |
|||
|
|||
struct Ctx { |
|||
wasi: WasiCtx, |
|||
http: WasiHttp, |
|||
} |
|||
|
|||
wasmtime_wasi::sync::add_to_linker(&mut linker, |cx: &mut Ctx| &mut cx.wasi)?; |
|||
wasmtime_wasi_http::add_to_linker(&mut linker, |cx: &mut Ctx| &mut cx.http)?; |
|||
|
|||
// Create our wasi context.
|
|||
let wasi = WasiCtxBuilder::new().inherit_stdio().arg(name)?.build(); |
|||
|
|||
let mut store = Store::new( |
|||
&ENGINE, |
|||
Ctx { |
|||
wasi, |
|||
http: WasiHttp::new(), |
|||
}, |
|||
); |
|||
|
|||
let instance = linker.instantiate(&mut store, &module)?; |
|||
let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?; |
|||
start.call(&mut store, ()) |
|||
} |
|||
|
|||
#[test_log::test] |
|||
fn outbound_request() { |
|||
run("outbound_request").unwrap() |
|||
} |
@ -1,188 +0,0 @@ |
|||
use anyhow::{anyhow, Context, Result}; |
|||
use std::fmt; |
|||
use wasi_http_tests::*; |
|||
|
|||
struct Response { |
|||
status: wasi::http::types::StatusCode, |
|||
headers: Vec<(String, Vec<u8>)>, |
|||
body: Vec<u8>, |
|||
} |
|||
impl fmt::Debug for Response { |
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|||
let mut out = f.debug_struct("Response"); |
|||
out.field("status", &self.status) |
|||
.field("headers", &self.headers); |
|||
if let Ok(body) = std::str::from_utf8(&self.body) { |
|||
out.field("body", &body); |
|||
} else { |
|||
out.field("body", &self.body); |
|||
} |
|||
out.finish() |
|||
} |
|||
} |
|||
|
|||
impl Response { |
|||
fn header(&self, name: &str) -> Option<&Vec<u8>> { |
|||
self.headers |
|||
.iter() |
|||
.find_map(|(k, v)| if k == name { Some(v) } else { None }) |
|||
} |
|||
} |
|||
|
|||
fn request( |
|||
method: wasi::http::types::Method, |
|||
scheme: wasi::http::types::Scheme, |
|||
authority: &str, |
|||
path_with_query: &str, |
|||
body: &[u8], |
|||
) -> Result<Response> { |
|||
let headers = wasi::http::types::new_fields(&[ |
|||
("User-agent".to_string(), "WASI-HTTP/0.0.1".to_string()), |
|||
("Content-type".to_string(), "application/json".to_string()), |
|||
]); |
|||
|
|||
let request = wasi::http::types::new_outgoing_request( |
|||
&method, |
|||
Some(&path_with_query), |
|||
Some(&scheme), |
|||
Some(&authority), |
|||
headers, |
|||
); |
|||
|
|||
let request_body = wasi::http::types::outgoing_request_write(request) |
|||
.map_err(|_| anyhow!("outgoing request write failed"))?; |
|||
|
|||
let mut body_cursor = 0; |
|||
while body_cursor < body.len() { |
|||
let written = wasi::io::streams::write(request_body, &body[body_cursor..]) |
|||
.context("writing request body")?; |
|||
body_cursor += written as usize; |
|||
} |
|||
|
|||
let future_response = wasi::http::outgoing_handler::handle(request, None); |
|||
|
|||
let incoming_response = wasi::http::types::future_incoming_response_get(future_response) |
|||
.ok_or_else(|| anyhow!("incoming response is available immediately"))? |
|||
// TODO: maybe anything that appears in the Result<_, E> position should impl
|
|||
// Error? anyway, just use its Debug here:
|
|||
.map_err(|e| anyhow!("{e:?}"))?; |
|||
|
|||
// TODO: The current implementation requires this drop after the request is sent.
|
|||
// The ownership semantics are unclear in wasi-http we should clarify exactly what is
|
|||
// supposed to happen here.
|
|||
wasi::io::streams::drop_output_stream(request_body); |
|||
|
|||
// TODO: we could create a pollable from the future_response and poll on it here to test that
|
|||
// its available immediately
|
|||
|
|||
wasi::http::types::drop_outgoing_request(request); |
|||
|
|||
wasi::http::types::drop_future_incoming_response(future_response); |
|||
|
|||
let status = wasi::http::types::incoming_response_status(incoming_response); |
|||
|
|||
let headers_handle = wasi::http::types::incoming_response_headers(incoming_response); |
|||
let headers = wasi::http::types::fields_entries(headers_handle); |
|||
wasi::http::types::drop_fields(headers_handle); |
|||
|
|||
let body_stream = wasi::http::types::incoming_response_consume(incoming_response) |
|||
.map_err(|()| anyhow!("incoming response has no body stream"))?; |
|||
|
|||
let mut body = Vec::new(); |
|||
let mut eof = false; |
|||
while !eof { |
|||
let (mut body_chunk, stream_ended) = wasi::io::streams::read(body_stream, u64::MAX)?; |
|||
eof = stream_ended; |
|||
body.append(&mut body_chunk); |
|||
} |
|||
wasi::io::streams::drop_input_stream(body_stream); |
|||
wasi::http::types::drop_incoming_response(incoming_response); |
|||
|
|||
Ok(Response { |
|||
status, |
|||
headers, |
|||
body, |
|||
}) |
|||
} |
|||
|
|||
fn main() -> Result<()> { |
|||
let r1 = request( |
|||
wasi::http::types::Method::Get, |
|||
wasi::http::types::Scheme::Http, |
|||
"localhost:3000", |
|||
"/get?some=arg?goes=here", |
|||
&[], |
|||
) |
|||
.context("localhost:3000 /get")?; |
|||
|
|||
println!("localhost:3000 /get: {r1:?}"); |
|||
assert_eq!(r1.status, 200); |
|||
let method = r1.header("x-wasmtime-test-method").unwrap(); |
|||
assert_eq!(std::str::from_utf8(method).unwrap(), "GET"); |
|||
let uri = r1.header("x-wasmtime-test-uri").unwrap(); |
|||
assert_eq!( |
|||
std::str::from_utf8(uri).unwrap(), |
|||
"http://localhost:3000/get?some=arg?goes=here" |
|||
); |
|||
assert_eq!(r1.body, b""); |
|||
|
|||
let r2 = request( |
|||
wasi::http::types::Method::Post, |
|||
wasi::http::types::Scheme::Http, |
|||
"localhost:3000", |
|||
"/post", |
|||
b"{\"foo\": \"bar\"}", |
|||
) |
|||
.context("localhost:3000 /post")?; |
|||
|
|||
println!("localhost:3000 /post: {r2:?}"); |
|||
assert_eq!(r2.status, 200); |
|||
let method = r2.header("x-wasmtime-test-method").unwrap(); |
|||
assert_eq!(std::str::from_utf8(method).unwrap(), "POST"); |
|||
assert_eq!(r2.body, b"{\"foo\": \"bar\"}"); |
|||
|
|||
let r3 = request( |
|||
wasi::http::types::Method::Put, |
|||
wasi::http::types::Scheme::Http, |
|||
"localhost:3000", |
|||
"/put", |
|||
&[], |
|||
) |
|||
.context("localhost:3000 /put")?; |
|||
|
|||
println!("localhost:3000 /put: {r3:?}"); |
|||
assert_eq!(r3.status, 200); |
|||
let method = r3.header("x-wasmtime-test-method").unwrap(); |
|||
assert_eq!(std::str::from_utf8(method).unwrap(), "PUT"); |
|||
assert_eq!(r3.body, b""); |
|||
|
|||
let r4 = request( |
|||
wasi::http::types::Method::Other("OTHER".to_owned()), |
|||
wasi::http::types::Scheme::Http, |
|||
"localhost:3000", |
|||
"/", |
|||
&[], |
|||
); |
|||
|
|||
let error = r4.unwrap_err(); |
|||
assert_eq!( |
|||
error.to_string(), |
|||
"Error::UnexpectedError(\"unknown method OTHER\")" |
|||
); |
|||
|
|||
let r5 = request( |
|||
wasi::http::types::Method::Get, |
|||
wasi::http::types::Scheme::Other("WS".to_owned()), |
|||
"localhost:3000", |
|||
"/", |
|||
&[], |
|||
); |
|||
|
|||
let error = r5.unwrap_err(); |
|||
assert_eq!( |
|||
error.to_string(), |
|||
"Error::UnexpectedError(\"unsupported scheme WS\")" |
|||
); |
|||
|
|||
Ok(()) |
|||
} |
@ -0,0 +1,41 @@ |
|||
use anyhow::{Context, Result}; |
|||
use wasi_http_tests::bindings::wasi::http::types::{Method, Scheme}; |
|||
|
|||
struct Component; |
|||
|
|||
fn main() {} |
|||
|
|||
async fn run() -> Result<(), ()> { |
|||
let res = wasi_http_tests::request( |
|||
Method::Get, |
|||
Scheme::Http, |
|||
"localhost:3000", |
|||
"/get?some=arg&goes=here", |
|||
None, |
|||
None, |
|||
) |
|||
.await |
|||
.context("localhost:3000 /get") |
|||
.unwrap(); |
|||
|
|||
println!("localhost:3000 /get: {res:?}"); |
|||
assert_eq!(res.status, 200); |
|||
let method = res.header("x-wasmtime-test-method").unwrap(); |
|||
assert_eq!(std::str::from_utf8(method).unwrap(), "GET"); |
|||
let uri = res.header("x-wasmtime-test-uri").unwrap(); |
|||
assert_eq!( |
|||
std::str::from_utf8(uri).unwrap(), |
|||
"http://localhost:3000/get?some=arg&goes=here" |
|||
); |
|||
assert_eq!(res.body, b""); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
impl wasi_http_tests::bindings::exports::wasi::cli::run::Run for Component { |
|||
fn run() -> Result<(), ()> { |
|||
wasi_http_tests::in_tokio(async { run().await }) |
|||
} |
|||
} |
|||
|
|||
wasi_http_tests::export_command_extended!(Component); |
@ -0,0 +1,31 @@ |
|||
use anyhow::Result; |
|||
use wasi_http_tests::bindings::wasi::http::types::{Method, Scheme}; |
|||
|
|||
struct Component; |
|||
|
|||
fn main() {} |
|||
|
|||
async fn run() -> Result<(), ()> { |
|||
let res = wasi_http_tests::request( |
|||
Method::Get, |
|||
Scheme::Http, |
|||
"some.invalid.dnsname:3000", |
|||
"/", |
|||
None, |
|||
None, |
|||
) |
|||
.await; |
|||
|
|||
let error = res.unwrap_err(); |
|||
assert_eq!(error.to_string(), "Error::InvalidUrl(\"invalid dnsname\")"); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
impl wasi_http_tests::bindings::exports::wasi::cli::run::Run for Component { |
|||
fn run() -> Result<(), ()> { |
|||
wasi_http_tests::in_tokio(async { run().await }) |
|||
} |
|||
} |
|||
|
|||
wasi_http_tests::export_command_extended!(Component); |
@ -0,0 +1,34 @@ |
|||
use anyhow::Result; |
|||
use wasi_http_tests::bindings::wasi::http::types::{Method, Scheme}; |
|||
|
|||
struct Component; |
|||
|
|||
fn main() {} |
|||
|
|||
async fn run() -> Result<(), ()> { |
|||
let res = wasi_http_tests::request( |
|||
Method::Get, |
|||
Scheme::Http, |
|||
"localhost:99999", |
|||
"/", |
|||
None, |
|||
None, |
|||
) |
|||
.await; |
|||
|
|||
let error = res.unwrap_err(); |
|||
assert_eq!( |
|||
error.to_string(), |
|||
"Error::InvalidUrl(\"invalid port value\")" |
|||
); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
impl wasi_http_tests::bindings::exports::wasi::cli::run::Run for Component { |
|||
fn run() -> Result<(), ()> { |
|||
wasi_http_tests::in_tokio(async { run().await }) |
|||
} |
|||
} |
|||
|
|||
wasi_http_tests::export_command_extended!(Component); |
@ -0,0 +1,40 @@ |
|||
use anyhow::Result; |
|||
use wasi_http_tests::bindings::wasi::http::types::{Method, Scheme}; |
|||
|
|||
struct Component; |
|||
|
|||
fn main() {} |
|||
|
|||
async fn run() -> Result<(), ()> { |
|||
let res = wasi_http_tests::request( |
|||
Method::Connect, |
|||
Scheme::Http, |
|||
"localhost:3001", |
|||
"/", |
|||
None, |
|||
Some(&[]), |
|||
) |
|||
.await; |
|||
|
|||
let error = res.unwrap_err().to_string(); |
|||
if error.ne("Error::ProtocolError(\"invalid HTTP version parsed\")") |
|||
&& error.ne("Error::ProtocolError(\"operation was canceled\")") |
|||
{ |
|||
panic!( |
|||
r#"assertion failed: `(left == right)` |
|||
left: `"{error}"`, |
|||
right: `"Error::ProtocolError(\"invalid HTTP version parsed\")"` |
|||
or `"Error::ProtocolError(\"operation was canceled\")"`)"# |
|||
) |
|||
} |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
impl wasi_http_tests::bindings::exports::wasi::cli::run::Run for Component { |
|||
fn run() -> Result<(), ()> { |
|||
wasi_http_tests::in_tokio(async { run().await }) |
|||
} |
|||
} |
|||
|
|||
wasi_http_tests::export_command_extended!(Component); |
@ -0,0 +1,36 @@ |
|||
use anyhow::{Context, Result}; |
|||
use wasi_http_tests::bindings::wasi::http::types::{Method, Scheme}; |
|||
|
|||
struct Component; |
|||
|
|||
fn main() {} |
|||
|
|||
async fn run() -> Result<(), ()> { |
|||
let res = wasi_http_tests::request( |
|||
Method::Post, |
|||
Scheme::Http, |
|||
"localhost:3000", |
|||
"/post", |
|||
Some(b"{\"foo\": \"bar\"}"), |
|||
None, |
|||
) |
|||
.await |
|||
.context("localhost:3000 /post") |
|||
.unwrap(); |
|||
|
|||
println!("localhost:3000 /post: {res:?}"); |
|||
assert_eq!(res.status, 200); |
|||
let method = res.header("x-wasmtime-test-method").unwrap(); |
|||
assert_eq!(std::str::from_utf8(method).unwrap(), "POST"); |
|||
assert_eq!(res.body, b"{\"foo\": \"bar\"}"); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
impl wasi_http_tests::bindings::exports::wasi::cli::run::Run for Component { |
|||
fn run() -> Result<(), ()> { |
|||
wasi_http_tests::in_tokio(async { run().await }) |
|||
} |
|||
} |
|||
|
|||
wasi_http_tests::export_command_extended!(Component); |
@ -0,0 +1,36 @@ |
|||
use anyhow::{Context, Result}; |
|||
use wasi_http_tests::bindings::wasi::http::types::{Method, Scheme}; |
|||
|
|||
struct Component; |
|||
|
|||
fn main() {} |
|||
|
|||
async fn run() -> Result<(), ()> { |
|||
let res = wasi_http_tests::request( |
|||
Method::Put, |
|||
Scheme::Http, |
|||
"localhost:3000", |
|||
"/put", |
|||
Some(&[]), |
|||
None, |
|||
) |
|||
.await |
|||
.context("localhost:3000 /put") |
|||
.unwrap(); |
|||
|
|||
println!("localhost:3000 /put: {res:?}"); |
|||
assert_eq!(res.status, 200); |
|||
let method = res.header("x-wasmtime-test-method").unwrap(); |
|||
assert_eq!(std::str::from_utf8(method).unwrap(), "PUT"); |
|||
assert_eq!(res.body, b""); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
impl wasi_http_tests::bindings::exports::wasi::cli::run::Run for Component { |
|||
fn run() -> Result<(), ()> { |
|||
wasi_http_tests::in_tokio(async { run().await }) |
|||
} |
|||
} |
|||
|
|||
wasi_http_tests::export_command_extended!(Component); |
@ -0,0 +1,34 @@ |
|||
use anyhow::Result; |
|||
use wasi_http_tests::bindings::wasi::http::types::{Method, Scheme}; |
|||
|
|||
struct Component; |
|||
|
|||
fn main() {} |
|||
|
|||
async fn run() -> Result<(), ()> { |
|||
let res = wasi_http_tests::request( |
|||
Method::Other("OTHER".to_owned()), |
|||
Scheme::Http, |
|||
"localhost:3000", |
|||
"/", |
|||
None, |
|||
None, |
|||
) |
|||
.await; |
|||
|
|||
let error = res.unwrap_err(); |
|||
assert_eq!( |
|||
error.to_string(), |
|||
"Error::InvalidUrl(\"unknown method OTHER\")" |
|||
); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
impl wasi_http_tests::bindings::exports::wasi::cli::run::Run for Component { |
|||
fn run() -> Result<(), ()> { |
|||
wasi_http_tests::in_tokio(async { run().await }) |
|||
} |
|||
} |
|||
|
|||
wasi_http_tests::export_command_extended!(Component); |
@ -0,0 +1,34 @@ |
|||
use anyhow::Result; |
|||
use wasi_http_tests::bindings::wasi::http::types::{Method, Scheme}; |
|||
|
|||
struct Component; |
|||
|
|||
fn main() {} |
|||
|
|||
async fn run() -> Result<(), ()> { |
|||
let res = wasi_http_tests::request( |
|||
Method::Get, |
|||
Scheme::Other("WS".to_owned()), |
|||
"localhost:3000", |
|||
"/", |
|||
None, |
|||
None, |
|||
) |
|||
.await; |
|||
|
|||
let error = res.unwrap_err(); |
|||
assert_eq!( |
|||
error.to_string(), |
|||
"Error::InvalidUrl(\"unsupported scheme WS\")" |
|||
); |
|||
|
|||
Ok(()) |
|||
} |
|||
|
|||
impl wasi_http_tests::bindings::exports::wasi::cli::run::Run for Component { |
|||
fn run() -> Result<(), ()> { |
|||
wasi_http_tests::in_tokio(async { run().await }) |
|||
} |
|||
} |
|||
|
|||
wasi_http_tests::export_command_extended!(Component); |
@ -1,3 +1,175 @@ |
|||
// The macro will generate a macro for defining exports which we won't be reusing
|
|||
#![allow(unused)] |
|||
wit_bindgen::generate!({ path: "../../wasi-http/wasi-http/wit" }); |
|||
pub mod bindings { |
|||
wit_bindgen::generate!({ |
|||
path: "../../wasi-http/wit", |
|||
world: "wasmtime:wasi/command-extended", |
|||
macro_call_prefix: "::wasi_http_tests::bindings::", |
|||
macro_export, |
|||
}); |
|||
} |
|||
|
|||
use anyhow::{anyhow, Context, Result}; |
|||
use std::fmt; |
|||
use std::sync::OnceLock; |
|||
|
|||
use bindings::wasi::http::{outgoing_handler, types as http_types}; |
|||
use bindings::wasi::io::streams; |
|||
use bindings::wasi::poll::poll; |
|||
|
|||
pub struct Response { |
|||
pub status: http_types::StatusCode, |
|||
pub headers: Vec<(String, Vec<u8>)>, |
|||
pub body: Vec<u8>, |
|||
} |
|||
impl fmt::Debug for Response { |
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|||
let mut out = f.debug_struct("Response"); |
|||
out.field("status", &self.status) |
|||
.field("headers", &self.headers); |
|||
if let Ok(body) = std::str::from_utf8(&self.body) { |
|||
out.field("body", &body); |
|||
} else { |
|||
out.field("body", &self.body); |
|||
} |
|||
out.finish() |
|||
} |
|||
} |
|||
|
|||
impl Response { |
|||
pub fn header(&self, name: &str) -> Option<&Vec<u8>> { |
|||
self.headers |
|||
.iter() |
|||
.find_map(|(k, v)| if k == name { Some(v) } else { None }) |
|||
} |
|||
} |
|||
|
|||
pub async fn request( |
|||
method: http_types::Method, |
|||
scheme: http_types::Scheme, |
|||
authority: &str, |
|||
path_with_query: &str, |
|||
body: Option<&[u8]>, |
|||
additional_headers: Option<&[(String, String)]>, |
|||
) -> Result<Response> { |
|||
let headers = http_types::new_fields( |
|||
&[ |
|||
&[ |
|||
("User-agent".to_string(), "WASI-HTTP/0.0.1".to_string()), |
|||
("Content-type".to_string(), "application/json".to_string()), |
|||
], |
|||
additional_headers.unwrap_or(&[]), |
|||
] |
|||
.concat(), |
|||
); |
|||
|
|||
let request = http_types::new_outgoing_request( |
|||
&method, |
|||
Some(path_with_query), |
|||
Some(&scheme), |
|||
Some(authority), |
|||
headers, |
|||
); |
|||
|
|||
let request_body = http_types::outgoing_request_write(request) |
|||
.map_err(|_| anyhow!("outgoing request write failed"))?; |
|||
|
|||
if let Some(body) = body { |
|||
let output_stream_pollable = streams::subscribe_to_output_stream(request_body); |
|||
let len = body.len(); |
|||
if len == 0 { |
|||
let (_written, _status) = streams::write(request_body, &[]) |
|||
.map_err(|_| anyhow!("request_body stream write failed")) |
|||
.context("writing empty request body")?; |
|||
} else { |
|||
let mut body_cursor = 0; |
|||
while body_cursor < body.len() { |
|||
let (written, _status) = streams::write(request_body, &body[body_cursor..]) |
|||
.map_err(|_| anyhow!("request_body stream write failed")) |
|||
.context("writing request body")?; |
|||
body_cursor += written as usize; |
|||
} |
|||
} |
|||
|
|||
// TODO: enable when working as expected
|
|||
// let _ = poll::poll_oneoff(&[output_stream_pollable]);
|
|||
|
|||
poll::drop_pollable(output_stream_pollable); |
|||
} |
|||
|
|||
let future_response = outgoing_handler::handle(request, None); |
|||
|
|||
let incoming_response = match http_types::future_incoming_response_get(future_response) { |
|||
Some(result) => result, |
|||
None => { |
|||
let pollable = http_types::listen_to_future_incoming_response(future_response); |
|||
let _ = poll::poll_oneoff(&[pollable]); |
|||
http_types::future_incoming_response_get(future_response) |
|||
.expect("incoming response available") |
|||
} |
|||
} |
|||
// TODO: maybe anything that appears in the Result<_, E> position should impl
|
|||
// Error? anyway, just use its Debug here:
|
|||
.map_err(|e| anyhow!("{e:?}"))?; |
|||
|
|||
// TODO: The current implementation requires this drop after the request is sent.
|
|||
// The ownership semantics are unclear in wasi-http we should clarify exactly what is
|
|||
// supposed to happen here.
|
|||
streams::drop_output_stream(request_body); |
|||
|
|||
http_types::drop_outgoing_request(request); |
|||
|
|||
http_types::drop_future_incoming_response(future_response); |
|||
|
|||
let status = http_types::incoming_response_status(incoming_response); |
|||
|
|||
let headers_handle = http_types::incoming_response_headers(incoming_response); |
|||
let headers = http_types::fields_entries(headers_handle); |
|||
http_types::drop_fields(headers_handle); |
|||
|
|||
let body_stream = http_types::incoming_response_consume(incoming_response) |
|||
.map_err(|()| anyhow!("incoming response has no body stream"))?; |
|||
let input_stream_pollable = streams::subscribe_to_input_stream(body_stream); |
|||
|
|||
let mut body = Vec::new(); |
|||
let mut eof = streams::StreamStatus::Open; |
|||
while eof != streams::StreamStatus::Ended { |
|||
let (mut body_chunk, stream_status) = |
|||
streams::read(body_stream, u64::MAX).map_err(|_| anyhow!("body_stream read failed"))?; |
|||
eof = if body_chunk.is_empty() { |
|||
streams::StreamStatus::Ended |
|||
} else { |
|||
stream_status |
|||
}; |
|||
body.append(&mut body_chunk); |
|||
} |
|||
|
|||
poll::drop_pollable(input_stream_pollable); |
|||
streams::drop_input_stream(body_stream); |
|||
http_types::drop_incoming_response(incoming_response); |
|||
|
|||
Ok(Response { |
|||
status, |
|||
headers, |
|||
body, |
|||
}) |
|||
} |
|||
|
|||
static RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new(); |
|||
|
|||
pub fn in_tokio<F: std::future::Future>(f: F) -> F::Output { |
|||
match tokio::runtime::Handle::try_current() { |
|||
Ok(h) => { |
|||
let _enter = h.enter(); |
|||
h.block_on(f) |
|||
} |
|||
Err(_) => { |
|||
let runtime = RUNTIME.get_or_init(|| { |
|||
tokio::runtime::Builder::new_current_thread() |
|||
.enable_all() |
|||
.build() |
|||
.unwrap() |
|||
}); |
|||
let _enter = runtime.enter(); |
|||
runtime.block_on(f) |
|||
} |
|||
} |
|||
} |
|||
|
@ -1,27 +1,132 @@ |
|||
use crate::component_impl::add_component_to_linker; |
|||
pub use crate::r#struct::WasiHttp; |
|||
pub use crate::http_impl::WasiHttpViewExt; |
|||
pub use crate::r#struct::{WasiHttpCtx, WasiHttpView}; |
|||
use core::fmt::Formatter; |
|||
use std::fmt::{self, Display}; |
|||
|
|||
wasmtime::component::bindgen!({ path: "wasi-http/wit", world: "proxy"}); |
|||
wasmtime::component::bindgen!({ |
|||
world: "wasi:http/proxy", |
|||
with: { |
|||
"wasi:io/streams": wasmtime_wasi::preview2::bindings::io::streams, |
|||
"wasi:poll/poll": wasmtime_wasi::preview2::bindings::poll::poll, |
|||
}, |
|||
async: true, |
|||
}); |
|||
|
|||
pub mod component_impl; |
|||
pub mod http_impl; |
|||
pub mod streams_impl; |
|||
pub mod r#struct; |
|||
pub mod types_impl; |
|||
|
|||
pub fn add_to_component_linker<T>( |
|||
linker: &mut wasmtime::component::Linker<T>, |
|||
get_cx: impl Fn(&mut T) -> &mut WasiHttp + Send + Sync + Copy + 'static, |
|||
) -> anyhow::Result<()> { |
|||
crate::wasi::http::outgoing_handler::add_to_linker(linker, get_cx)?; |
|||
crate::wasi::http::types::add_to_linker(linker, get_cx)?; |
|||
crate::wasi::io::streams::add_to_linker(linker, get_cx)?; |
|||
pub fn add_to_component_linker<T>(linker: &mut wasmtime::component::Linker<T>) -> anyhow::Result<()> |
|||
where |
|||
T: WasiHttpView |
|||
+ WasiHttpViewExt |
|||
+ crate::wasi::http::outgoing_handler::Host |
|||
+ crate::wasi::http::types::Host, |
|||
{ |
|||
crate::wasi::http::outgoing_handler::add_to_linker(linker, |t| t)?; |
|||
crate::wasi::http::types::add_to_linker(linker, |t| t)?; |
|||
Ok(()) |
|||
} |
|||
|
|||
pub fn add_to_linker<T>( |
|||
linker: &mut wasmtime::Linker<T>, |
|||
get_cx: impl Fn(&mut T) -> &mut WasiHttp + Send + Sync + Copy + 'static, |
|||
) -> anyhow::Result<()> { |
|||
add_component_to_linker(linker, get_cx) |
|||
pub fn add_to_linker<T>(linker: &mut wasmtime::Linker<T>) -> anyhow::Result<()> |
|||
where |
|||
T: WasiHttpView |
|||
+ WasiHttpViewExt |
|||
+ crate::wasi::http::outgoing_handler::Host |
|||
+ crate::wasi::http::types::Host |
|||
+ wasmtime_wasi::preview2::bindings::io::streams::Host |
|||
+ wasmtime_wasi::preview2::bindings::poll::poll::Host, |
|||
{ |
|||
add_component_to_linker::<T>(linker, |t| t) |
|||
} |
|||
|
|||
impl std::error::Error for crate::wasi::http::types::Error {} |
|||
|
|||
impl Display for crate::wasi::http::types::Error { |
|||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
|||
match self { |
|||
crate::wasi::http::types::Error::InvalidUrl(m) => { |
|||
write!(f, "[InvalidUrl] {}", m) |
|||
} |
|||
crate::wasi::http::types::Error::ProtocolError(m) => { |
|||
write!(f, "[ProtocolError] {}", m) |
|||
} |
|||
crate::wasi::http::types::Error::TimeoutError(m) => { |
|||
write!(f, "[TimeoutError] {}", m) |
|||
} |
|||
crate::wasi::http::types::Error::UnexpectedError(m) => { |
|||
write!(f, "[UnexpectedError] {}", m) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
impl From<wasmtime_wasi::preview2::TableError> for crate::wasi::http::types::Error { |
|||
fn from(err: wasmtime_wasi::preview2::TableError) -> Self { |
|||
Self::UnexpectedError(err.to_string()) |
|||
} |
|||
} |
|||
|
|||
impl From<anyhow::Error> for crate::wasi::http::types::Error { |
|||
fn from(err: anyhow::Error) -> Self { |
|||
Self::UnexpectedError(err.to_string()) |
|||
} |
|||
} |
|||
|
|||
impl From<std::io::Error> for crate::wasi::http::types::Error { |
|||
fn from(err: std::io::Error) -> Self { |
|||
let message = err.to_string(); |
|||
match err.kind() { |
|||
std::io::ErrorKind::InvalidInput => Self::InvalidUrl(message), |
|||
std::io::ErrorKind::AddrNotAvailable => Self::InvalidUrl(message), |
|||
_ => { |
|||
if message.starts_with("failed to lookup address information") { |
|||
Self::InvalidUrl("invalid dnsname".to_string()) |
|||
} else { |
|||
Self::ProtocolError(message) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
impl From<http::Error> for crate::wasi::http::types::Error { |
|||
fn from(err: http::Error) -> Self { |
|||
Self::InvalidUrl(err.to_string()) |
|||
} |
|||
} |
|||
|
|||
impl From<hyper::Error> for crate::wasi::http::types::Error { |
|||
fn from(err: hyper::Error) -> Self { |
|||
let message = err.message().to_string(); |
|||
if err.is_timeout() { |
|||
Self::TimeoutError(message) |
|||
} else if err.is_parse_status() || err.is_user() { |
|||
Self::InvalidUrl(message) |
|||
} else if err.is_body_write_aborted() |
|||
|| err.is_canceled() |
|||
|| err.is_closed() |
|||
|| err.is_incomplete_message() |
|||
|| err.is_parse() |
|||
{ |
|||
Self::ProtocolError(message) |
|||
} else { |
|||
Self::UnexpectedError(message) |
|||
} |
|||
} |
|||
} |
|||
|
|||
impl From<tokio::time::error::Elapsed> for crate::wasi::http::types::Error { |
|||
fn from(err: tokio::time::error::Elapsed) -> Self { |
|||
Self::TimeoutError(err.to_string()) |
|||
} |
|||
} |
|||
|
|||
#[cfg(not(any(target_arch = "riscv64", target_arch = "s390x")))] |
|||
impl From<rustls::client::InvalidDnsNameError> for crate::wasi::http::types::Error { |
|||
fn from(_err: rustls::client::InvalidDnsNameError) -> Self { |
|||
Self::InvalidUrl("invalid dnsname".to_string()) |
|||
} |
|||
} |
|||
|
@ -1,167 +0,0 @@ |
|||
use crate::wasi::io::streams::{Host, InputStream, OutputStream, Pollable, StreamError}; |
|||
use crate::WasiHttp; |
|||
use anyhow::{anyhow, bail}; |
|||
use std::vec::Vec; |
|||
|
|||
impl Host for WasiHttp { |
|||
fn read( |
|||
&mut self, |
|||
stream: InputStream, |
|||
len: u64, |
|||
) -> wasmtime::Result<Result<(Vec<u8>, bool), StreamError>> { |
|||
let st = self |
|||
.streams |
|||
.get_mut(&stream) |
|||
.ok_or_else(|| anyhow!("stream not found: {stream}"))?; |
|||
if st.closed { |
|||
bail!("stream is dropped!"); |
|||
} |
|||
let s = &mut st.data; |
|||
if len == 0 { |
|||
Ok(Ok((bytes::Bytes::new().to_vec(), s.len() > 0))) |
|||
} else if s.len() > len.try_into()? { |
|||
let result = s.split_to(len.try_into()?); |
|||
Ok(Ok((result.to_vec(), false))) |
|||
} else { |
|||
s.truncate(s.len()); |
|||
Ok(Ok((s.clone().to_vec(), true))) |
|||
} |
|||
} |
|||
|
|||
fn skip( |
|||
&mut self, |
|||
stream: InputStream, |
|||
len: u64, |
|||
) -> wasmtime::Result<Result<(u64, bool), StreamError>> { |
|||
let st = self |
|||
.streams |
|||
.get_mut(&stream) |
|||
.ok_or_else(|| anyhow!("stream not found: {stream}"))?; |
|||
if st.closed { |
|||
bail!("stream is dropped!"); |
|||
} |
|||
let s = &mut st.data; |
|||
if len == 0 { |
|||
Ok(Ok((0, s.len() > 0))) |
|||
} else if s.len() > len.try_into()? { |
|||
s.truncate(len.try_into()?); |
|||
Ok(Ok((len, false))) |
|||
} else { |
|||
let bytes = s.len(); |
|||
s.truncate(s.len()); |
|||
Ok(Ok((bytes.try_into()?, true))) |
|||
} |
|||
} |
|||
|
|||
fn subscribe_to_input_stream(&mut self, _this: InputStream) -> wasmtime::Result<Pollable> { |
|||
bail!("unimplemented: subscribe_to_input_stream"); |
|||
} |
|||
|
|||
fn drop_input_stream(&mut self, stream: InputStream) -> wasmtime::Result<()> { |
|||
let st = self |
|||
.streams |
|||
.get_mut(&stream) |
|||
.ok_or_else(|| anyhow!("stream not found: {stream}"))?; |
|||
st.closed = true; |
|||
Ok(()) |
|||
} |
|||
|
|||
fn write( |
|||
&mut self, |
|||
this: OutputStream, |
|||
buf: Vec<u8>, |
|||
) -> wasmtime::Result<Result<u64, StreamError>> { |
|||
let len = buf.len(); |
|||
let st = self.streams.entry(this).or_default(); |
|||
if st.closed { |
|||
bail!("cannot write to closed stream"); |
|||
} |
|||
st.data.extend_from_slice(buf.as_slice()); |
|||
Ok(Ok(len.try_into()?)) |
|||
} |
|||
|
|||
fn write_zeroes( |
|||
&mut self, |
|||
this: OutputStream, |
|||
len: u64, |
|||
) -> wasmtime::Result<Result<u64, StreamError>> { |
|||
let mut data = Vec::with_capacity(len.try_into()?); |
|||
let mut i = 0; |
|||
while i < len { |
|||
data.push(0); |
|||
i = i + 1; |
|||
} |
|||
self.write(this, data) |
|||
} |
|||
|
|||
fn splice( |
|||
&mut self, |
|||
_this: OutputStream, |
|||
_src: InputStream, |
|||
_len: u64, |
|||
) -> wasmtime::Result<Result<(u64, bool), StreamError>> { |
|||
bail!("unimplemented: splice"); |
|||
} |
|||
|
|||
fn forward( |
|||
&mut self, |
|||
_this: OutputStream, |
|||
_src: InputStream, |
|||
) -> wasmtime::Result<Result<u64, StreamError>> { |
|||
bail!("unimplemented: forward"); |
|||
} |
|||
|
|||
fn subscribe_to_output_stream(&mut self, _this: OutputStream) -> wasmtime::Result<Pollable> { |
|||
bail!("unimplemented: subscribe_to_output_stream"); |
|||
} |
|||
|
|||
fn drop_output_stream(&mut self, stream: OutputStream) -> wasmtime::Result<()> { |
|||
let st = self |
|||
.streams |
|||
.get_mut(&stream) |
|||
.ok_or_else(|| anyhow!("stream not found: {stream}"))?; |
|||
st.closed = true; |
|||
Ok(()) |
|||
} |
|||
|
|||
fn blocking_read( |
|||
&mut self, |
|||
_: InputStream, |
|||
_: u64, |
|||
) -> wasmtime::Result<Result<(Vec<u8>, bool), StreamError>> { |
|||
bail!("unimplemented") |
|||
} |
|||
|
|||
fn blocking_skip( |
|||
&mut self, |
|||
_: InputStream, |
|||
_: u64, |
|||
) -> wasmtime::Result<Result<(u64, bool), StreamError>> { |
|||
bail!("unimplemented") |
|||
} |
|||
|
|||
fn blocking_write( |
|||
&mut self, |
|||
_: OutputStream, |
|||
_: Vec<u8>, |
|||
) -> wasmtime::Result<Result<u64, StreamError>> { |
|||
bail!("unimplemented") |
|||
} |
|||
|
|||
fn blocking_write_zeroes( |
|||
&mut self, |
|||
_: OutputStream, |
|||
_: u64, |
|||
) -> wasmtime::Result<Result<u64, StreamError>> { |
|||
bail!("unimplemented") |
|||
} |
|||
|
|||
fn blocking_splice( |
|||
&mut self, |
|||
_: OutputStream, |
|||
_: InputStream, |
|||
_: u64, |
|||
) -> wasmtime::Result<Result<(u64, bool), StreamError>> { |
|||
bail!("unimplemented") |
|||
} |
|||
} |
@ -1,124 +1,419 @@ |
|||
use crate::wasi::http::types::{Method, RequestOptions, Scheme}; |
|||
use bytes::{BufMut, Bytes, BytesMut}; |
|||
//! Implements the base structure (i.e. [WasiHttpCtx]) that will provide the
|
|||
//! implementation of the wasi-http API.
|
|||
|
|||
use crate::wasi::http::types::{ |
|||
IncomingStream, Method, OutgoingRequest, OutgoingStream, RequestOptions, Scheme, |
|||
}; |
|||
use bytes::Bytes; |
|||
use std::any::Any; |
|||
use std::collections::HashMap; |
|||
use std::ops::{Deref, DerefMut}; |
|||
use wasmtime_wasi::preview2::{ |
|||
pipe::{AsyncReadStream, AsyncWriteStream}, |
|||
HostInputStream, HostOutputStream, Table, TableError, TableStreamExt, WasiView, |
|||
}; |
|||
|
|||
#[derive(Clone, Default)] |
|||
pub struct Stream { |
|||
pub closed: bool, |
|||
pub data: BytesMut, |
|||
} |
|||
const MAX_BUF_SIZE: usize = 65_536; |
|||
|
|||
#[derive(Clone)] |
|||
pub struct WasiHttp { |
|||
pub request_id_base: u32, |
|||
pub response_id_base: u32, |
|||
pub fields_id_base: u32, |
|||
pub streams_id_base: u32, |
|||
pub future_id_base: u32, |
|||
pub requests: HashMap<u32, ActiveRequest>, |
|||
pub responses: HashMap<u32, ActiveResponse>, |
|||
pub fields: HashMap<u32, HashMap<String, Vec<Vec<u8>>>>, |
|||
/// Capture the state necessary for use in the wasi-http API implementation.
|
|||
pub struct WasiHttpCtx { |
|||
pub streams: HashMap<u32, Stream>, |
|||
pub futures: HashMap<u32, ActiveFuture>, |
|||
} |
|||
|
|||
#[derive(Clone)] |
|||
impl WasiHttpCtx { |
|||
/// Make a new context from the default state.
|
|||
pub fn new() -> Self { |
|||
Self { |
|||
streams: HashMap::new(), |
|||
} |
|||
} |
|||
} |
|||
|
|||
pub trait WasiHttpView: WasiView { |
|||
fn http_ctx(&self) -> &WasiHttpCtx; |
|||
fn http_ctx_mut(&mut self) -> &mut WasiHttpCtx; |
|||
} |
|||
|
|||
pub type FieldsMap = HashMap<String, Vec<Vec<u8>>>; |
|||
|
|||
#[derive(Clone, Debug)] |
|||
pub struct ActiveRequest { |
|||
pub id: u32, |
|||
pub active_request: bool, |
|||
pub active: bool, |
|||
pub method: Method, |
|||
pub scheme: Option<Scheme>, |
|||
pub path_with_query: String, |
|||
pub authority: String, |
|||
pub headers: HashMap<String, Vec<Vec<u8>>>, |
|||
pub body: u32, |
|||
pub headers: Option<u32>, |
|||
pub body: Option<u32>, |
|||
} |
|||
|
|||
#[derive(Clone)] |
|||
pub struct ActiveResponse { |
|||
pub id: u32, |
|||
pub active_response: bool, |
|||
pub status: u16, |
|||
pub body: u32, |
|||
pub response_headers: HashMap<String, Vec<Vec<u8>>>, |
|||
pub trailers: u32, |
|||
} |
|||
pub trait HttpRequest: Send + Sync { |
|||
fn new() -> Self |
|||
where |
|||
Self: Sized; |
|||
|
|||
#[derive(Clone)] |
|||
pub struct ActiveFuture { |
|||
pub id: u32, |
|||
pub request_id: u32, |
|||
pub options: Option<RequestOptions>, |
|||
fn as_any(&self) -> &dyn Any; |
|||
|
|||
fn method(&self) -> &Method; |
|||
fn scheme(&self) -> &Option<Scheme>; |
|||
fn path_with_query(&self) -> &str; |
|||
fn authority(&self) -> &str; |
|||
fn headers(&self) -> Option<u32>; |
|||
fn set_headers(&mut self, headers: u32); |
|||
fn body(&self) -> Option<u32>; |
|||
fn set_body(&mut self, body: u32); |
|||
} |
|||
|
|||
impl ActiveRequest { |
|||
pub fn new(id: u32) -> Self { |
|||
impl HttpRequest for ActiveRequest { |
|||
fn new() -> Self { |
|||
Self { |
|||
id, |
|||
active_request: false, |
|||
active: false, |
|||
method: Method::Get, |
|||
scheme: Some(Scheme::Http), |
|||
path_with_query: "".to_string(), |
|||
authority: "".to_string(), |
|||
headers: HashMap::new(), |
|||
body: 0, |
|||
headers: None, |
|||
body: None, |
|||
} |
|||
} |
|||
|
|||
fn as_any(&self) -> &dyn Any { |
|||
self |
|||
} |
|||
|
|||
fn method(&self) -> &Method { |
|||
&self.method |
|||
} |
|||
|
|||
fn scheme(&self) -> &Option<Scheme> { |
|||
&self.scheme |
|||
} |
|||
|
|||
fn path_with_query(&self) -> &str { |
|||
&self.path_with_query |
|||
} |
|||
|
|||
fn authority(&self) -> &str { |
|||
&self.authority |
|||
} |
|||
|
|||
fn headers(&self) -> Option<u32> { |
|||
self.headers |
|||
} |
|||
|
|||
fn set_headers(&mut self, headers: u32) { |
|||
self.headers = Some(headers); |
|||
} |
|||
|
|||
fn body(&self) -> Option<u32> { |
|||
self.body |
|||
} |
|||
|
|||
fn set_body(&mut self, body: u32) { |
|||
self.body = Some(body); |
|||
} |
|||
} |
|||
|
|||
#[derive(Clone, Debug)] |
|||
pub struct ActiveResponse { |
|||
pub active: bool, |
|||
pub status: u16, |
|||
pub headers: Option<u32>, |
|||
pub body: Option<u32>, |
|||
pub trailers: Option<u32>, |
|||
} |
|||
|
|||
pub trait HttpResponse: Send + Sync { |
|||
fn new() -> Self |
|||
where |
|||
Self: Sized; |
|||
|
|||
fn as_any(&self) -> &dyn Any; |
|||
|
|||
fn status(&self) -> u16; |
|||
fn headers(&self) -> Option<u32>; |
|||
fn set_headers(&mut self, headers: u32); |
|||
fn body(&self) -> Option<u32>; |
|||
fn set_body(&mut self, body: u32); |
|||
fn trailers(&self) -> Option<u32>; |
|||
fn set_trailers(&mut self, trailers: u32); |
|||
} |
|||
|
|||
impl ActiveResponse { |
|||
pub fn new(id: u32) -> Self { |
|||
impl HttpResponse for ActiveResponse { |
|||
fn new() -> Self { |
|||
Self { |
|||
id, |
|||
active_response: false, |
|||
active: false, |
|||
status: 0, |
|||
body: 0, |
|||
response_headers: HashMap::new(), |
|||
trailers: 0, |
|||
headers: None, |
|||
body: None, |
|||
trailers: None, |
|||
} |
|||
} |
|||
|
|||
fn as_any(&self) -> &dyn Any { |
|||
self |
|||
} |
|||
|
|||
fn status(&self) -> u16 { |
|||
self.status |
|||
} |
|||
|
|||
fn headers(&self) -> Option<u32> { |
|||
self.headers |
|||
} |
|||
|
|||
fn set_headers(&mut self, headers: u32) { |
|||
self.headers = Some(headers); |
|||
} |
|||
|
|||
fn body(&self) -> Option<u32> { |
|||
self.body |
|||
} |
|||
|
|||
fn set_body(&mut self, body: u32) { |
|||
self.body = Some(body); |
|||
} |
|||
|
|||
fn trailers(&self) -> Option<u32> { |
|||
self.trailers |
|||
} |
|||
|
|||
fn set_trailers(&mut self, trailers: u32) { |
|||
self.trailers = Some(trailers); |
|||
} |
|||
} |
|||
|
|||
#[derive(Clone)] |
|||
pub struct ActiveFuture { |
|||
request_id: OutgoingRequest, |
|||
options: Option<RequestOptions>, |
|||
response_id: Option<u32>, |
|||
pollable_id: Option<u32>, |
|||
} |
|||
|
|||
impl ActiveFuture { |
|||
pub fn new(id: u32, request_id: u32, options: Option<RequestOptions>) -> Self { |
|||
pub fn new(request_id: OutgoingRequest, options: Option<RequestOptions>) -> Self { |
|||
Self { |
|||
id, |
|||
request_id, |
|||
options, |
|||
response_id: None, |
|||
pollable_id: None, |
|||
} |
|||
} |
|||
|
|||
pub fn request_id(&self) -> u32 { |
|||
self.request_id |
|||
} |
|||
|
|||
pub fn options(&self) -> Option<RequestOptions> { |
|||
self.options |
|||
} |
|||
|
|||
pub fn response_id(&self) -> Option<u32> { |
|||
self.response_id |
|||
} |
|||
|
|||
pub fn set_response_id(&mut self, response_id: u32) { |
|||
self.response_id = Some(response_id); |
|||
} |
|||
|
|||
pub fn pollable_id(&self) -> Option<u32> { |
|||
self.pollable_id |
|||
} |
|||
|
|||
pub fn set_pollable_id(&mut self, pollable_id: u32) { |
|||
self.pollable_id = Some(pollable_id); |
|||
} |
|||
} |
|||
|
|||
impl Stream { |
|||
#[derive(Clone, Debug)] |
|||
pub struct ActiveFields(HashMap<String, Vec<Vec<u8>>>); |
|||
|
|||
impl ActiveFields { |
|||
pub fn new() -> Self { |
|||
Self::default() |
|||
Self(FieldsMap::new()) |
|||
} |
|||
} |
|||
|
|||
impl From<Bytes> for Stream { |
|||
fn from(bytes: Bytes) -> Self { |
|||
let mut buf = BytesMut::with_capacity(bytes.len()); |
|||
buf.put(bytes); |
|||
pub trait HttpFields: Send + Sync { |
|||
fn as_any(&self) -> &dyn Any; |
|||
} |
|||
|
|||
impl HttpFields for ActiveFields { |
|||
fn as_any(&self) -> &dyn Any { |
|||
self |
|||
} |
|||
} |
|||
|
|||
impl Deref for ActiveFields { |
|||
type Target = FieldsMap; |
|||
fn deref(&self) -> &FieldsMap { |
|||
&self.0 |
|||
} |
|||
} |
|||
|
|||
impl DerefMut for ActiveFields { |
|||
fn deref_mut(&mut self) -> &mut FieldsMap { |
|||
&mut self.0 |
|||
} |
|||
} |
|||
|
|||
#[derive(Clone, Debug)] |
|||
pub struct Stream { |
|||
input_id: u32, |
|||
output_id: u32, |
|||
parent_id: u32, |
|||
} |
|||
|
|||
impl Stream { |
|||
pub fn new(input_id: u32, output_id: u32, parent_id: u32) -> Self { |
|||
Self { |
|||
closed: false, |
|||
data: buf, |
|||
input_id, |
|||
output_id, |
|||
parent_id, |
|||
} |
|||
} |
|||
|
|||
pub fn incoming(&self) -> IncomingStream { |
|||
self.input_id |
|||
} |
|||
|
|||
pub fn outgoing(&self) -> OutgoingStream { |
|||
self.output_id |
|||
} |
|||
|
|||
pub fn parent_id(&self) -> u32 { |
|||
self.parent_id |
|||
} |
|||
} |
|||
|
|||
impl WasiHttp { |
|||
pub fn new() -> Self { |
|||
Self { |
|||
request_id_base: 1, |
|||
response_id_base: 1, |
|||
fields_id_base: 1, |
|||
streams_id_base: 1, |
|||
future_id_base: 1, |
|||
requests: HashMap::new(), |
|||
responses: HashMap::new(), |
|||
fields: HashMap::new(), |
|||
streams: HashMap::new(), |
|||
futures: HashMap::new(), |
|||
pub trait TableHttpExt { |
|||
fn push_request(&mut self, request: Box<dyn HttpRequest>) -> Result<u32, TableError>; |
|||
fn get_request(&self, id: u32) -> Result<&(dyn HttpRequest), TableError>; |
|||
fn get_request_mut(&mut self, id: u32) -> Result<&mut Box<dyn HttpRequest>, TableError>; |
|||
fn delete_request(&mut self, id: u32) -> Result<(), TableError>; |
|||
|
|||
fn push_response(&mut self, response: Box<dyn HttpResponse>) -> Result<u32, TableError>; |
|||
fn get_response(&self, id: u32) -> Result<&dyn HttpResponse, TableError>; |
|||
fn get_response_mut(&mut self, id: u32) -> Result<&mut Box<dyn HttpResponse>, TableError>; |
|||
fn delete_response(&mut self, id: u32) -> Result<(), TableError>; |
|||
|
|||
fn push_future(&mut self, future: Box<ActiveFuture>) -> Result<u32, TableError>; |
|||
fn get_future(&self, id: u32) -> Result<&ActiveFuture, TableError>; |
|||
fn get_future_mut(&mut self, id: u32) -> Result<&mut Box<ActiveFuture>, TableError>; |
|||
fn delete_future(&mut self, id: u32) -> Result<(), TableError>; |
|||
|
|||
fn push_fields(&mut self, fields: Box<ActiveFields>) -> Result<u32, TableError>; |
|||
fn get_fields(&self, id: u32) -> Result<&ActiveFields, TableError>; |
|||
fn get_fields_mut(&mut self, id: u32) -> Result<&mut Box<ActiveFields>, TableError>; |
|||
fn delete_fields(&mut self, id: u32) -> Result<(), TableError>; |
|||
|
|||
fn push_stream(&mut self, content: Bytes, parent: u32) -> Result<(u32, Stream), TableError>; |
|||
fn get_stream(&self, id: u32) -> Result<&Stream, TableError>; |
|||
fn get_stream_mut(&mut self, id: u32) -> Result<&mut Box<Stream>, TableError>; |
|||
fn delete_stream(&mut self, id: u32) -> Result<(), TableError>; |
|||
} |
|||
|
|||
impl TableHttpExt for Table { |
|||
fn push_request(&mut self, request: Box<dyn HttpRequest>) -> Result<u32, TableError> { |
|||
self.push(Box::new(request)) |
|||
} |
|||
fn get_request(&self, id: u32) -> Result<&dyn HttpRequest, TableError> { |
|||
self.get::<Box<dyn HttpRequest>>(id).map(|f| f.as_ref()) |
|||
} |
|||
fn get_request_mut(&mut self, id: u32) -> Result<&mut Box<dyn HttpRequest>, TableError> { |
|||
self.get_mut::<Box<dyn HttpRequest>>(id) |
|||
} |
|||
fn delete_request(&mut self, id: u32) -> Result<(), TableError> { |
|||
self.delete::<Box<dyn HttpRequest>>(id).map(|_old| ()) |
|||
} |
|||
|
|||
fn push_response(&mut self, response: Box<dyn HttpResponse>) -> Result<u32, TableError> { |
|||
self.push(Box::new(response)) |
|||
} |
|||
fn get_response(&self, id: u32) -> Result<&dyn HttpResponse, TableError> { |
|||
self.get::<Box<dyn HttpResponse>>(id).map(|f| f.as_ref()) |
|||
} |
|||
fn get_response_mut(&mut self, id: u32) -> Result<&mut Box<dyn HttpResponse>, TableError> { |
|||
self.get_mut::<Box<dyn HttpResponse>>(id) |
|||
} |
|||
fn delete_response(&mut self, id: u32) -> Result<(), TableError> { |
|||
self.delete::<Box<dyn HttpResponse>>(id).map(|_old| ()) |
|||
} |
|||
|
|||
fn push_future(&mut self, future: Box<ActiveFuture>) -> Result<u32, TableError> { |
|||
self.push(Box::new(future)) |
|||
} |
|||
fn get_future(&self, id: u32) -> Result<&ActiveFuture, TableError> { |
|||
self.get::<Box<ActiveFuture>>(id).map(|f| f.as_ref()) |
|||
} |
|||
fn get_future_mut(&mut self, id: u32) -> Result<&mut Box<ActiveFuture>, TableError> { |
|||
self.get_mut::<Box<ActiveFuture>>(id) |
|||
} |
|||
fn delete_future(&mut self, id: u32) -> Result<(), TableError> { |
|||
self.delete::<Box<ActiveFuture>>(id).map(|_old| ()) |
|||
} |
|||
|
|||
fn push_fields(&mut self, fields: Box<ActiveFields>) -> Result<u32, TableError> { |
|||
self.push(Box::new(fields)) |
|||
} |
|||
fn get_fields(&self, id: u32) -> Result<&ActiveFields, TableError> { |
|||
self.get::<Box<ActiveFields>>(id).map(|f| f.as_ref()) |
|||
} |
|||
fn get_fields_mut(&mut self, id: u32) -> Result<&mut Box<ActiveFields>, TableError> { |
|||
self.get_mut::<Box<ActiveFields>>(id) |
|||
} |
|||
fn delete_fields(&mut self, id: u32) -> Result<(), TableError> { |
|||
self.delete::<Box<ActiveFields>>(id).map(|_old| ()) |
|||
} |
|||
|
|||
fn push_stream(&mut self, content: Bytes, parent: u32) -> Result<(u32, Stream), TableError> { |
|||
let (a, b) = tokio::io::duplex(MAX_BUF_SIZE); |
|||
let (_, write_stream) = tokio::io::split(a); |
|||
let (read_stream, _) = tokio::io::split(b); |
|||
let input_stream = AsyncReadStream::new(read_stream); |
|||
let mut output_stream = AsyncWriteStream::new(write_stream); |
|||
|
|||
let mut cursor = 0; |
|||
while cursor < content.len() { |
|||
let (written, _) = output_stream |
|||
.write(content.slice(cursor..content.len())) |
|||
.map_err(|_| TableError::NotPresent)?; |
|||
cursor += written; |
|||
} |
|||
|
|||
let input_stream = Box::new(input_stream); |
|||
let output_id = self.push_output_stream(Box::new(output_stream))?; |
|||
let input_id = self.push_input_stream(input_stream)?; |
|||
let stream = Stream::new(input_id, output_id, parent); |
|||
let cloned_stream = stream.clone(); |
|||
let stream_id = self.push(Box::new(Box::new(stream)))?; |
|||
Ok((stream_id, cloned_stream)) |
|||
} |
|||
fn get_stream(&self, id: u32) -> Result<&Stream, TableError> { |
|||
self.get::<Box<Stream>>(id).map(|f| f.as_ref()) |
|||
} |
|||
fn get_stream_mut(&mut self, id: u32) -> Result<&mut Box<Stream>, TableError> { |
|||
self.get_mut::<Box<Stream>>(id) |
|||
} |
|||
fn delete_stream(&mut self, id: u32) -> Result<(), TableError> { |
|||
let stream = self.get_stream_mut(id)?; |
|||
let input_stream = stream.incoming(); |
|||
let output_stream = stream.outgoing(); |
|||
self.delete::<Box<Stream>>(id).map(|_old| ())?; |
|||
self.delete::<Box<dyn HostInputStream>>(input_stream) |
|||
.map(|_old| ())?; |
|||
self.delete::<Box<dyn HostOutputStream>>(output_stream) |
|||
.map(|_old| ()) |
|||
} |
|||
} |
|||
|
|||
#[cfg(test)] |
|||
mod test { |
|||
use super::*; |
|||
|
|||
#[test] |
|||
fn instantiate() { |
|||
WasiHttpCtx::new().unwrap(); |
|||
} |
|||
} |
|||
|
@ -1 +0,0 @@ |
|||
Subproject commit 1c95bc21dbd193b046e4232d063a82c8b5ba7994 |
@ -0,0 +1,39 @@ |
|||
// All of the same imports and exports available in the wasi:cli/command world |
|||
// with addition of HTTP proxy related imports: |
|||
world command-extended { |
|||
import wasi:clocks/wall-clock |
|||
import wasi:clocks/monotonic-clock |
|||
import wasi:clocks/timezone |
|||
import wasi:filesystem/types |
|||
import wasi:filesystem/preopens |
|||
import wasi:sockets/instance-network |
|||
import wasi:sockets/ip-name-lookup |
|||
import wasi:sockets/network |
|||
import wasi:sockets/tcp-create-socket |
|||
import wasi:sockets/tcp |
|||
import wasi:sockets/udp-create-socket |
|||
import wasi:sockets/udp |
|||
import wasi:random/random |
|||
import wasi:random/insecure |
|||
import wasi:random/insecure-seed |
|||
import wasi:poll/poll |
|||
import wasi:io/streams |
|||
import wasi:cli/environment |
|||
import wasi:cli/exit |
|||
import wasi:cli/stdin |
|||
import wasi:cli/stdout |
|||
import wasi:cli/stderr |
|||
import wasi:cli/terminal-input |
|||
import wasi:cli/terminal-output |
|||
import wasi:cli/terminal-stdin |
|||
import wasi:cli/terminal-stdout |
|||
import wasi:cli/terminal-stderr |
|||
|
|||
export wasi:cli/run |
|||
|
|||
// We should replace all others with `include self.command` |
|||
// as soon as the unioning of worlds is available: |
|||
// https://github.com/WebAssembly/component-model/issues/169 |
|||
import wasi:logging/logging |
|||
import wasi:http/outgoing-handler |
|||
} |
@ -0,0 +1,33 @@ |
|||
package wasi:cli |
|||
|
|||
world command { |
|||
import wasi:clocks/wall-clock |
|||
import wasi:clocks/monotonic-clock |
|||
import wasi:clocks/timezone |
|||
import wasi:filesystem/types |
|||
import wasi:filesystem/preopens |
|||
import wasi:sockets/instance-network |
|||
import wasi:sockets/ip-name-lookup |
|||
import wasi:sockets/network |
|||
import wasi:sockets/tcp-create-socket |
|||
import wasi:sockets/tcp |
|||
import wasi:sockets/udp-create-socket |
|||
import wasi:sockets/udp |
|||
import wasi:random/random |
|||
import wasi:random/insecure |
|||
import wasi:random/insecure-seed |
|||
import wasi:poll/poll |
|||
import wasi:io/streams |
|||
|
|||
import environment |
|||
import exit |
|||
import stdin |
|||
import stdout |
|||
import stderr |
|||
import terminal-input |
|||
import terminal-output |
|||
import terminal-stdin |
|||
import terminal-stdout |
|||
import terminal-stderr |
|||
export run |
|||
} |
@ -0,0 +1,18 @@ |
|||
interface environment { |
|||
/// Get the POSIX-style environment variables. |
|||
/// |
|||
/// Each environment variable is provided as a pair of string variable names |
|||
/// and string value. |
|||
/// |
|||
/// Morally, these are a value import, but until value imports are available |
|||
/// in the component model, this import function should return the same |
|||
/// values each time it is called. |
|||
get-environment: func() -> list<tuple<string, string>> |
|||
|
|||
/// Get the POSIX-style arguments to the program. |
|||
get-arguments: func() -> list<string> |
|||
|
|||
/// Return a path that programs should use as their initial current working |
|||
/// directory, interpreting `.` as shorthand for this. |
|||
initial-cwd: func() -> option<string> |
|||
} |
@ -0,0 +1,4 @@ |
|||
interface exit { |
|||
/// Exit the current instance and any linked instances. |
|||
exit: func(status: result) |
|||
} |
@ -0,0 +1,4 @@ |
|||
interface run { |
|||
/// Run the program. |
|||
run: func() -> result |
|||
} |
@ -0,0 +1,17 @@ |
|||
interface stdin { |
|||
use wasi:io/streams.{input-stream} |
|||
|
|||
get-stdin: func() -> input-stream |
|||
} |
|||
|
|||
interface stdout { |
|||
use wasi:io/streams.{output-stream} |
|||
|
|||
get-stdout: func() -> output-stream |
|||
} |
|||
|
|||
interface stderr { |
|||
use wasi:io/streams.{output-stream} |
|||
|
|||
get-stderr: func() -> output-stream |
|||
} |
@ -0,0 +1,59 @@ |
|||
interface terminal-input { |
|||
/// The input side of a terminal. |
|||
/// |
|||
/// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). |
|||
type terminal-input = u32 |
|||
|
|||
// In the future, this may include functions for disabling echoing, |
|||
// disabling input buffering so that keyboard events are sent through |
|||
// immediately, querying supported features, and so on. |
|||
|
|||
/// Dispose of the specified terminal-input after which it may no longer |
|||
/// be used. |
|||
drop-terminal-input: func(this: terminal-input) |
|||
} |
|||
|
|||
interface terminal-output { |
|||
/// The output side of a terminal. |
|||
/// |
|||
/// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). |
|||
type terminal-output = u32 |
|||
|
|||
// In the future, this may include functions for querying the terminal |
|||
// size, being notified of terminal size changes, querying supported |
|||
// features, and so on. |
|||
|
|||
/// Dispose of the specified terminal-output, after which it may no longer |
|||
/// be used. |
|||
drop-terminal-output: func(this: terminal-output) |
|||
} |
|||
|
|||
/// An interface providing an optional `terminal-input` for stdin as a |
|||
/// link-time authority. |
|||
interface terminal-stdin { |
|||
use terminal-input.{terminal-input} |
|||
|
|||
/// If stdin is connected to a terminal, return a `terminal-input` handle |
|||
/// allowing further interaction with it. |
|||
get-terminal-stdin: func() -> option<terminal-input> |
|||
} |
|||
|
|||
/// An interface providing an optional `terminal-output` for stdout as a |
|||
/// link-time authority. |
|||
interface terminal-stdout { |
|||
use terminal-output.{terminal-output} |
|||
|
|||
/// If stdout is connected to a terminal, return a `terminal-output` handle |
|||
/// allowing further interaction with it. |
|||
get-terminal-stdout: func() -> option<terminal-output> |
|||
} |
|||
|
|||
/// An interface providing an optional `terminal-output` for stderr as a |
|||
/// link-time authority. |
|||
interface terminal-stderr { |
|||
use terminal-output.{terminal-output} |
|||
|
|||
/// If stderr is connected to a terminal, return a `terminal-output` handle |
|||
/// allowing further interaction with it. |
|||
get-terminal-stderr: func() -> option<terminal-output> |
|||
} |
@ -0,0 +1,34 @@ |
|||
package wasi:clocks |
|||
|
|||
/// WASI Monotonic Clock is a clock API intended to let users measure elapsed |
|||
/// time. |
|||
/// |
|||
/// It is intended to be portable at least between Unix-family platforms and |
|||
/// Windows. |
|||
/// |
|||
/// A monotonic clock is a clock which has an unspecified initial value, and |
|||
/// successive reads of the clock will produce non-decreasing values. |
|||
/// |
|||
/// It is intended for measuring elapsed time. |
|||
interface monotonic-clock { |
|||
use wasi:poll/poll.{pollable} |
|||
|
|||
/// A timestamp in nanoseconds. |
|||
type instant = u64 |
|||
|
|||
/// Read the current value of the clock. |
|||
/// |
|||
/// The clock is monotonic, therefore calling this function repeatedly will |
|||
/// produce a sequence of non-decreasing values. |
|||
now: func() -> instant |
|||
|
|||
/// Query the resolution of the clock. |
|||
resolution: func() -> instant |
|||
|
|||
/// Create a `pollable` which will resolve once the specified time has been |
|||
/// reached. |
|||
subscribe: func( |
|||
when: instant, |
|||
absolute: bool |
|||
) -> pollable |
|||
} |
@ -0,0 +1,63 @@ |
|||
package wasi:clocks |
|||
|
|||
interface timezone { |
|||
use wall-clock.{datetime} |
|||
|
|||
/// A timezone. |
|||
/// |
|||
/// In timezones that recognize daylight saving time, also known as daylight |
|||
/// time and summer time, the information returned from the functions varies |
|||
/// over time to reflect these adjustments. |
|||
/// |
|||
/// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). |
|||
type timezone = u32 |
|||
|
|||
/// Return information needed to display the given `datetime`. This includes |
|||
/// the UTC offset, the time zone name, and a flag indicating whether |
|||
/// daylight saving time is active. |
|||
/// |
|||
/// If the timezone cannot be determined for the given `datetime`, return a |
|||
/// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight |
|||
/// saving time. |
|||
display: func(this: timezone, when: datetime) -> timezone-display |
|||
|
|||
/// The same as `display`, but only return the UTC offset. |
|||
utc-offset: func(this: timezone, when: datetime) -> s32 |
|||
|
|||
/// Dispose of the specified input-stream, after which it may no longer |
|||
/// be used. |
|||
drop-timezone: func(this: timezone) |
|||
|
|||
/// Information useful for displaying the timezone of a specific `datetime`. |
|||
/// |
|||
/// This information may vary within a single `timezone` to reflect daylight |
|||
/// saving time adjustments. |
|||
record timezone-display { |
|||
/// The number of seconds difference between UTC time and the local |
|||
/// time of the timezone. |
|||
/// |
|||
/// The returned value will always be less than 86400 which is the |
|||
/// number of seconds in a day (24*60*60). |
|||
/// |
|||
/// In implementations that do not expose an actual time zone, this |
|||
/// should return 0. |
|||
utc-offset: s32, |
|||
|
|||
/// The abbreviated name of the timezone to display to a user. The name |
|||
/// `UTC` indicates Coordinated Universal Time. Otherwise, this should |
|||
/// reference local standards for the name of the time zone. |
|||
/// |
|||
/// In implementations that do not expose an actual time zone, this |
|||
/// should be the string `UTC`. |
|||
/// |
|||
/// In time zones that do not have an applicable name, a formatted |
|||
/// representation of the UTC offset may be returned, such as `-04:00`. |
|||
name: string, |
|||
|
|||
/// Whether daylight saving time is active. |
|||
/// |
|||
/// In implementations that do not expose an actual time zone, this |
|||
/// should return false. |
|||
in-daylight-saving-time: bool, |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
package wasi:clocks |
|||
|
|||
/// WASI Wall Clock is a clock API intended to let users query the current |
|||
/// time. The name "wall" makes an analogy to a "clock on the wall", which |
|||
/// is not necessarily monotonic as it may be reset. |
|||
/// |
|||
/// It is intended to be portable at least between Unix-family platforms and |
|||
/// Windows. |
|||
/// |
|||
/// A wall clock is a clock which measures the date and time according to |
|||
/// some external reference. |
|||
/// |
|||
/// External references may be reset, so this clock is not necessarily |
|||
/// monotonic, making it unsuitable for measuring elapsed time. |
|||
/// |
|||
/// It is intended for reporting the current date and time for humans. |
|||
interface wall-clock { |
|||
/// A time and date in seconds plus nanoseconds. |
|||
record datetime { |
|||
seconds: u64, |
|||
nanoseconds: u32, |
|||
} |
|||
|
|||
/// Read the current value of the clock. |
|||
/// |
|||
/// This clock is not monotonic, therefore calling this function repeatedly |
|||
/// will not necessarily produce a sequence of non-decreasing values. |
|||
/// |
|||
/// The returned timestamps represent the number of seconds since |
|||
/// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], |
|||
/// also known as [Unix Time]. |
|||
/// |
|||
/// The nanoseconds field of the output is always less than 1000000000. |
|||
/// |
|||
/// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 |
|||
/// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time |
|||
now: func() -> datetime |
|||
|
|||
/// Query the resolution of the clock. |
|||
/// |
|||
/// The nanoseconds field of the output is always less than 1000000000. |
|||
resolution: func() -> datetime |
|||
} |
@ -0,0 +1,6 @@ |
|||
interface preopens { |
|||
use types.{descriptor} |
|||
|
|||
/// Return the set of preopened directories, and their path. |
|||
get-directories: func() -> list<tuple<descriptor, string>> |
|||
} |
@ -0,0 +1,824 @@ |
|||
/// WASI filesystem is a filesystem API primarily intended to let users run WASI |
|||
/// programs that access their files on their existing filesystems, without |
|||
/// significant overhead. |
|||
/// |
|||
/// It is intended to be roughly portable between Unix-family platforms and |
|||
/// Windows, though it does not hide many of the major differences. |
|||
/// |
|||
/// Paths are passed as interface-type `string`s, meaning they must consist of |
|||
/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain |
|||
/// paths which are not accessible by this API. |
|||
/// |
|||
/// The directory separator in WASI is always the forward-slash (`/`). |
|||
/// |
|||
/// All paths in WASI are relative paths, and are interpreted relative to a |
|||
/// `descriptor` referring to a base directory. If a `path` argument to any WASI |
|||
/// function starts with `/`, or if any step of resolving a `path`, including |
|||
/// `..` and symbolic link steps, reaches a directory outside of the base |
|||
/// directory, or reaches a symlink to an absolute or rooted path in the |
|||
/// underlying filesystem, the function fails with `error-code::not-permitted`. |
|||
interface types { |
|||
use wasi:io/streams.{input-stream, output-stream} |
|||
use wasi:clocks/wall-clock.{datetime} |
|||
|
|||
/// File size or length of a region within a file. |
|||
type filesize = u64 |
|||
|
|||
/// The type of a filesystem object referenced by a descriptor. |
|||
/// |
|||
/// Note: This was called `filetype` in earlier versions of WASI. |
|||
enum descriptor-type { |
|||
/// The type of the descriptor or file is unknown or is different from |
|||
/// any of the other types specified. |
|||
unknown, |
|||
/// The descriptor refers to a block device inode. |
|||
block-device, |
|||
/// The descriptor refers to a character device inode. |
|||
character-device, |
|||
/// The descriptor refers to a directory inode. |
|||
directory, |
|||
/// The descriptor refers to a named pipe. |
|||
fifo, |
|||
/// The file refers to a symbolic link inode. |
|||
symbolic-link, |
|||
/// The descriptor refers to a regular file inode. |
|||
regular-file, |
|||
/// The descriptor refers to a socket. |
|||
socket, |
|||
} |
|||
|
|||
/// Descriptor flags. |
|||
/// |
|||
/// Note: This was called `fdflags` in earlier versions of WASI. |
|||
flags descriptor-flags { |
|||
/// Read mode: Data can be read. |
|||
read, |
|||
/// Write mode: Data can be written to. |
|||
write, |
|||
/// Request that writes be performed according to synchronized I/O file |
|||
/// integrity completion. The data stored in the file and the file's |
|||
/// metadata are synchronized. This is similar to `O_SYNC` in POSIX. |
|||
/// |
|||
/// The precise semantics of this operation have not yet been defined for |
|||
/// WASI. At this time, it should be interpreted as a request, and not a |
|||
/// requirement. |
|||
file-integrity-sync, |
|||
/// Request that writes be performed according to synchronized I/O data |
|||
/// integrity completion. Only the data stored in the file is |
|||
/// synchronized. This is similar to `O_DSYNC` in POSIX. |
|||
/// |
|||
/// The precise semantics of this operation have not yet been defined for |
|||
/// WASI. At this time, it should be interpreted as a request, and not a |
|||
/// requirement. |
|||
data-integrity-sync, |
|||
/// Requests that reads be performed at the same level of integrety |
|||
/// requested for writes. This is similar to `O_RSYNC` in POSIX. |
|||
/// |
|||
/// The precise semantics of this operation have not yet been defined for |
|||
/// WASI. At this time, it should be interpreted as a request, and not a |
|||
/// requirement. |
|||
requested-write-sync, |
|||
/// Mutating directories mode: Directory contents may be mutated. |
|||
/// |
|||
/// When this flag is unset on a descriptor, operations using the |
|||
/// descriptor which would create, rename, delete, modify the data or |
|||
/// metadata of filesystem objects, or obtain another handle which |
|||
/// would permit any of those, shall fail with `error-code::read-only` if |
|||
/// they would otherwise succeed. |
|||
/// |
|||
/// This may only be set on directories. |
|||
mutate-directory, |
|||
} |
|||
|
|||
/// File attributes. |
|||
/// |
|||
/// Note: This was called `filestat` in earlier versions of WASI. |
|||
record descriptor-stat { |
|||
/// File type. |
|||
%type: descriptor-type, |
|||
/// Number of hard links to the file. |
|||
link-count: link-count, |
|||
/// For regular files, the file size in bytes. For symbolic links, the |
|||
/// length in bytes of the pathname contained in the symbolic link. |
|||
size: filesize, |
|||
/// Last data access timestamp. |
|||
data-access-timestamp: datetime, |
|||
/// Last data modification timestamp. |
|||
data-modification-timestamp: datetime, |
|||
/// Last file status change timestamp. |
|||
status-change-timestamp: datetime, |
|||
} |
|||
|
|||
/// Flags determining the method of how paths are resolved. |
|||
flags path-flags { |
|||
/// As long as the resolved path corresponds to a symbolic link, it is |
|||
/// expanded. |
|||
symlink-follow, |
|||
} |
|||
|
|||
/// Open flags used by `open-at`. |
|||
flags open-flags { |
|||
/// Create file if it does not exist, similar to `O_CREAT` in POSIX. |
|||
create, |
|||
/// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. |
|||
directory, |
|||
/// Fail if file already exists, similar to `O_EXCL` in POSIX. |
|||
exclusive, |
|||
/// Truncate file to size 0, similar to `O_TRUNC` in POSIX. |
|||
truncate, |
|||
} |
|||
|
|||
/// Permissions mode used by `open-at`, `change-file-permissions-at`, and |
|||
/// similar. |
|||
flags modes { |
|||
/// True if the resource is considered readable by the containing |
|||
/// filesystem. |
|||
readable, |
|||
/// True if the resource is considered writable by the containing |
|||
/// filesystem. |
|||
writable, |
|||
/// True if the resource is considered executable by the containing |
|||
/// filesystem. This does not apply to directories. |
|||
executable, |
|||
} |
|||
|
|||
/// Access type used by `access-at`. |
|||
variant access-type { |
|||
/// Test for readability, writeability, or executability. |
|||
access(modes), |
|||
|
|||
/// Test whether the path exists. |
|||
exists, |
|||
} |
|||
|
|||
/// Number of hard links to an inode. |
|||
type link-count = u64 |
|||
|
|||
/// When setting a timestamp, this gives the value to set it to. |
|||
variant new-timestamp { |
|||
/// Leave the timestamp set to its previous value. |
|||
no-change, |
|||
/// Set the timestamp to the current time of the system clock associated |
|||
/// with the filesystem. |
|||
now, |
|||
/// Set the timestamp to the given value. |
|||
timestamp(datetime), |
|||
} |
|||
|
|||
/// A directory entry. |
|||
record directory-entry { |
|||
/// The type of the file referred to by this directory entry. |
|||
%type: descriptor-type, |
|||
|
|||
/// The name of the object. |
|||
name: string, |
|||
} |
|||
|
|||
/// Error codes returned by functions, similar to `errno` in POSIX. |
|||
/// Not all of these error codes are returned by the functions provided by this |
|||
/// API; some are used in higher-level library layers, and others are provided |
|||
/// merely for alignment with POSIX. |
|||
enum error-code { |
|||
/// Permission denied, similar to `EACCES` in POSIX. |
|||
access, |
|||
/// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. |
|||
would-block, |
|||
/// Connection already in progress, similar to `EALREADY` in POSIX. |
|||
already, |
|||
/// Bad descriptor, similar to `EBADF` in POSIX. |
|||
bad-descriptor, |
|||
/// Device or resource busy, similar to `EBUSY` in POSIX. |
|||
busy, |
|||
/// Resource deadlock would occur, similar to `EDEADLK` in POSIX. |
|||
deadlock, |
|||
/// Storage quota exceeded, similar to `EDQUOT` in POSIX. |
|||
quota, |
|||
/// File exists, similar to `EEXIST` in POSIX. |
|||
exist, |
|||
/// File too large, similar to `EFBIG` in POSIX. |
|||
file-too-large, |
|||
/// Illegal byte sequence, similar to `EILSEQ` in POSIX. |
|||
illegal-byte-sequence, |
|||
/// Operation in progress, similar to `EINPROGRESS` in POSIX. |
|||
in-progress, |
|||
/// Interrupted function, similar to `EINTR` in POSIX. |
|||
interrupted, |
|||
/// Invalid argument, similar to `EINVAL` in POSIX. |
|||
invalid, |
|||
/// I/O error, similar to `EIO` in POSIX. |
|||
io, |
|||
/// Is a directory, similar to `EISDIR` in POSIX. |
|||
is-directory, |
|||
/// Too many levels of symbolic links, similar to `ELOOP` in POSIX. |
|||
loop, |
|||
/// Too many links, similar to `EMLINK` in POSIX. |
|||
too-many-links, |
|||
/// Message too large, similar to `EMSGSIZE` in POSIX. |
|||
message-size, |
|||
/// Filename too long, similar to `ENAMETOOLONG` in POSIX. |
|||
name-too-long, |
|||
/// No such device, similar to `ENODEV` in POSIX. |
|||
no-device, |
|||
/// No such file or directory, similar to `ENOENT` in POSIX. |
|||
no-entry, |
|||
/// No locks available, similar to `ENOLCK` in POSIX. |
|||
no-lock, |
|||
/// Not enough space, similar to `ENOMEM` in POSIX. |
|||
insufficient-memory, |
|||
/// No space left on device, similar to `ENOSPC` in POSIX. |
|||
insufficient-space, |
|||
/// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. |
|||
not-directory, |
|||
/// Directory not empty, similar to `ENOTEMPTY` in POSIX. |
|||
not-empty, |
|||
/// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. |
|||
not-recoverable, |
|||
/// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. |
|||
unsupported, |
|||
/// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. |
|||
no-tty, |
|||
/// No such device or address, similar to `ENXIO` in POSIX. |
|||
no-such-device, |
|||
/// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. |
|||
overflow, |
|||
/// Operation not permitted, similar to `EPERM` in POSIX. |
|||
not-permitted, |
|||
/// Broken pipe, similar to `EPIPE` in POSIX. |
|||
pipe, |
|||
/// Read-only file system, similar to `EROFS` in POSIX. |
|||
read-only, |
|||
/// Invalid seek, similar to `ESPIPE` in POSIX. |
|||
invalid-seek, |
|||
/// Text file busy, similar to `ETXTBSY` in POSIX. |
|||
text-file-busy, |
|||
/// Cross-device link, similar to `EXDEV` in POSIX. |
|||
cross-device, |
|||
} |
|||
|
|||
/// File or memory access pattern advisory information. |
|||
enum advice { |
|||
/// The application has no advice to give on its behavior with respect |
|||
/// to the specified data. |
|||
normal, |
|||
/// The application expects to access the specified data sequentially |
|||
/// from lower offsets to higher offsets. |
|||
sequential, |
|||
/// The application expects to access the specified data in a random |
|||
/// order. |
|||
random, |
|||
/// The application expects to access the specified data in the near |
|||
/// future. |
|||
will-need, |
|||
/// The application expects that it will not access the specified data |
|||
/// in the near future. |
|||
dont-need, |
|||
/// The application expects to access the specified data once and then |
|||
/// not reuse it thereafter. |
|||
no-reuse, |
|||
} |
|||
|
|||
/// A descriptor is a reference to a filesystem object, which may be a file, |
|||
/// directory, named pipe, special file, or other object on which filesystem |
|||
/// calls may be made. |
|||
/// |
|||
/// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). |
|||
type descriptor = u32 |
|||
|
|||
/// A 128-bit hash value, split into parts because wasm doesn't have a |
|||
/// 128-bit integer type. |
|||
record metadata-hash-value { |
|||
/// 64 bits of a 128-bit hash value. |
|||
lower: u64, |
|||
/// Another 64 bits of a 128-bit hash value. |
|||
upper: u64, |
|||
} |
|||
|
|||
/// Return a stream for reading from a file, if available. |
|||
/// |
|||
/// May fail with an error-code describing why the file cannot be read. |
|||
/// |
|||
/// Multiple read, write, and append streams may be active on the same open |
|||
/// file and they do not interfere with each other. |
|||
/// |
|||
/// Note: This allows using `read-stream`, which is similar to `read` in POSIX. |
|||
read-via-stream: func( |
|||
this: descriptor, |
|||
/// The offset within the file at which to start reading. |
|||
offset: filesize, |
|||
) -> result<input-stream, error-code> |
|||
|
|||
/// Return a stream for writing to a file, if available. |
|||
/// |
|||
/// May fail with an error-code describing why the file cannot be written. |
|||
/// |
|||
/// Note: This allows using `write-stream`, which is similar to `write` in |
|||
/// POSIX. |
|||
write-via-stream: func( |
|||
this: descriptor, |
|||
/// The offset within the file at which to start writing. |
|||
offset: filesize, |
|||
) -> result<output-stream, error-code> |
|||
|
|||
/// Return a stream for appending to a file, if available. |
|||
/// |
|||
/// May fail with an error-code describing why the file cannot be appended. |
|||
/// |
|||
/// Note: This allows using `write-stream`, which is similar to `write` with |
|||
/// `O_APPEND` in in POSIX. |
|||
append-via-stream: func( |
|||
this: descriptor, |
|||
) -> result<output-stream, error-code> |
|||
|
|||
/// Provide file advisory information on a descriptor. |
|||
/// |
|||
/// This is similar to `posix_fadvise` in POSIX. |
|||
advise: func( |
|||
this: descriptor, |
|||
/// The offset within the file to which the advisory applies. |
|||
offset: filesize, |
|||
/// The length of the region to which the advisory applies. |
|||
length: filesize, |
|||
/// The advice. |
|||
advice: advice |
|||
) -> result<_, error-code> |
|||
|
|||
/// Synchronize the data of a file to disk. |
|||
/// |
|||
/// This function succeeds with no effect if the file descriptor is not |
|||
/// opened for writing. |
|||
/// |
|||
/// Note: This is similar to `fdatasync` in POSIX. |
|||
sync-data: func(this: descriptor) -> result<_, error-code> |
|||
|
|||
/// Get flags associated with a descriptor. |
|||
/// |
|||
/// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. |
|||
/// |
|||
/// Note: This returns the value that was the `fs_flags` value returned |
|||
/// from `fdstat_get` in earlier versions of WASI. |
|||
get-flags: func(this: descriptor) -> result<descriptor-flags, error-code> |
|||
|
|||
/// Get the dynamic type of a descriptor. |
|||
/// |
|||
/// Note: This returns the same value as the `type` field of the `fd-stat` |
|||
/// returned by `stat`, `stat-at` and similar. |
|||
/// |
|||
/// Note: This returns similar flags to the `st_mode & S_IFMT` value provided |
|||
/// by `fstat` in POSIX. |
|||
/// |
|||
/// Note: This returns the value that was the `fs_filetype` value returned |
|||
/// from `fdstat_get` in earlier versions of WASI. |
|||
get-type: func(this: descriptor) -> result<descriptor-type, error-code> |
|||
|
|||
/// Adjust the size of an open file. If this increases the file's size, the |
|||
/// extra bytes are filled with zeros. |
|||
/// |
|||
/// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. |
|||
set-size: func(this: descriptor, size: filesize) -> result<_, error-code> |
|||
|
|||
/// Adjust the timestamps of an open file or directory. |
|||
/// |
|||
/// Note: This is similar to `futimens` in POSIX. |
|||
/// |
|||
/// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. |
|||
set-times: func( |
|||
this: descriptor, |
|||
/// The desired values of the data access timestamp. |
|||
data-access-timestamp: new-timestamp, |
|||
/// The desired values of the data modification timestamp. |
|||
data-modification-timestamp: new-timestamp, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Read from a descriptor, without using and updating the descriptor's offset. |
|||
/// |
|||
/// This function returns a list of bytes containing the data that was |
|||
/// read, along with a bool which, when true, indicates that the end of the |
|||
/// file was reached. The returned list will contain up to `length` bytes; it |
|||
/// may return fewer than requested, if the end of the file is reached or |
|||
/// if the I/O operation is interrupted. |
|||
/// |
|||
/// In the future, this may change to return a `stream<u8, error-code>`. |
|||
/// |
|||
/// Note: This is similar to `pread` in POSIX. |
|||
read: func( |
|||
this: descriptor, |
|||
/// The maximum number of bytes to read. |
|||
length: filesize, |
|||
/// The offset within the file at which to read. |
|||
offset: filesize, |
|||
) -> result<tuple<list<u8>, bool>, error-code> |
|||
|
|||
/// Write to a descriptor, without using and updating the descriptor's offset. |
|||
/// |
|||
/// It is valid to write past the end of a file; the file is extended to the |
|||
/// extent of the write, with bytes between the previous end and the start of |
|||
/// the write set to zero. |
|||
/// |
|||
/// In the future, this may change to take a `stream<u8, error-code>`. |
|||
/// |
|||
/// Note: This is similar to `pwrite` in POSIX. |
|||
write: func( |
|||
this: descriptor, |
|||
/// Data to write |
|||
buffer: list<u8>, |
|||
/// The offset within the file at which to write. |
|||
offset: filesize, |
|||
) -> result<filesize, error-code> |
|||
|
|||
/// Read directory entries from a directory. |
|||
/// |
|||
/// On filesystems where directories contain entries referring to themselves |
|||
/// and their parents, often named `.` and `..` respectively, these entries |
|||
/// are omitted. |
|||
/// |
|||
/// This always returns a new stream which starts at the beginning of the |
|||
/// directory. Multiple streams may be active on the same directory, and they |
|||
/// do not interfere with each other. |
|||
read-directory: func( |
|||
this: descriptor |
|||
) -> result<directory-entry-stream, error-code> |
|||
|
|||
/// Synchronize the data and metadata of a file to disk. |
|||
/// |
|||
/// This function succeeds with no effect if the file descriptor is not |
|||
/// opened for writing. |
|||
/// |
|||
/// Note: This is similar to `fsync` in POSIX. |
|||
sync: func(this: descriptor) -> result<_, error-code> |
|||
|
|||
/// Create a directory. |
|||
/// |
|||
/// Note: This is similar to `mkdirat` in POSIX. |
|||
create-directory-at: func( |
|||
this: descriptor, |
|||
/// The relative path at which to create the directory. |
|||
path: string, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Return the attributes of an open file or directory. |
|||
/// |
|||
/// Note: This is similar to `fstat` in POSIX, except that it does not return |
|||
/// device and inode information. For testing whether two descriptors refer to |
|||
/// the same underlying filesystem object, use `is-same-object`. To obtain |
|||
/// additional data that can be used do determine whether a file has been |
|||
/// modified, use `metadata-hash`. |
|||
/// |
|||
/// Note: This was called `fd_filestat_get` in earlier versions of WASI. |
|||
stat: func(this: descriptor) -> result<descriptor-stat, error-code> |
|||
|
|||
/// Return the attributes of a file or directory. |
|||
/// |
|||
/// Note: This is similar to `fstatat` in POSIX, except that it does not |
|||
/// return device and inode information. See the `stat` description for a |
|||
/// discussion of alternatives. |
|||
/// |
|||
/// Note: This was called `path_filestat_get` in earlier versions of WASI. |
|||
stat-at: func( |
|||
this: descriptor, |
|||
/// Flags determining the method of how the path is resolved. |
|||
path-flags: path-flags, |
|||
/// The relative path of the file or directory to inspect. |
|||
path: string, |
|||
) -> result<descriptor-stat, error-code> |
|||
|
|||
/// Adjust the timestamps of a file or directory. |
|||
/// |
|||
/// Note: This is similar to `utimensat` in POSIX. |
|||
/// |
|||
/// Note: This was called `path_filestat_set_times` in earlier versions of |
|||
/// WASI. |
|||
set-times-at: func( |
|||
this: descriptor, |
|||
/// Flags determining the method of how the path is resolved. |
|||
path-flags: path-flags, |
|||
/// The relative path of the file or directory to operate on. |
|||
path: string, |
|||
/// The desired values of the data access timestamp. |
|||
data-access-timestamp: new-timestamp, |
|||
/// The desired values of the data modification timestamp. |
|||
data-modification-timestamp: new-timestamp, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Create a hard link. |
|||
/// |
|||
/// Note: This is similar to `linkat` in POSIX. |
|||
link-at: func( |
|||
this: descriptor, |
|||
/// Flags determining the method of how the path is resolved. |
|||
old-path-flags: path-flags, |
|||
/// The relative source path from which to link. |
|||
old-path: string, |
|||
/// The base directory for `new-path`. |
|||
new-descriptor: descriptor, |
|||
/// The relative destination path at which to create the hard link. |
|||
new-path: string, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Open a file or directory. |
|||
/// |
|||
/// The returned descriptor is not guaranteed to be the lowest-numbered |
|||
/// descriptor not currently open/ it is randomized to prevent applications |
|||
/// from depending on making assumptions about indexes, since this is |
|||
/// error-prone in multi-threaded contexts. The returned descriptor is |
|||
/// guaranteed to be less than 2**31. |
|||
/// |
|||
/// If `flags` contains `descriptor-flags::mutate-directory`, and the base |
|||
/// descriptor doesn't have `descriptor-flags::mutate-directory` set, |
|||
/// `open-at` fails with `error-code::read-only`. |
|||
/// |
|||
/// If `flags` contains `write` or `mutate-directory`, or `open-flags` |
|||
/// contains `truncate` or `create`, and the base descriptor doesn't have |
|||
/// `descriptor-flags::mutate-directory` set, `open-at` fails with |
|||
/// `error-code::read-only`. |
|||
/// |
|||
/// Note: This is similar to `openat` in POSIX. |
|||
open-at: func( |
|||
this: descriptor, |
|||
/// Flags determining the method of how the path is resolved. |
|||
path-flags: path-flags, |
|||
/// The relative path of the object to open. |
|||
path: string, |
|||
/// The method by which to open the file. |
|||
open-flags: open-flags, |
|||
/// Flags to use for the resulting descriptor. |
|||
%flags: descriptor-flags, |
|||
/// Permissions to use when creating a new file. |
|||
modes: modes |
|||
) -> result<descriptor, error-code> |
|||
|
|||
/// Read the contents of a symbolic link. |
|||
/// |
|||
/// If the contents contain an absolute or rooted path in the underlying |
|||
/// filesystem, this function fails with `error-code::not-permitted`. |
|||
/// |
|||
/// Note: This is similar to `readlinkat` in POSIX. |
|||
readlink-at: func( |
|||
this: descriptor, |
|||
/// The relative path of the symbolic link from which to read. |
|||
path: string, |
|||
) -> result<string, error-code> |
|||
|
|||
/// Remove a directory. |
|||
/// |
|||
/// Return `error-code::not-empty` if the directory is not empty. |
|||
/// |
|||
/// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. |
|||
remove-directory-at: func( |
|||
this: descriptor, |
|||
/// The relative path to a directory to remove. |
|||
path: string, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Rename a filesystem object. |
|||
/// |
|||
/// Note: This is similar to `renameat` in POSIX. |
|||
rename-at: func( |
|||
this: descriptor, |
|||
/// The relative source path of the file or directory to rename. |
|||
old-path: string, |
|||
/// The base directory for `new-path`. |
|||
new-descriptor: descriptor, |
|||
/// The relative destination path to which to rename the file or directory. |
|||
new-path: string, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Create a symbolic link (also known as a "symlink"). |
|||
/// |
|||
/// If `old-path` starts with `/`, the function fails with |
|||
/// `error-code::not-permitted`. |
|||
/// |
|||
/// Note: This is similar to `symlinkat` in POSIX. |
|||
symlink-at: func( |
|||
this: descriptor, |
|||
/// The contents of the symbolic link. |
|||
old-path: string, |
|||
/// The relative destination path at which to create the symbolic link. |
|||
new-path: string, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Check accessibility of a filesystem path. |
|||
/// |
|||
/// Check whether the given filesystem path names an object which is |
|||
/// readable, writable, or executable, or whether it exists. |
|||
/// |
|||
/// This does not a guarantee that subsequent accesses will succeed, as |
|||
/// filesystem permissions may be modified asynchronously by external |
|||
/// entities. |
|||
/// |
|||
/// Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX. |
|||
access-at: func( |
|||
this: descriptor, |
|||
/// Flags determining the method of how the path is resolved. |
|||
path-flags: path-flags, |
|||
/// The relative path to check. |
|||
path: string, |
|||
/// The type of check to perform. |
|||
%type: access-type |
|||
) -> result<_, error-code> |
|||
|
|||
/// Unlink a filesystem object that is not a directory. |
|||
/// |
|||
/// Return `error-code::is-directory` if the path refers to a directory. |
|||
/// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. |
|||
unlink-file-at: func( |
|||
this: descriptor, |
|||
/// The relative path to a file to unlink. |
|||
path: string, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Change the permissions of a filesystem object that is not a directory. |
|||
/// |
|||
/// Note that the ultimate meanings of these permissions is |
|||
/// filesystem-specific. |
|||
/// |
|||
/// Note: This is similar to `fchmodat` in POSIX. |
|||
change-file-permissions-at: func( |
|||
this: descriptor, |
|||
/// Flags determining the method of how the path is resolved. |
|||
path-flags: path-flags, |
|||
/// The relative path to operate on. |
|||
path: string, |
|||
/// The new permissions for the filesystem object. |
|||
modes: modes, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Change the permissions of a directory. |
|||
/// |
|||
/// Note that the ultimate meanings of these permissions is |
|||
/// filesystem-specific. |
|||
/// |
|||
/// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" |
|||
/// flag. `read` on a directory implies readability and searchability, and |
|||
/// `execute` is not valid for directories. |
|||
/// |
|||
/// Note: This is similar to `fchmodat` in POSIX. |
|||
change-directory-permissions-at: func( |
|||
this: descriptor, |
|||
/// Flags determining the method of how the path is resolved. |
|||
path-flags: path-flags, |
|||
/// The relative path to operate on. |
|||
path: string, |
|||
/// The new permissions for the directory. |
|||
modes: modes, |
|||
) -> result<_, error-code> |
|||
|
|||
/// Request a shared advisory lock for an open file. |
|||
/// |
|||
/// This requests a *shared* lock; more than one shared lock can be held for |
|||
/// a file at the same time. |
|||
/// |
|||
/// If the open file has an exclusive lock, this function downgrades the lock |
|||
/// to a shared lock. If it has a shared lock, this function has no effect. |
|||
/// |
|||
/// This requests an *advisory* lock, meaning that the file could be accessed |
|||
/// by other programs that don't hold the lock. |
|||
/// |
|||
/// It is unspecified how shared locks interact with locks acquired by |
|||
/// non-WASI programs. |
|||
/// |
|||
/// This function blocks until the lock can be acquired. |
|||
/// |
|||
/// Not all filesystems support locking; on filesystems which don't support |
|||
/// locking, this function returns `error-code::unsupported`. |
|||
/// |
|||
/// Note: This is similar to `flock(fd, LOCK_SH)` in Unix. |
|||
lock-shared: func(this: descriptor) -> result<_, error-code> |
|||
|
|||
/// Request an exclusive advisory lock for an open file. |
|||
/// |
|||
/// This requests an *exclusive* lock; no other locks may be held for the |
|||
/// file while an exclusive lock is held. |
|||
/// |
|||
/// If the open file has a shared lock and there are no exclusive locks held |
|||
/// for the file, this function upgrades the lock to an exclusive lock. If the |
|||
/// open file already has an exclusive lock, this function has no effect. |
|||
/// |
|||
/// This requests an *advisory* lock, meaning that the file could be accessed |
|||
/// by other programs that don't hold the lock. |
|||
/// |
|||
/// It is unspecified whether this function succeeds if the file descriptor |
|||
/// is not opened for writing. It is unspecified how exclusive locks interact |
|||
/// with locks acquired by non-WASI programs. |
|||
/// |
|||
/// This function blocks until the lock can be acquired. |
|||
/// |
|||
/// Not all filesystems support locking; on filesystems which don't support |
|||
/// locking, this function returns `error-code::unsupported`. |
|||
/// |
|||
/// Note: This is similar to `flock(fd, LOCK_EX)` in Unix. |
|||
lock-exclusive: func(this: descriptor) -> result<_, error-code> |
|||
|
|||
/// Request a shared advisory lock for an open file. |
|||
/// |
|||
/// This requests a *shared* lock; more than one shared lock can be held for |
|||
/// a file at the same time. |
|||
/// |
|||
/// If the open file has an exclusive lock, this function downgrades the lock |
|||
/// to a shared lock. If it has a shared lock, this function has no effect. |
|||
/// |
|||
/// This requests an *advisory* lock, meaning that the file could be accessed |
|||
/// by other programs that don't hold the lock. |
|||
/// |
|||
/// It is unspecified how shared locks interact with locks acquired by |
|||
/// non-WASI programs. |
|||
/// |
|||
/// This function returns `error-code::would-block` if the lock cannot be |
|||
/// acquired. |
|||
/// |
|||
/// Not all filesystems support locking; on filesystems which don't support |
|||
/// locking, this function returns `error-code::unsupported`. |
|||
/// |
|||
/// Note: This is similar to `flock(fd, LOCK_SH | LOCK_NB)` in Unix. |
|||
try-lock-shared: func(this: descriptor) -> result<_, error-code> |
|||
|
|||
/// Request an exclusive advisory lock for an open file. |
|||
/// |
|||
/// This requests an *exclusive* lock; no other locks may be held for the |
|||
/// file while an exclusive lock is held. |
|||
/// |
|||
/// If the open file has a shared lock and there are no exclusive locks held |
|||
/// for the file, this function upgrades the lock to an exclusive lock. If the |
|||
/// open file already has an exclusive lock, this function has no effect. |
|||
/// |
|||
/// This requests an *advisory* lock, meaning that the file could be accessed |
|||
/// by other programs that don't hold the lock. |
|||
/// |
|||
/// It is unspecified whether this function succeeds if the file descriptor |
|||
/// is not opened for writing. It is unspecified how exclusive locks interact |
|||
/// with locks acquired by non-WASI programs. |
|||
/// |
|||
/// This function returns `error-code::would-block` if the lock cannot be |
|||
/// acquired. |
|||
/// |
|||
/// Not all filesystems support locking; on filesystems which don't support |
|||
/// locking, this function returns `error-code::unsupported`. |
|||
/// |
|||
/// Note: This is similar to `flock(fd, LOCK_EX | LOCK_NB)` in Unix. |
|||
try-lock-exclusive: func(this: descriptor) -> result<_, error-code> |
|||
|
|||
/// Release a shared or exclusive lock on an open file. |
|||
/// |
|||
/// Note: This is similar to `flock(fd, LOCK_UN)` in Unix. |
|||
unlock: func(this: descriptor) -> result<_, error-code> |
|||
|
|||
/// Dispose of the specified `descriptor`, after which it may no longer |
|||
/// be used. |
|||
drop-descriptor: func(this: descriptor) |
|||
|
|||
/// A stream of directory entries. |
|||
/// |
|||
/// This [represents a stream of `dir-entry`](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Streams). |
|||
type directory-entry-stream = u32 |
|||
|
|||
/// Read a single directory entry from a `directory-entry-stream`. |
|||
read-directory-entry: func( |
|||
this: directory-entry-stream |
|||
) -> result<option<directory-entry>, error-code> |
|||
|
|||
/// Dispose of the specified `directory-entry-stream`, after which it may no longer |
|||
/// be used. |
|||
drop-directory-entry-stream: func(this: directory-entry-stream) |
|||
|
|||
/// Test whether two descriptors refer to the same filesystem object. |
|||
/// |
|||
/// In POSIX, this corresponds to testing whether the two descriptors have the |
|||
/// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. |
|||
/// wasi-filesystem does not expose device and inode numbers, so this function |
|||
/// may be used instead. |
|||
is-same-object: func(this: descriptor, other: descriptor) -> bool |
|||
|
|||
/// Return a hash of the metadata associated with a filesystem object referred |
|||
/// to by a descriptor. |
|||
/// |
|||
/// This returns a hash of the last-modification timestamp and file size, and |
|||
/// may also include the inode number, device number, birth timestamp, and |
|||
/// other metadata fields that may change when the file is modified or |
|||
/// replaced. It may also include a secret value chosen by the |
|||
/// implementation and not otherwise exposed. |
|||
/// |
|||
/// Implementations are encourated to provide the following properties: |
|||
/// |
|||
/// - If the file is not modified or replaced, the computed hash value should |
|||
/// usually not change. |
|||
/// - If the object is modified or replaced, the computed hash value should |
|||
/// usually change. |
|||
/// - The inputs to the hash should not be easily computable from the |
|||
/// computed hash. |
|||
/// |
|||
/// However, none of these is required. |
|||
metadata-hash: func( |
|||
this: descriptor, |
|||
) -> result<metadata-hash-value, error-code> |
|||
|
|||
/// Return a hash of the metadata associated with a filesystem object referred |
|||
/// to by a directory descriptor and a relative path. |
|||
/// |
|||
/// This performs the same hash computation as `metadata-hash`. |
|||
metadata-hash-at: func( |
|||
this: descriptor, |
|||
/// Flags determining the method of how the path is resolved. |
|||
path-flags: path-flags, |
|||
/// The relative path of the file or directory to inspect. |
|||
path: string, |
|||
) -> result<metadata-hash-value, error-code> |
|||
} |
@ -0,0 +1,6 @@ |
|||
package wasi:filesystem |
|||
|
|||
world example-world { |
|||
import types |
|||
import preopens |
|||
} |
@ -0,0 +1,24 @@ |
|||
// The `wasi:http/incoming-handler` interface is meant to be exported by |
|||
// components and called by the host in response to a new incoming HTTP |
|||
// response. |
|||
// |
|||
// NOTE: in Preview3, this interface will be merged with |
|||
// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface |
|||
// that takes a `request` parameter and returns a `response` result. |
|||
// |
|||
interface incoming-handler { |
|||
use types.{incoming-request, response-outparam} |
|||
|
|||
// The `handle` function takes an outparam instead of returning its response |
|||
// so that the component may stream its response while streaming any other |
|||
// request or response bodies. The callee MUST write a response to the |
|||
// `response-out` and then finish the response before returning. The `handle` |
|||
// function is allowed to continue execution after finishing the response's |
|||
// output stream. While this post-response execution is taken off the |
|||
// critical path, since there is no return value, there is no way to report |
|||
// its success or failure. |
|||
handle: func( |
|||
request: incoming-request, |
|||
response-out: response-outparam |
|||
) |
|||
} |
@ -0,0 +1,18 @@ |
|||
// The `wasi:http/outgoing-handler` interface is meant to be imported by |
|||
// components and implemented by the host. |
|||
// |
|||
// NOTE: in Preview3, this interface will be merged with |
|||
// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface |
|||
// that takes a `request` parameter and returns a `response` result. |
|||
// |
|||
interface outgoing-handler { |
|||
use types.{outgoing-request, request-options, future-incoming-response} |
|||
|
|||
// The parameter and result types of the `handle` function allow the caller |
|||
// to concurrently stream the bodies of the outgoing request and the incoming |
|||
// response. |
|||
handle: func( |
|||
request: outgoing-request, |
|||
options: option<request-options> |
|||
) -> future-incoming-response |
|||
} |
@ -0,0 +1,34 @@ |
|||
package wasi:http |
|||
|
|||
// The `wasi:http/proxy` world captures a widely-implementable intersection of |
|||
// hosts that includes HTTP forward and reverse proxies. Components targeting |
|||
// this world may concurrently stream in and out any number of incoming and |
|||
// outgoing HTTP requests. |
|||
world proxy { |
|||
// HTTP proxies have access to time and randomness. |
|||
import wasi:clocks/wall-clock |
|||
import wasi:clocks/monotonic-clock |
|||
import wasi:clocks/timezone |
|||
import wasi:random/random |
|||
|
|||
// Proxies have standard output and error streams which are expected to |
|||
// terminate in a developer-facing console provided by the host. |
|||
import wasi:cli/stdout |
|||
import wasi:cli/stderr |
|||
|
|||
// TODO: this is a temporary workaround until component tooling is able to |
|||
// gracefully handle the absence of stdin. Hosts must return an eof stream |
|||
// for this import, which is what wasi-libc + tooling will do automatically |
|||
// when this import is properly removed. |
|||
import wasi:cli/stdin |
|||
|
|||
// This is the default handler to use when user code simply wants to make an |
|||
// HTTP request (e.g., via `fetch()`). |
|||
import outgoing-handler |
|||
|
|||
// The host delivers incoming HTTP requests to a component by calling the |
|||
// `handle` function of this exported interface. A host may arbitrarily reuse |
|||
// or not reuse component instance when delivering incoming HTTP requests and |
|||
// thus a component must be able to handle 0..N calls to `handle`. |
|||
export incoming-handler |
|||
} |
@ -0,0 +1,155 @@ |
|||
// The `wasi:http/types` interface is meant to be imported by components to |
|||
// define the HTTP resource types and operations used by the component's |
|||
// imported and exported interfaces. |
|||
interface types { |
|||
use wasi:io/streams.{input-stream, output-stream} |
|||
use wasi:poll/poll.{pollable} |
|||
|
|||
// This type corresponds to HTTP standard Methods. |
|||
variant method { |
|||
get, |
|||
head, |
|||
post, |
|||
put, |
|||
delete, |
|||
connect, |
|||
options, |
|||
trace, |
|||
patch, |
|||
other(string) |
|||
} |
|||
|
|||
// This type corresponds to HTTP standard Related Schemes. |
|||
variant scheme { |
|||
HTTP, |
|||
HTTPS, |
|||
other(string) |
|||
} |
|||
|
|||
// TODO: perhaps better align with HTTP semantics? |
|||
// This type enumerates the different kinds of errors that may occur when |
|||
// initially returning a response. |
|||
variant error { |
|||
invalid-url(string), |
|||
timeout-error(string), |
|||
protocol-error(string), |
|||
unexpected-error(string) |
|||
} |
|||
|
|||
// This following block defines the `fields` resource which corresponds to |
|||
// HTTP standard Fields. Soon, when resource types are added, the `type |
|||
// fields = u32` type alias can be replaced by a proper `resource fields` |
|||
// definition containing all the functions using the method syntactic sugar. |
|||
type fields = u32 |
|||
drop-fields: func(fields: fields) |
|||
new-fields: func(entries: list<tuple<string,string>>) -> fields |
|||
fields-get: func(fields: fields, name: string) -> list<list<u8>> |
|||
fields-set: func(fields: fields, name: string, value: list<list<u8>>) |
|||
fields-delete: func(fields: fields, name: string) |
|||
fields-append: func(fields: fields, name: string, value: list<u8>) |
|||
fields-entries: func(fields: fields) -> list<tuple<string,list<u8>>> |
|||
fields-clone: func(fields: fields) -> fields |
|||
|
|||
type headers = fields |
|||
type trailers = fields |
|||
|
|||
// The following block defines stream types which corresponds to the HTTP |
|||
// standard Contents and Trailers. With Preview3, all of these fields can be |
|||
// replaced by a stream<u8, option<trailers>>. In the interim, we need to |
|||
// build on separate resource types defined by `wasi:io/streams`. The |
|||
// `finish-` functions emulate the stream's result value and MUST be called |
|||
// exactly once after the final read/write from/to the stream before dropping |
|||
// the stream. |
|||
type incoming-stream = input-stream |
|||
type outgoing-stream = output-stream |
|||
finish-incoming-stream: func(s: incoming-stream) -> option<trailers> |
|||
finish-outgoing-stream: func(s: outgoing-stream, trailers: option<trailers>) |
|||
|
|||
// The following block defines the `incoming-request` and `outgoing-request` |
|||
// resource types that correspond to HTTP standard Requests. Soon, when |
|||
// resource types are added, the `u32` type aliases can be replaced by |
|||
// proper `resource` type definitions containing all the functions as |
|||
// methods. Later, Preview2 will allow both types to be merged together into |
|||
// a single `request` type (that uses the single `stream` type mentioned |
|||
// above). The `consume` and `write` methods may only be called once (and |
|||
// return failure thereafter). |
|||
type incoming-request = u32 |
|||
type outgoing-request = u32 |
|||
drop-incoming-request: func(request: incoming-request) |
|||
drop-outgoing-request: func(request: outgoing-request) |
|||
incoming-request-method: func(request: incoming-request) -> method |
|||
incoming-request-path-with-query: func(request: incoming-request) -> option<string> |
|||
incoming-request-scheme: func(request: incoming-request) -> option<scheme> |
|||
incoming-request-authority: func(request: incoming-request) -> option<string> |
|||
incoming-request-headers: func(request: incoming-request) -> headers |
|||
incoming-request-consume: func(request: incoming-request) -> result<incoming-stream> |
|||
new-outgoing-request: func( |
|||
method: method, |
|||
path-with-query: option<string>, |
|||
scheme: option<scheme>, |
|||
authority: option<string>, |
|||
headers: headers |
|||
) -> outgoing-request |
|||
outgoing-request-write: func(request: outgoing-request) -> result<outgoing-stream> |
|||
|
|||
// Additional optional parameters that can be set when making a request. |
|||
record request-options { |
|||
// The following timeouts are specific to the HTTP protocol and work |
|||
// independently of the overall timeouts passed to `io.poll.poll-oneoff`. |
|||
|
|||
// The timeout for the initial connect. |
|||
connect-timeout-ms: option<u32>, |
|||
|
|||
// The timeout for receiving the first byte of the response body. |
|||
first-byte-timeout-ms: option<u32>, |
|||
|
|||
// The timeout for receiving the next chunk of bytes in the response body |
|||
// stream. |
|||
between-bytes-timeout-ms: option<u32> |
|||
} |
|||
|
|||
// The following block defines a special resource type used by the |
|||
// `wasi:http/incoming-handler` interface. When resource types are added, this |
|||
// block can be replaced by a proper `resource response-outparam { ... }` |
|||
// definition. Later, with Preview3, the need for an outparam goes away entirely |
|||
// (the `wasi:http/handler` interface used for both incoming and outgoing can |
|||
// simply return a `stream`). |
|||
type response-outparam = u32 |
|||
drop-response-outparam: func(response: response-outparam) |
|||
set-response-outparam: func(param: response-outparam, response: result<outgoing-response, error>) -> result |
|||
|
|||
// This type corresponds to the HTTP standard Status Code. |
|||
type status-code = u16 |
|||
|
|||
// The following block defines the `incoming-response` and `outgoing-response` |
|||
// resource types that correspond to HTTP standard Responses. Soon, when |
|||
// resource types are added, the `u32` type aliases can be replaced by proper |
|||
// `resource` type definitions containing all the functions as methods. Later, |
|||
// Preview2 will allow both types to be merged together into a single `response` |
|||
// type (that uses the single `stream` type mentioned above). The `consume` and |
|||
// `write` methods may only be called once (and return failure thereafter). |
|||
type incoming-response = u32 |
|||
type outgoing-response = u32 |
|||
drop-incoming-response: func(response: incoming-response) |
|||
drop-outgoing-response: func(response: outgoing-response) |
|||
incoming-response-status: func(response: incoming-response) -> status-code |
|||
incoming-response-headers: func(response: incoming-response) -> headers |
|||
incoming-response-consume: func(response: incoming-response) -> result<incoming-stream> |
|||
new-outgoing-response: func( |
|||
status-code: status-code, |
|||
headers: headers |
|||
) -> outgoing-response |
|||
outgoing-response-write: func(response: outgoing-response) -> result<outgoing-stream> |
|||
|
|||
// The following block defines a special resource type used by the |
|||
// `wasi:http/outgoing-handler` interface to emulate |
|||
// `future<result<response, error>>` in advance of Preview3. Given a |
|||
// `future-incoming-response`, the client can call the non-blocking `get` |
|||
// method to get the result if it is available. If the result is not available, |
|||
// the client can call `listen` to get a `pollable` that can be passed to |
|||
// `io.poll.poll-oneoff`. |
|||
type future-incoming-response = u32 |
|||
drop-future-incoming-response: func(f: future-incoming-response) |
|||
future-incoming-response-get: func(f: future-incoming-response) -> option<result<incoming-response, error>> |
|||
listen-to-future-incoming-response: func(f: future-incoming-response) -> pollable |
|||
} |
@ -0,0 +1,253 @@ |
|||
package wasi:io |
|||
|
|||
/// WASI I/O is an I/O abstraction API which is currently focused on providing |
|||
/// stream types. |
|||
/// |
|||
/// In the future, the component model is expected to add built-in stream types; |
|||
/// when it does, they are expected to subsume this API. |
|||
interface streams { |
|||
use wasi:poll/poll.{pollable} |
|||
|
|||
/// Streams provide a sequence of data and then end; once they end, they |
|||
/// no longer provide any further data. |
|||
/// |
|||
/// For example, a stream reading from a file ends when the stream reaches |
|||
/// the end of the file. For another example, a stream reading from a |
|||
/// socket ends when the socket is closed. |
|||
enum stream-status { |
|||
/// The stream is open and may produce further data. |
|||
open, |
|||
/// When reading, this indicates that the stream will not produce |
|||
/// further data. |
|||
/// When writing, this indicates that the stream will no longer be read. |
|||
/// Further writes are still permitted. |
|||
ended, |
|||
} |
|||
|
|||
/// An input bytestream. In the future, this will be replaced by handle |
|||
/// types. |
|||
/// |
|||
/// `input-stream`s are *non-blocking* to the extent practical on underlying |
|||
/// platforms. I/O operations always return promptly; if fewer bytes are |
|||
/// promptly available than requested, they return the number of bytes promptly |
|||
/// available, which could even be zero. To wait for data to be available, |
|||
/// use the `subscribe-to-input-stream` function to obtain a `pollable` which |
|||
/// can be polled for using `wasi:poll/poll.poll_oneoff`. |
|||
/// |
|||
/// And at present, it is a `u32` instead of being an actual handle, until |
|||
/// the wit-bindgen implementation of handles and resources is ready. |
|||
/// |
|||
/// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). |
|||
type input-stream = u32 |
|||
|
|||
/// Perform a non-blocking read from the stream. |
|||
/// |
|||
/// This function returns a list of bytes containing the data that was |
|||
/// read, along with a `stream-status` which, indicates whether further |
|||
/// reads are expected to produce data. The returned list will contain up to |
|||
/// `len` bytes; it may return fewer than requested, but not more. An |
|||
/// empty list and `stream-status:open` indicates no more data is |
|||
/// available at this time, and that the pollable given by |
|||
/// `subscribe-to-input-stream` will be ready when more data is available. |
|||
/// |
|||
/// Once a stream has reached the end, subsequent calls to `read` or |
|||
/// `skip` will always report `stream-status:ended` rather than producing more |
|||
/// data. |
|||
/// |
|||
/// When the caller gives a `len` of 0, it represents a request to read 0 |
|||
/// bytes. This read should always succeed and return an empty list and |
|||
/// the current `stream-status`. |
|||
/// |
|||
/// The `len` parameter is a `u64`, which could represent a list of u8 which |
|||
/// is not possible to allocate in wasm32, or not desirable to allocate as |
|||
/// as a return value by the callee. The callee may return a list of bytes |
|||
/// less than `len` in size while more bytes are available for reading. |
|||
read: func( |
|||
this: input-stream, |
|||
/// The maximum number of bytes to read |
|||
len: u64 |
|||
) -> result<tuple<list<u8>, stream-status>> |
|||
|
|||
/// Read bytes from a stream, after blocking until at least one byte can |
|||
/// be read. Except for blocking, identical to `read`. |
|||
blocking-read: func( |
|||
this: input-stream, |
|||
/// The maximum number of bytes to read |
|||
len: u64 |
|||
) -> result<tuple<list<u8>, stream-status>> |
|||
|
|||
/// Skip bytes from a stream. |
|||
/// |
|||
/// This is similar to the `read` function, but avoids copying the |
|||
/// bytes into the instance. |
|||
/// |
|||
/// Once a stream has reached the end, subsequent calls to read or |
|||
/// `skip` will always report end-of-stream rather than producing more |
|||
/// data. |
|||
/// |
|||
/// This function returns the number of bytes skipped, along with a |
|||
/// `stream-status` indicating whether the end of the stream was |
|||
/// reached. The returned value will be at most `len`; it may be less. |
|||
skip: func( |
|||
this: input-stream, |
|||
/// The maximum number of bytes to skip. |
|||
len: u64, |
|||
) -> result<tuple<u64, stream-status>> |
|||
|
|||
/// Skip bytes from a stream, after blocking until at least one byte |
|||
/// can be skipped. Except for blocking behavior, identical to `skip`. |
|||
blocking-skip: func( |
|||
this: input-stream, |
|||
/// The maximum number of bytes to skip. |
|||
len: u64, |
|||
) -> result<tuple<u64, stream-status>> |
|||
|
|||
/// Create a `pollable` which will resolve once either the specified stream |
|||
/// has bytes available to read or the other end of the stream has been |
|||
/// closed. |
|||
/// The created `pollable` is a child resource of the `input-stream`. |
|||
/// Implementations may trap if the `input-stream` is dropped before |
|||
/// all derived `pollable`s created with this function are dropped. |
|||
subscribe-to-input-stream: func(this: input-stream) -> pollable |
|||
|
|||
/// Dispose of the specified `input-stream`, after which it may no longer |
|||
/// be used. |
|||
/// Implementations may trap if this `input-stream` is dropped while child |
|||
/// `pollable` resources are still alive. |
|||
/// After this `input-stream` is dropped, implementations may report any |
|||
/// corresponding `output-stream` has `stream-state.closed`. |
|||
drop-input-stream: func(this: input-stream) |
|||
|
|||
/// An output bytestream. In the future, this will be replaced by handle |
|||
/// types. |
|||
/// |
|||
/// `output-stream`s are *non-blocking* to the extent practical on |
|||
/// underlying platforms. Except where specified otherwise, I/O operations also |
|||
/// always return promptly, after the number of bytes that can be written |
|||
/// promptly, which could even be zero. To wait for the stream to be ready to |
|||
/// accept data, the `subscribe-to-output-stream` function to obtain a |
|||
/// `pollable` which can be polled for using `wasi:poll`. |
|||
/// |
|||
/// And at present, it is a `u32` instead of being an actual handle, until |
|||
/// the wit-bindgen implementation of handles and resources is ready. |
|||
/// |
|||
/// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). |
|||
type output-stream = u32 |
|||
|
|||
/// Perform a non-blocking write of bytes to a stream. |
|||
/// |
|||
/// This function returns a `u64` and a `stream-status`. The `u64` indicates |
|||
/// the number of bytes from `buf` that were written, which may be less than |
|||
/// the length of `buf`. The `stream-status` indicates if further writes to |
|||
/// the stream are expected to be read. |
|||
/// |
|||
/// When the returned `stream-status` is `open`, the `u64` return value may |
|||
/// be less than the length of `buf`. This indicates that no more bytes may |
|||
/// be written to the stream promptly. In that case the |
|||
/// `subscribe-to-output-stream` pollable will indicate when additional bytes |
|||
/// may be promptly written. |
|||
/// |
|||
/// Writing an empty list must return a non-error result with `0` for the |
|||
/// `u64` return value, and the current `stream-status`. |
|||
write: func( |
|||
this: output-stream, |
|||
/// Data to write |
|||
buf: list<u8> |
|||
) -> result<tuple<u64, stream-status>> |
|||
|
|||
/// Blocking write of bytes to a stream. |
|||
/// |
|||
/// This is similar to `write`, except that it blocks until at least one |
|||
/// byte can be written. |
|||
blocking-write: func( |
|||
this: output-stream, |
|||
/// Data to write |
|||
buf: list<u8> |
|||
) -> result<tuple<u64, stream-status>> |
|||
|
|||
/// Write multiple zero-bytes to a stream. |
|||
/// |
|||
/// This function returns a `u64` indicating the number of zero-bytes |
|||
/// that were written; it may be less than `len`. Equivelant to a call to |
|||
/// `write` with a list of zeroes of the given length. |
|||
write-zeroes: func( |
|||
this: output-stream, |
|||
/// The number of zero-bytes to write |
|||
len: u64 |
|||
) -> result<tuple<u64, stream-status>> |
|||
|
|||
/// Write multiple zero bytes to a stream, with blocking. |
|||
/// |
|||
/// This is similar to `write-zeroes`, except that it blocks until at least |
|||
/// one byte can be written. Equivelant to a call to `blocking-write` with |
|||
/// a list of zeroes of the given length. |
|||
blocking-write-zeroes: func( |
|||
this: output-stream, |
|||
/// The number of zero bytes to write |
|||
len: u64 |
|||
) -> result<tuple<u64, stream-status>> |
|||
|
|||
/// Read from one stream and write to another. |
|||
/// |
|||
/// This function returns the number of bytes transferred; it may be less |
|||
/// than `len`. |
|||
/// |
|||
/// Unlike other I/O functions, this function blocks until all the data |
|||
/// read from the input stream has been written to the output stream. |
|||
splice: func( |
|||
this: output-stream, |
|||
/// The stream to read from |
|||
src: input-stream, |
|||
/// The number of bytes to splice |
|||
len: u64, |
|||
) -> result<tuple<u64, stream-status>> |
|||
|
|||
/// Read from one stream and write to another, with blocking. |
|||
/// |
|||
/// This is similar to `splice`, except that it blocks until at least |
|||
/// one byte can be read. |
|||
blocking-splice: func( |
|||
this: output-stream, |
|||
/// The stream to read from |
|||
src: input-stream, |
|||
/// The number of bytes to splice |
|||
len: u64, |
|||
) -> result<tuple<u64, stream-status>> |
|||
|
|||
/// Forward the entire contents of an input stream to an output stream. |
|||
/// |
|||
/// This function repeatedly reads from the input stream and writes |
|||
/// the data to the output stream, until the end of the input stream |
|||
/// is reached, or an error is encountered. |
|||
/// |
|||
/// Unlike other I/O functions, this function blocks until the end |
|||
/// of the input stream is seen and all the data has been written to |
|||
/// the output stream. |
|||
/// |
|||
/// This function returns the number of bytes transferred, and the status of |
|||
/// the output stream. |
|||
forward: func( |
|||
this: output-stream, |
|||
/// The stream to read from |
|||
src: input-stream |
|||
) -> result<tuple<u64, stream-status>> |
|||
|
|||
/// Create a `pollable` which will resolve once either the specified stream |
|||
/// is ready to accept bytes or the `stream-state` has become closed. |
|||
/// |
|||
/// Once the stream-state is closed, this pollable is always ready |
|||
/// immediately. |
|||
/// |
|||
/// The created `pollable` is a child resource of the `output-stream`. |
|||
/// Implementations may trap if the `output-stream` is dropped before |
|||
/// all derived `pollable`s created with this function are dropped. |
|||
subscribe-to-output-stream: func(this: output-stream) -> pollable |
|||
|
|||
/// Dispose of the specified `output-stream`, after which it may no longer |
|||
/// be used. |
|||
/// Implementations may trap if this `output-stream` is dropped while |
|||
/// child `pollable` resources are still alive. |
|||
/// After this `output-stream` is dropped, implementations may report any |
|||
/// corresponding `input-stream` has `stream-state.closed`. |
|||
drop-output-stream: func(this: output-stream) |
|||
} |
@ -0,0 +1,39 @@ |
|||
package wasi:poll |
|||
|
|||
/// A poll API intended to let users wait for I/O events on multiple handles |
|||
/// at once. |
|||
interface poll { |
|||
/// A "pollable" handle. |
|||
/// |
|||
/// This is conceptually represents a `stream<_, _>`, or in other words, |
|||
/// a stream that one can wait on, repeatedly, but which does not itself |
|||
/// produce any data. It's temporary scaffolding until component-model's |
|||
/// async features are ready. |
|||
/// |
|||
/// And at present, it is a `u32` instead of being an actual handle, until |
|||
/// the wit-bindgen implementation of handles and resources is ready. |
|||
/// |
|||
/// `pollable` lifetimes are not automatically managed. Users must ensure |
|||
/// that they do not outlive the resource they reference. |
|||
/// |
|||
/// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). |
|||
type pollable = u32 |
|||
|
|||
/// Dispose of the specified `pollable`, after which it may no longer |
|||
/// be used. |
|||
drop-pollable: func(this: pollable) |
|||
|
|||
/// Poll for completion on a set of pollables. |
|||
/// |
|||
/// The "oneoff" in the name refers to the fact that this function must do a |
|||
/// linear scan through the entire list of subscriptions, which may be |
|||
/// inefficient if the number is large and the same subscriptions are used |
|||
/// many times. In the future, this is expected to be obsoleted by the |
|||
/// component model async proposal, which will include a scalable waiting |
|||
/// facility. |
|||
/// |
|||
/// The result list<bool> is the same length as the argument |
|||
/// list<pollable>, and indicates the readiness of each corresponding |
|||
/// element in that / list, with true indicating ready. |
|||
poll-oneoff: func(in: list<pollable>) -> list<bool> |
|||
} |
@ -0,0 +1,24 @@ |
|||
/// The insecure-seed interface for seeding hash-map DoS resistance. |
|||
/// |
|||
/// It is intended to be portable at least between Unix-family platforms and |
|||
/// Windows. |
|||
interface insecure-seed { |
|||
/// Return a 128-bit value that may contain a pseudo-random value. |
|||
/// |
|||
/// The returned value is not required to be computed from a CSPRNG, and may |
|||
/// even be entirely deterministic. Host implementations are encouraged to |
|||
/// provide pseudo-random values to any program exposed to |
|||
/// attacker-controlled content, to enable DoS protection built into many |
|||
/// languages' hash-map implementations. |
|||
/// |
|||
/// This function is intended to only be called once, by a source language |
|||
/// to initialize Denial Of Service (DoS) protection in its hash-map |
|||
/// implementation. |
|||
/// |
|||
/// # Expected future evolution |
|||
/// |
|||
/// This will likely be changed to a value import, to prevent it from being |
|||
/// called multiple times and potentially used for purposes other than DoS |
|||
/// protection. |
|||
insecure-seed: func() -> tuple<u64, u64> |
|||
} |
@ -0,0 +1,21 @@ |
|||
/// The insecure interface for insecure pseudo-random numbers. |
|||
/// |
|||
/// It is intended to be portable at least between Unix-family platforms and |
|||
/// Windows. |
|||
interface insecure { |
|||
/// Return `len` insecure pseudo-random bytes. |
|||
/// |
|||
/// This function is not cryptographically secure. Do not use it for |
|||
/// anything related to security. |
|||
/// |
|||
/// There are no requirements on the values of the returned bytes, however |
|||
/// implementations are encouraged to return evenly distributed values with |
|||
/// a long period. |
|||
get-insecure-random-bytes: func(len: u64) -> list<u8> |
|||
|
|||
/// Return an insecure pseudo-random `u64` value. |
|||
/// |
|||
/// This function returns the same type of pseudo-random data as |
|||
/// `get-insecure-random-bytes`, represented as a `u64`. |
|||
get-insecure-random-u64: func() -> u64 |
|||
} |
@ -0,0 +1,25 @@ |
|||
package wasi:random |
|||
|
|||
/// WASI Random is a random data API. |
|||
/// |
|||
/// It is intended to be portable at least between Unix-family platforms and |
|||
/// Windows. |
|||
interface random { |
|||
/// Return `len` cryptographically-secure pseudo-random bytes. |
|||
/// |
|||
/// This function must produce data from an adequately seeded |
|||
/// cryptographically-secure pseudo-random number generator (CSPRNG), so it |
|||
/// must not block, from the perspective of the calling program, and the |
|||
/// returned data is always unpredictable. |
|||
/// |
|||
/// This function must always return fresh pseudo-random data. Deterministic |
|||
/// environments must omit this function, rather than implementing it with |
|||
/// deterministic data. |
|||
get-random-bytes: func(len: u64) -> list<u8> |
|||
|
|||
/// Return a cryptographically-secure pseudo-random `u64` value. |
|||
/// |
|||
/// This function returns the same type of pseudo-random data as |
|||
/// `get-random-bytes`, represented as a `u64`. |
|||
get-random-u64: func() -> u64 |
|||
} |
@ -0,0 +1,9 @@ |
|||
|
|||
/// This interface provides a value-export of the default network handle.. |
|||
interface instance-network { |
|||
use network.{network} |
|||
|
|||
/// Get a handle to the default network. |
|||
instance-network: func() -> network |
|||
|
|||
} |
@ -0,0 +1,69 @@ |
|||
|
|||
interface ip-name-lookup { |
|||
use wasi:poll/poll.{pollable} |
|||
use network.{network, error-code, ip-address, ip-address-family} |
|||
|
|||
|
|||
/// Resolve an internet host name to a list of IP addresses. |
|||
/// |
|||
/// See the wasi-socket proposal README.md for a comparison with getaddrinfo. |
|||
/// |
|||
/// # Parameters |
|||
/// - `name`: The name to look up. IP addresses are not allowed. Unicode domain names are automatically converted |
|||
/// to ASCII using IDNA encoding. |
|||
/// - `address-family`: If provided, limit the results to addresses of this specific address family. |
|||
/// - `include-unavailable`: When set to true, this function will also return addresses of which the runtime |
|||
/// thinks (or knows) can't be connected to at the moment. For example, this will return IPv6 addresses on |
|||
/// systems without an active IPv6 interface. Notes: |
|||
/// - Even when no public IPv6 interfaces are present or active, names like "localhost" can still resolve to an IPv6 address. |
|||
/// - Whatever is "available" or "unavailable" is volatile and can change everytime a network cable is unplugged. |
|||
/// |
|||
/// This function never blocks. It either immediately fails or immediately returns successfully with a `resolve-address-stream` |
|||
/// that can be used to (asynchronously) fetch the results. |
|||
/// |
|||
/// At the moment, the stream never completes successfully with 0 items. Ie. the first call |
|||
/// to `resolve-next-address` never returns `ok(none)`. This may change in the future. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `invalid-name`: `name` is a syntactically invalid domain name. |
|||
/// - `invalid-name`: `name` is an IP address. |
|||
/// - `address-family-not-supported`: The specified `address-family` is not supported. (EAI_FAMILY) |
|||
/// |
|||
/// # References: |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html> |
|||
/// - <https://man7.org/linux/man-pages/man3/getaddrinfo.3.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=getaddrinfo&sektion=3> |
|||
resolve-addresses: func(network: network, name: string, address-family: option<ip-address-family>, include-unavailable: bool) -> result<resolve-address-stream, error-code> |
|||
|
|||
|
|||
|
|||
type resolve-address-stream = u32 |
|||
|
|||
/// Returns the next address from the resolver. |
|||
/// |
|||
/// This function should be called multiple times. On each call, it will |
|||
/// return the next address in connection order preference. If all |
|||
/// addresses have been exhausted, this function returns `none`. |
|||
/// After which, you should release the stream with `drop-resolve-address-stream`. |
|||
/// |
|||
/// This function never returns IPv4-mapped IPv6 addresses. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) |
|||
/// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) |
|||
/// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) |
|||
/// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) |
|||
resolve-next-address: func(this: resolve-address-stream) -> result<option<ip-address>, error-code> |
|||
|
|||
/// Dispose of the specified `resolve-address-stream`, after which it may no longer be used. |
|||
/// |
|||
/// Note: this function is scheduled to be removed when Resources are natively supported in Wit. |
|||
drop-resolve-address-stream: func(this: resolve-address-stream) |
|||
|
|||
/// Create a `pollable` which will resolve once the stream is ready for I/O. |
|||
/// |
|||
/// Note: this function is here for WASI Preview2 only. |
|||
/// It's planned to be removed when `future` is natively supported in Preview3. |
|||
subscribe: func(this: resolve-address-stream) -> pollable |
|||
} |
@ -0,0 +1,187 @@ |
|||
package wasi:sockets |
|||
|
|||
interface network { |
|||
/// An opaque resource that represents access to (a subset of) the network. |
|||
/// This enables context-based security for networking. |
|||
/// There is no need for this to map 1:1 to a physical network interface. |
|||
/// |
|||
/// FYI, In the future this will be replaced by handle types. |
|||
type network = u32 |
|||
|
|||
/// Dispose of the specified `network`, after which it may no longer be used. |
|||
/// |
|||
/// Note: this function is scheduled to be removed when Resources are natively supported in Wit. |
|||
drop-network: func(this: network) |
|||
|
|||
|
|||
/// Error codes. |
|||
/// |
|||
/// In theory, every API can return any error code. |
|||
/// In practice, API's typically only return the errors documented per API |
|||
/// combined with a couple of errors that are always possible: |
|||
/// - `unknown` |
|||
/// - `access-denied` |
|||
/// - `not-supported` |
|||
/// - `out-of-memory` |
|||
/// |
|||
/// See each individual API for what the POSIX equivalents are. They sometimes differ per API. |
|||
enum error-code { |
|||
// ### GENERAL ERRORS ### |
|||
|
|||
/// Unknown error |
|||
unknown, |
|||
|
|||
/// Access denied. |
|||
/// |
|||
/// POSIX equivalent: EACCES, EPERM |
|||
access-denied, |
|||
|
|||
/// The operation is not supported. |
|||
/// |
|||
/// POSIX equivalent: EOPNOTSUPP |
|||
not-supported, |
|||
|
|||
/// Not enough memory to complete the operation. |
|||
/// |
|||
/// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY |
|||
out-of-memory, |
|||
|
|||
/// The operation timed out before it could finish completely. |
|||
timeout, |
|||
|
|||
/// This operation is incompatible with another asynchronous operation that is already in progress. |
|||
concurrency-conflict, |
|||
|
|||
/// Trying to finish an asynchronous operation that: |
|||
/// - has not been started yet, or: |
|||
/// - was already finished by a previous `finish-*` call. |
|||
/// |
|||
/// Note: this is scheduled to be removed when `future`s are natively supported. |
|||
not-in-progress, |
|||
|
|||
/// The operation has been aborted because it could not be completed immediately. |
|||
/// |
|||
/// Note: this is scheduled to be removed when `future`s are natively supported. |
|||
would-block, |
|||
|
|||
|
|||
// ### IP ERRORS ### |
|||
|
|||
/// The specified address-family is not supported. |
|||
address-family-not-supported, |
|||
|
|||
/// An IPv4 address was passed to an IPv6 resource, or vice versa. |
|||
address-family-mismatch, |
|||
|
|||
/// The socket address is not a valid remote address. E.g. the IP address is set to INADDR_ANY, or the port is set to 0. |
|||
invalid-remote-address, |
|||
|
|||
/// The operation is only supported on IPv4 resources. |
|||
ipv4-only-operation, |
|||
|
|||
/// The operation is only supported on IPv6 resources. |
|||
ipv6-only-operation, |
|||
|
|||
|
|||
|
|||
// ### TCP & UDP SOCKET ERRORS ### |
|||
|
|||
/// A new socket resource could not be created because of a system limit. |
|||
new-socket-limit, |
|||
|
|||
/// The socket is already attached to another network. |
|||
already-attached, |
|||
|
|||
/// The socket is already bound. |
|||
already-bound, |
|||
|
|||
/// The socket is already in the Connection state. |
|||
already-connected, |
|||
|
|||
/// The socket is not bound to any local address. |
|||
not-bound, |
|||
|
|||
/// The socket is not in the Connection state. |
|||
not-connected, |
|||
|
|||
/// A bind operation failed because the provided address is not an address that the `network` can bind to. |
|||
address-not-bindable, |
|||
|
|||
/// A bind operation failed because the provided address is already in use. |
|||
address-in-use, |
|||
|
|||
/// A bind operation failed because there are no ephemeral ports available. |
|||
ephemeral-ports-exhausted, |
|||
|
|||
/// The remote address is not reachable |
|||
remote-unreachable, |
|||
|
|||
|
|||
// ### TCP SOCKET ERRORS ### |
|||
|
|||
/// The socket is already in the Listener state. |
|||
already-listening, |
|||
|
|||
/// The socket is already in the Listener state. |
|||
not-listening, |
|||
|
|||
/// The connection was forcefully rejected |
|||
connection-refused, |
|||
|
|||
/// The connection was reset. |
|||
connection-reset, |
|||
|
|||
|
|||
// ### UDP SOCKET ERRORS ### |
|||
datagram-too-large, |
|||
|
|||
|
|||
// ### NAME LOOKUP ERRORS ### |
|||
|
|||
/// The provided name is a syntactically invalid domain name. |
|||
invalid-name, |
|||
|
|||
/// Name does not exist or has no suitable associated IP addresses. |
|||
name-unresolvable, |
|||
|
|||
/// A temporary failure in name resolution occurred. |
|||
temporary-resolver-failure, |
|||
|
|||
/// A permanent failure in name resolution occurred. |
|||
permanent-resolver-failure, |
|||
} |
|||
|
|||
enum ip-address-family { |
|||
/// Similar to `AF_INET` in POSIX. |
|||
ipv4, |
|||
|
|||
/// Similar to `AF_INET6` in POSIX. |
|||
ipv6, |
|||
} |
|||
|
|||
type ipv4-address = tuple<u8, u8, u8, u8> |
|||
type ipv6-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16> |
|||
|
|||
variant ip-address { |
|||
ipv4(ipv4-address), |
|||
ipv6(ipv6-address), |
|||
} |
|||
|
|||
record ipv4-socket-address { |
|||
port: u16, // sin_port |
|||
address: ipv4-address, // sin_addr |
|||
} |
|||
|
|||
record ipv6-socket-address { |
|||
port: u16, // sin6_port |
|||
flow-info: u32, // sin6_flowinfo |
|||
address: ipv6-address, // sin6_addr |
|||
scope-id: u32, // sin6_scope_id |
|||
} |
|||
|
|||
variant ip-socket-address { |
|||
ipv4(ipv4-socket-address), |
|||
ipv6(ipv6-socket-address), |
|||
} |
|||
|
|||
} |
@ -0,0 +1,27 @@ |
|||
|
|||
interface tcp-create-socket { |
|||
use network.{network, error-code, ip-address-family} |
|||
use tcp.{tcp-socket} |
|||
|
|||
/// Create a new TCP socket. |
|||
/// |
|||
/// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. |
|||
/// |
|||
/// This function does not require a network capability handle. This is considered to be safe because |
|||
/// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`listen`/`connect` |
|||
/// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. |
|||
/// |
|||
/// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `not-supported`: The host does not support TCP sockets. (EOPNOTSUPP) |
|||
/// - `address-family-not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) |
|||
/// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/socket.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2> |
|||
create-tcp-socket: func(address-family: ip-address-family) -> result<tcp-socket, error-code> |
|||
} |
@ -0,0 +1,255 @@ |
|||
|
|||
interface tcp { |
|||
use wasi:io/streams.{input-stream, output-stream} |
|||
use wasi:poll/poll.{pollable} |
|||
use network.{network, error-code, ip-socket-address, ip-address-family} |
|||
|
|||
/// A TCP socket handle. |
|||
type tcp-socket = u32 |
|||
|
|||
|
|||
enum shutdown-type { |
|||
/// Similar to `SHUT_RD` in POSIX. |
|||
receive, |
|||
|
|||
/// Similar to `SHUT_WR` in POSIX. |
|||
send, |
|||
|
|||
/// Similar to `SHUT_RDWR` in POSIX. |
|||
both, |
|||
} |
|||
|
|||
|
|||
/// Bind the socket to a specific network on the provided IP address and port. |
|||
/// |
|||
/// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which |
|||
/// network interface(s) to bind to. |
|||
/// If the TCP/UDP port is zero, the socket will be bound to a random free port. |
|||
/// |
|||
/// When a socket is not explicitly bound, the first invocation to a listen or connect operation will |
|||
/// implicitly bind the socket. |
|||
/// |
|||
/// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. |
|||
/// |
|||
/// # Typical `start` errors |
|||
/// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) |
|||
/// - `already-bound`: The socket is already bound. (EINVAL) |
|||
/// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) |
|||
/// |
|||
/// # Typical `finish` errors |
|||
/// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) |
|||
/// - `address-in-use`: Address is already in use. (EADDRINUSE) |
|||
/// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) |
|||
/// - `not-in-progress`: A `bind` operation is not in progress. |
|||
/// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/bind.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html> |
|||
start-bind: func(this: tcp-socket, network: network, local-address: ip-socket-address) -> result<_, error-code> |
|||
finish-bind: func(this: tcp-socket) -> result<_, error-code> |
|||
|
|||
/// Connect to a remote endpoint. |
|||
/// |
|||
/// On success: |
|||
/// - the socket is transitioned into the Connection state |
|||
/// - a pair of streams is returned that can be used to read & write to the connection |
|||
/// |
|||
/// # Typical `start` errors |
|||
/// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) |
|||
/// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) |
|||
/// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) |
|||
/// - `already-attached`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. |
|||
/// - `already-connected`: The socket is already in the Connection state. (EISCONN) |
|||
/// - `already-listening`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) |
|||
/// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EALREADY) |
|||
/// |
|||
/// # Typical `finish` errors |
|||
/// - `timeout`: Connection timed out. (ETIMEDOUT) |
|||
/// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) |
|||
/// - `connection-reset`: The connection was reset. (ECONNRESET) |
|||
/// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) |
|||
/// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) |
|||
/// - `not-in-progress`: A `connect` operation is not in progress. |
|||
/// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/connect.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?connect> |
|||
start-connect: func(this: tcp-socket, network: network, remote-address: ip-socket-address) -> result<_, error-code> |
|||
finish-connect: func(this: tcp-socket) -> result<tuple<input-stream, output-stream>, error-code> |
|||
|
|||
/// Start listening for new connections. |
|||
/// |
|||
/// Transitions the socket into the Listener state. |
|||
/// |
|||
/// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. |
|||
/// |
|||
/// # Typical `start` errors |
|||
/// - `already-attached`: The socket is already attached to a different network. The `network` passed to `listen` must be identical to the one passed to `bind`. |
|||
/// - `already-connected`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) |
|||
/// - `already-listening`: The socket is already in the Listener state. |
|||
/// - `concurrency-conflict`: Another `bind`, `connect` or `listen` operation is already in progress. (EINVAL on BSD) |
|||
/// |
|||
/// # Typical `finish` errors |
|||
/// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) |
|||
/// - `not-in-progress`: A `listen` operation is not in progress. |
|||
/// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/listen.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=listen&sektion=2> |
|||
start-listen: func(this: tcp-socket, network: network) -> result<_, error-code> |
|||
finish-listen: func(this: tcp-socket) -> result<_, error-code> |
|||
|
|||
/// Accept a new client socket. |
|||
/// |
|||
/// The returned socket is bound and in the Connection state. |
|||
/// |
|||
/// On success, this function returns the newly accepted client socket along with |
|||
/// a pair of streams that can be used to read & write to the connection. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `not-listening`: Socket is not in the Listener state. (EINVAL) |
|||
/// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) |
|||
/// |
|||
/// Host implementations must skip over transient errors returned by the native accept syscall. |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/accept.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=accept&sektion=2> |
|||
accept: func(this: tcp-socket) -> result<tuple<tcp-socket, input-stream, output-stream>, error-code> |
|||
|
|||
/// Get the bound local address. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `not-bound`: The socket is not bound to any local address. |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/getsockname.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?getsockname> |
|||
local-address: func(this: tcp-socket) -> result<ip-socket-address, error-code> |
|||
|
|||
/// Get the bound remote address. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/getpeername.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1> |
|||
remote-address: func(this: tcp-socket) -> result<ip-socket-address, error-code> |
|||
|
|||
/// Whether this is a IPv4 or IPv6 socket. |
|||
/// |
|||
/// Equivalent to the SO_DOMAIN socket option. |
|||
address-family: func(this: tcp-socket) -> ip-address-family |
|||
|
|||
/// Whether IPv4 compatibility (dual-stack) mode is disabled or not. |
|||
/// |
|||
/// Equivalent to the IPV6_V6ONLY socket option. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. |
|||
/// - `already-bound`: (set) The socket is already bound. |
|||
/// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) |
|||
/// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) |
|||
ipv6-only: func(this: tcp-socket) -> result<bool, error-code> |
|||
set-ipv6-only: func(this: tcp-socket, value: bool) -> result<_, error-code> |
|||
|
|||
/// Hints the desired listen queue size. Implementations are free to ignore this. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `already-connected`: (set) The socket is already in the Connection state. |
|||
/// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) |
|||
set-listen-backlog-size: func(this: tcp-socket, value: u64) -> result<_, error-code> |
|||
|
|||
/// Equivalent to the SO_KEEPALIVE socket option. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) |
|||
keep-alive: func(this: tcp-socket) -> result<bool, error-code> |
|||
set-keep-alive: func(this: tcp-socket, value: bool) -> result<_, error-code> |
|||
|
|||
/// Equivalent to the TCP_NODELAY socket option. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) |
|||
no-delay: func(this: tcp-socket) -> result<bool, error-code> |
|||
set-no-delay: func(this: tcp-socket, value: bool) -> result<_, error-code> |
|||
|
|||
/// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `already-connected`: (set) The socket is already in the Connection state. |
|||
/// - `already-listening`: (set) The socket is already in the Listener state. |
|||
/// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) |
|||
unicast-hop-limit: func(this: tcp-socket) -> result<u8, error-code> |
|||
set-unicast-hop-limit: func(this: tcp-socket, value: u8) -> result<_, error-code> |
|||
|
|||
/// The kernel buffer space reserved for sends/receives on this socket. |
|||
/// |
|||
/// Note #1: an implementation may choose to cap or round the buffer size when setting the value. |
|||
/// In other words, after setting a value, reading the same setting back may return a different value. |
|||
/// |
|||
/// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of |
|||
/// actual data to be sent/received by the application, because the kernel might also use the buffer space |
|||
/// for internal metadata structures. |
|||
/// |
|||
/// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `already-connected`: (set) The socket is already in the Connection state. |
|||
/// - `already-listening`: (set) The socket is already in the Listener state. |
|||
/// - `concurrency-conflict`: (set) A `bind`, `connect` or `listen` operation is already in progress. (EALREADY) |
|||
receive-buffer-size: func(this: tcp-socket) -> result<u64, error-code> |
|||
set-receive-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error-code> |
|||
send-buffer-size: func(this: tcp-socket) -> result<u64, error-code> |
|||
set-send-buffer-size: func(this: tcp-socket, value: u64) -> result<_, error-code> |
|||
|
|||
/// Create a `pollable` which will resolve once the socket is ready for I/O. |
|||
/// |
|||
/// Note: this function is here for WASI Preview2 only. |
|||
/// It's planned to be removed when `future` is natively supported in Preview3. |
|||
subscribe: func(this: tcp-socket) -> pollable |
|||
|
|||
/// Initiate a graceful shutdown. |
|||
/// |
|||
/// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read |
|||
/// operations on the `input-stream` associated with this socket will return an End Of Stream indication. |
|||
/// Any data still in the receive queue at time of calling `shutdown` will be discarded. |
|||
/// - send: the socket is not expecting to send any more data to the peer. All subsequent write |
|||
/// operations on the `output-stream` associated with this socket will return an error. |
|||
/// - both: same effect as receive & send combined. |
|||
/// |
|||
/// The shutdown function does not close (drop) the socket. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `not-connected`: The socket is not in the Connection state. (ENOTCONN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/shutdown.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=shutdown&sektion=2> |
|||
shutdown: func(this: tcp-socket, shutdown-type: shutdown-type) -> result<_, error-code> |
|||
|
|||
/// Dispose of the specified `tcp-socket`, after which it may no longer be used. |
|||
/// |
|||
/// Similar to the POSIX `close` function. |
|||
/// |
|||
/// Note: this function is scheduled to be removed when Resources are natively supported in Wit. |
|||
drop-tcp-socket: func(this: tcp-socket) |
|||
} |
@ -0,0 +1,27 @@ |
|||
|
|||
interface udp-create-socket { |
|||
use network.{network, error-code, ip-address-family} |
|||
use udp.{udp-socket} |
|||
|
|||
/// Create a new UDP socket. |
|||
/// |
|||
/// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. |
|||
/// |
|||
/// This function does not require a network capability handle. This is considered to be safe because |
|||
/// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` is called, |
|||
/// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. |
|||
/// |
|||
/// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `not-supported`: The host does not support UDP sockets. (EOPNOTSUPP) |
|||
/// - `address-family-not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) |
|||
/// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) |
|||
/// |
|||
/// # References: |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/socket.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2> |
|||
create-udp-socket: func(address-family: ip-address-family) -> result<udp-socket, error-code> |
|||
} |
@ -0,0 +1,211 @@ |
|||
|
|||
interface udp { |
|||
use wasi:poll/poll.{pollable} |
|||
use network.{network, error-code, ip-socket-address, ip-address-family} |
|||
|
|||
|
|||
/// A UDP socket handle. |
|||
type udp-socket = u32 |
|||
|
|||
|
|||
record datagram { |
|||
data: list<u8>, // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. |
|||
remote-address: ip-socket-address, |
|||
|
|||
/// Possible future additions: |
|||
/// local-address: ip-socket-address, // IP_PKTINFO / IP_RECVDSTADDR / IPV6_PKTINFO |
|||
/// local-interface: u32, // IP_PKTINFO / IP_RECVIF |
|||
/// ttl: u8, // IP_RECVTTL |
|||
/// dscp: u6, // IP_RECVTOS |
|||
/// ecn: u2, // IP_RECVTOS |
|||
} |
|||
|
|||
|
|||
|
|||
/// Bind the socket to a specific network on the provided IP address and port. |
|||
/// |
|||
/// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which |
|||
/// network interface(s) to bind to. |
|||
/// If the TCP/UDP port is zero, the socket will be bound to a random free port. |
|||
/// |
|||
/// When a socket is not explicitly bound, the first invocation to connect will implicitly bind the socket. |
|||
/// |
|||
/// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. |
|||
/// |
|||
/// # Typical `start` errors |
|||
/// - `address-family-mismatch`: The `local-address` has the wrong address family. (EINVAL) |
|||
/// - `already-bound`: The socket is already bound. (EINVAL) |
|||
/// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) |
|||
/// |
|||
/// # Typical `finish` errors |
|||
/// - `ephemeral-ports-exhausted`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) |
|||
/// - `address-in-use`: Address is already in use. (EADDRINUSE) |
|||
/// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) |
|||
/// - `not-in-progress`: A `bind` operation is not in progress. |
|||
/// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/bind.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html> |
|||
start-bind: func(this: udp-socket, network: network, local-address: ip-socket-address) -> result<_, error-code> |
|||
finish-bind: func(this: udp-socket) -> result<_, error-code> |
|||
|
|||
/// Set the destination address. |
|||
/// |
|||
/// The local-address is updated based on the best network path to `remote-address`. |
|||
/// |
|||
/// When a destination address is set: |
|||
/// - all receive operations will only return datagrams sent from the provided `remote-address`. |
|||
/// - the `send` function can only be used to send to this destination. |
|||
/// |
|||
/// Note that this function does not generate any network traffic and the peer is not aware of this "connection". |
|||
/// |
|||
/// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. |
|||
/// |
|||
/// # Typical `start` errors |
|||
/// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) |
|||
/// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) |
|||
/// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) |
|||
/// - `already-attached`: The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. |
|||
/// - `concurrency-conflict`: Another `bind` or `connect` operation is already in progress. (EALREADY) |
|||
/// |
|||
/// # Typical `finish` errors |
|||
/// - `ephemeral-ports-exhausted`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) |
|||
/// - `not-in-progress`: A `connect` operation is not in progress. |
|||
/// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/connect.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?connect> |
|||
start-connect: func(this: udp-socket, network: network, remote-address: ip-socket-address) -> result<_, error-code> |
|||
finish-connect: func(this: udp-socket) -> result<_, error-code> |
|||
|
|||
/// Receive a message. |
|||
/// |
|||
/// Returns: |
|||
/// - The sender address of the datagram |
|||
/// - The number of bytes read. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `not-bound`: The socket is not bound to any local address. (EINVAL) |
|||
/// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) |
|||
/// - `would-block`: There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html> |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/recv.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom> |
|||
/// - <https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms741687(v=vs.85)> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=recv&sektion=2> |
|||
receive: func(this: udp-socket) -> result<datagram, error-code> |
|||
|
|||
/// Send a message to a specific destination address. |
|||
/// |
|||
/// The remote address option is required. To send a message to the "connected" peer, |
|||
/// call `remote-address` to get their address. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `address-family-mismatch`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) |
|||
/// - `invalid-remote-address`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) |
|||
/// - `invalid-remote-address`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) |
|||
/// - `already-connected`: The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN) |
|||
/// - `not-bound`: The socket is not bound to any local address. Unlike POSIX, this function does not perform an implicit bind. |
|||
/// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) |
|||
/// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) |
|||
/// - `would-block`: The send buffer is currently full. (EWOULDBLOCK, EAGAIN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html> |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/send.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasendmsg> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=send&sektion=2> |
|||
send: func(this: udp-socket, datagram: datagram) -> result<_, error-code> |
|||
|
|||
/// Get the current bound address. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `not-bound`: The socket is not bound to any local address. |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/getsockname.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?getsockname> |
|||
local-address: func(this: udp-socket) -> result<ip-socket-address, error-code> |
|||
|
|||
/// Get the address set with `connect`. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `not-connected`: The socket is not connected to a remote address. (ENOTCONN) |
|||
/// |
|||
/// # References |
|||
/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html> |
|||
/// - <https://man7.org/linux/man-pages/man2/getpeername.2.html> |
|||
/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername> |
|||
/// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1> |
|||
remote-address: func(this: udp-socket) -> result<ip-socket-address, error-code> |
|||
|
|||
/// Whether this is a IPv4 or IPv6 socket. |
|||
/// |
|||
/// Equivalent to the SO_DOMAIN socket option. |
|||
address-family: func(this: udp-socket) -> ip-address-family |
|||
|
|||
/// Whether IPv4 compatibility (dual-stack) mode is disabled or not. |
|||
/// |
|||
/// Equivalent to the IPV6_V6ONLY socket option. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `ipv6-only-operation`: (get/set) `this` socket is an IPv4 socket. |
|||
/// - `already-bound`: (set) The socket is already bound. |
|||
/// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) |
|||
/// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) |
|||
ipv6-only: func(this: udp-socket) -> result<bool, error-code> |
|||
set-ipv6-only: func(this: udp-socket, value: bool) -> result<_, error-code> |
|||
|
|||
/// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) |
|||
unicast-hop-limit: func(this: udp-socket) -> result<u8, error-code> |
|||
set-unicast-hop-limit: func(this: udp-socket, value: u8) -> result<_, error-code> |
|||
|
|||
/// The kernel buffer space reserved for sends/receives on this socket. |
|||
/// |
|||
/// Note #1: an implementation may choose to cap or round the buffer size when setting the value. |
|||
/// In other words, after setting a value, reading the same setting back may return a different value. |
|||
/// |
|||
/// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of |
|||
/// actual data to be sent/received by the application, because the kernel might also use the buffer space |
|||
/// for internal metadata structures. |
|||
/// |
|||
/// Fails when this socket is in the Listening state. |
|||
/// |
|||
/// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. |
|||
/// |
|||
/// # Typical errors |
|||
/// - `concurrency-conflict`: (set) Another `bind` or `connect` operation is already in progress. (EALREADY) |
|||
receive-buffer-size: func(this: udp-socket) -> result<u64, error-code> |
|||
set-receive-buffer-size: func(this: udp-socket, value: u64) -> result<_, error-code> |
|||
send-buffer-size: func(this: udp-socket) -> result<u64, error-code> |
|||
set-send-buffer-size: func(this: udp-socket, value: u64) -> result<_, error-code> |
|||
|
|||
/// Create a `pollable` which will resolve once the socket is ready for I/O. |
|||
/// |
|||
/// Note: this function is here for WASI Preview2 only. |
|||
/// It's planned to be removed when `future` is natively supported in Preview3. |
|||
subscribe: func(this: udp-socket) -> pollable |
|||
|
|||
/// Dispose of the specified `udp-socket`, after which it may no longer be used. |
|||
/// |
|||
/// Note: this function is scheduled to be removed when Resources are natively supported in Wit. |
|||
drop-udp-socket: func(this: udp-socket) |
|||
} |
@ -0,0 +1,33 @@ |
|||
package wasmtime:wasi |
|||
|
|||
// All of the same imports available in the wasi:cli/command world, but no |
|||
// export required: |
|||
world preview1-adapter-reactor { |
|||
import wasi:clocks/wall-clock |
|||
import wasi:clocks/monotonic-clock |
|||
import wasi:clocks/timezone |
|||
import wasi:filesystem/types |
|||
import wasi:filesystem/preopens |
|||
import wasi:sockets/instance-network |
|||
import wasi:sockets/ip-name-lookup |
|||
import wasi:sockets/network |
|||
import wasi:sockets/tcp-create-socket |
|||
import wasi:sockets/tcp |
|||
import wasi:sockets/udp-create-socket |
|||
import wasi:sockets/udp |
|||
import wasi:random/random |
|||
import wasi:random/insecure |
|||
import wasi:random/insecure-seed |
|||
import wasi:poll/poll |
|||
import wasi:io/streams |
|||
import wasi:cli/environment |
|||
import wasi:cli/exit |
|||
import wasi:cli/stdin |
|||
import wasi:cli/stdout |
|||
import wasi:cli/stderr |
|||
import wasi:cli/terminal-input |
|||
import wasi:cli/terminal-output |
|||
import wasi:cli/terminal-stdin |
|||
import wasi:cli/terminal-stdout |
|||
import wasi:cli/terminal-stderr |
|||
} |
@ -0,0 +1,28 @@ |
|||
// only used as part of `test-programs` |
|||
world test-reactor { |
|||
|
|||
import wasi:cli/environment |
|||
import wasi:io/streams |
|||
import wasi:filesystem/types |
|||
import wasi:filesystem/preopens |
|||
import wasi:cli/exit |
|||
|
|||
export add-strings: func(s: list<string>) -> u32 |
|||
export get-strings: func() -> list<string> |
|||
|
|||
use wasi:io/streams.{output-stream} |
|||
|
|||
export write-strings-to: func(o: output-stream) -> result |
|||
|
|||
use wasi:filesystem/types.{descriptor-stat} |
|||
export pass-an-imported-record: func(d: descriptor-stat) -> string |
|||
} |
|||
|
|||
world test-command { |
|||
import wasi:poll/poll |
|||
import wasi:io/streams |
|||
import wasi:cli/environment |
|||
import wasi:cli/stdin |
|||
import wasi:cli/stdout |
|||
import wasi:cli/stderr |
|||
} |
@ -0,0 +1,39 @@ |
|||
// All of the same imports and exports available in the wasi:cli/command world |
|||
// with addition of HTTP proxy related imports: |
|||
world command-extended { |
|||
import wasi:clocks/wall-clock |
|||
import wasi:clocks/monotonic-clock |
|||
import wasi:clocks/timezone |
|||
import wasi:filesystem/types |
|||
import wasi:filesystem/preopens |
|||
import wasi:sockets/instance-network |
|||
import wasi:sockets/ip-name-lookup |
|||
import wasi:sockets/network |
|||
import wasi:sockets/tcp-create-socket |
|||
import wasi:sockets/tcp |
|||
import wasi:sockets/udp-create-socket |
|||
import wasi:sockets/udp |
|||
import wasi:random/random |
|||
import wasi:random/insecure |
|||
import wasi:random/insecure-seed |
|||
import wasi:poll/poll |
|||
import wasi:io/streams |
|||
import wasi:cli/environment |
|||
import wasi:cli/exit |
|||
import wasi:cli/stdin |
|||
import wasi:cli/stdout |
|||
import wasi:cli/stderr |
|||
import wasi:cli/terminal-input |
|||
import wasi:cli/terminal-output |
|||
import wasi:cli/terminal-stdin |
|||
import wasi:cli/terminal-stdout |
|||
import wasi:cli/terminal-stderr |
|||
|
|||
export wasi:cli/run |
|||
|
|||
// We should replace all others with `include self.command` |
|||
// as soon as the unioning of worlds is available: |
|||
// https://github.com/WebAssembly/component-model/issues/169 |
|||
import wasi:logging/logging |
|||
import wasi:http/outgoing-handler |
|||
} |
@ -0,0 +1,34 @@ |
|||
package wasi:http |
|||
|
|||
// The `wasi:http/proxy` world captures a widely-implementable intersection of |
|||
// hosts that includes HTTP forward and reverse proxies. Components targeting |
|||
// this world may concurrently stream in and out any number of incoming and |
|||
// outgoing HTTP requests. |
|||
world proxy { |
|||
// HTTP proxies have access to time and randomness. |
|||
import wasi:clocks/wall-clock |
|||
import wasi:clocks/monotonic-clock |
|||
import wasi:clocks/timezone |
|||
import wasi:random/random |
|||
|
|||
// Proxies have standard output and error streams which are expected to |
|||
// terminate in a developer-facing console provided by the host. |
|||
import wasi:cli/stdout |
|||
import wasi:cli/stderr |
|||
|
|||
// TODO: this is a temporary workaround until component tooling is able to |
|||
// gracefully handle the absence of stdin. Hosts must return an eof stream |
|||
// for this import, which is what wasi-libc + tooling will do automatically |
|||
// when this import is properly removed. |
|||
import wasi:cli/stdin |
|||
|
|||
// This is the default handler to use when user code simply wants to make an |
|||
// HTTP request (e.g., via `fetch()`). |
|||
import outgoing-handler |
|||
|
|||
// The host delivers incoming HTTP requests to a component by calling the |
|||
// `handle` function of this exported interface. A host may arbitrarily reuse |
|||
// or not reuse component instance when delivering incoming HTTP requests and |
|||
// thus a component must be able to handle 0..N calls to `handle`. |
|||
export incoming-handler |
|||
} |
@ -0,0 +1,37 @@ |
|||
package wasi:logging |
|||
|
|||
/// WASI Logging is a logging API intended to let users emit log messages with |
|||
/// simple priority levels and context values. |
|||
interface logging { |
|||
/// A log level, describing a kind of message. |
|||
enum level { |
|||
/// Describes messages about the values of variables and the flow of |
|||
/// control within a program. |
|||
trace, |
|||
|
|||
/// Describes messages likely to be of interest to someone debugging a |
|||
/// program. |
|||
debug, |
|||
|
|||
/// Describes messages likely to be of interest to someone monitoring a |
|||
/// program. |
|||
info, |
|||
|
|||
/// Describes messages indicating hazardous situations. |
|||
warn, |
|||
|
|||
/// Describes messages indicating serious errors. |
|||
error, |
|||
|
|||
/// Describes messages indicating fatal errors. |
|||
critical, |
|||
} |
|||
|
|||
/// Emit a log message. |
|||
/// |
|||
/// A log message has a `level` describing what kind of message is being |
|||
/// sent, a context, which is an uninterpreted string meant to help |
|||
/// consumers group similar messages, and a string containing the message |
|||
/// text. |
|||
log: func(level: level, context: string, message: string) |
|||
} |
Loading…
Reference in new issue