├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── cli ├── Cargo.toml └── src │ └── main.rs ├── guest ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs ├── host ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs ├── http ├── Cargo.toml └── src │ └── lib.rs ├── test ├── Cargo.toml ├── python-cases │ ├── echo │ │ └── app.py │ ├── hash-all │ │ └── app.py │ ├── round-trip │ │ └── app.py │ └── service │ │ └── app.py ├── rust-cases │ ├── Cargo.lock │ ├── Cargo.toml │ ├── echo │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── hash-all │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── middleware │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── round-trip │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── router │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── service │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── src │ └── lib.rs └── wasi-http-handler │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── lib.rs └── wit ├── deps ├── cli │ ├── command.wit │ ├── environment.wit │ ├── exit.wit │ ├── imports.wit │ ├── run.wit │ ├── stdio.wit │ └── terminal.wit ├── clocks │ ├── monotonic-clock.wit │ ├── wall-clock.wit │ └── world.wit ├── filesystem │ ├── preopens.wit │ ├── types.wit │ └── world.wit ├── http │ ├── handler.wit │ ├── proxy.wit │ └── types.wit ├── io │ ├── error.wit │ ├── poll.wit │ ├── streams.wit │ └── world.wit ├── isyswasfa-io │ ├── pipe.wit │ └── poll.wit ├── isyswasfa │ └── isyswasfa.wit ├── random │ ├── insecure-seed.wit │ ├── insecure.wit │ ├── random.wit │ └── world.wit └── sockets │ ├── instance-network.wit │ ├── ip-name-lookup.wit │ ├── network.wit │ ├── tcp-create-socket.wit │ ├── tcp.wit │ ├── udp-create-socket.wit │ ├── udp.wit │ └── world.wit └── test.wit /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | target 3 | *.wasm 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wit-bindgen"] 2 | path = wit-bindgen 3 | url = https://github.com/dicej/wit-bindgen 4 | [submodule "wasm-tools"] 5 | path = wasm-tools 6 | url = https://github.com/dicej/wasm-tools 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Building and running tests 2 | 3 | ### Prerequisites 4 | 5 | - Unix-like environment 6 | - Rust stable 1.71 or later *and* nightly 2023-07-27 or later, including the `wasm32-wasi` and `wasm32-unknown-unknown` targets 7 | - See https://github.com/bytecodealliance/componentize-py/blob/main/CONTRIBUTING.md for details 8 | 9 | First, make sure you have all the submodules cloned: 10 | 11 | ```shell 12 | git submodule update --init --recursive 13 | ``` 14 | 15 | Next, grab a `wasi-sockets`-enabled build of `wasi-sdk` (replace `linux` with `macos` or `mingw` (Windows) as appropriate): 16 | 17 | ```shell 18 | curl -LO https://github.com/dicej/wasi-sdk/releases/download/wasi-sockets-alpha-2/wasi-sdk-20.26g68203b20b82e-linux.tar.gz 19 | tar xf tar xf wasi-sdk-20.26g68203b20b82e-linux.tar.gz 20 | sudo mv wasi-sdk-20.26g68203b20b82e /opt/wasi-sdk 21 | export WASI_SDK_PATH=/opt/wasi-sdk 22 | ``` 23 | 24 | Finally, build and run the tests: 25 | 26 | ```shell 27 | cargo test --release 28 | ``` 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "cli", 5 | "host", 6 | "http", 7 | "test" 8 | ] 9 | exclude = [ 10 | "guest", 11 | "wit-bindgen", 12 | "wasmtime", 13 | "wasm-tools", 14 | "componentize-py", 15 | ] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## isyswasfa: I sync, you sync, we all sync for async 2 | 3 | An experimental polyfill for composable concurrency based on the [WebAssembly Component Model](https://github.com/WebAssembly/component-model) and [WASI](https://github.com/WebAssembly/WASI) 0.2 4 | 5 | **NOTE: This project is not being maintained. We've shifted our focus to developing WASIp3 and the underlying Component Model async support upstream in the [component-model](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md), [wasip3-prototyping](https://github.com/bytecodealliance/wasip3-prototyping) (with incremental upstreaming to the main Wasmtime repo), [wasm-tools](https://github.com/bytecodealliance/wasm-tools), and [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen) repos. Feel free to experiment with this, but don't use it for anything serious!** 6 | 7 | ### Background 8 | 9 | As of this writing, the Component Model does not support concurrent, composable execution. Although WASI 0.2 includes support for asynchronous I/O via the `wasi:io/poll` interface, it does not compose well: only one component in a composition can block at a time. A major goal for WASI 0.3 is to provide built-in support for "composable async" in the Component Model, thereby resolving the tension between composition and concurrency. 10 | 11 | ### So what is this? 12 | 13 | A pile of hacks -- but a _useful_ pile of hacks. The goals are: 14 | 15 | - To provide early, real-world implementation feedback to the Component Model "async" design process 16 | - To give developers a tool for "polyfilling" composable concurrency on top of WASI 0.2, ideally in such a way that upgrading application code to 0.3 requires little or no effort 17 | 18 | In short, it's an experiment to see how close we can get to the 0.3 developer experience with minimal changes to existing tools. 19 | 20 | ### Features 21 | 22 | - Modified guest WIT bindings generators and support libraries for first-class, composable async/await in Rust and Python 23 | - Support for concurrent `await`ing of any `wasi:io/poll.pollable` (e.g. files, sockets, timers, HTTP bodies) 24 | - No need for `wasi:io/poll.poll` anymore -- just use `await`! 25 | - `spawn` function allows guests to spawn tasks which may outlive the current function call from the host or composed component 26 | - For example, you can spawn a task to stream an HTTP response body and return the response object to the caller before the stream has finished. 27 | - Async-friendly composition using `wasm-compose` 28 | - Asynchronous cancellation of host and guest tasks (currently untested) 29 | - Modified Rust host-side binding generator and support library for hosting components, including a working implementation of `wasi:http@0.3.0-draft` 30 | - A CLI tool supporting a `serve` subcommand for running `isyswasfa`-flavored `wasi:http@0.3.0-draft` components 31 | 32 | ### Planned features 33 | 34 | - Guest support for other languages supporting stackless coroutines 35 | - E.g. JavaScript and .NET 36 | - Eventually, the Component Model will also support composable concurrency for stackful coroutines (e.g. Goroutines, Java fibers, etc.), but those are out of scope for this polyfill. 37 | - Host-side code generation for bridging async and sync components using backpressure to serialize async->sync calls without blocking the caller 38 | 39 | ### Examples 40 | 41 | The [test/rust-cases](./test/rust-cases) and [test/python-cases](./test/python-cases) directories contain a few guest programs: 42 | 43 | - **round-trip** ([Rust version](./test/rust-cases/round-trip/src/lib.rs), [Python version](./test/python-cases/round-trip/app.py)): a simple example of an exported async function calling an imported async function 44 | - **service** ([Rust version](./test/rust-cases/service/src/lib.rs), [Python version](./test/python-cases/service/app.py)) and **middleware** ([Rust version](./test/rust-cases/middleware/src/lib.rs)): a pair of components which are composed to demonstrate cross-component asynchronous I/O, with the middleware providing transparent `deflate` encoding and decoding support to the service. These use `wasi:http@0.3.0-draft`, which includes a single `request` type and a single `response` type; unlike `wasi:http@0.2.0`, there is no need for incoming and outgoing variations of those types. 45 | - **hash-all** ([Rust version](./test/rust-cases/hash-all/src/lib.rs), [Python version](./test/python-cases/hash-all/app.py)): a `wasi:http@0.3.0-draft` component, capable of sending multiple concurrent outgoing requests, hashing the response bodies without buffering, and streaming the hashes back to the client. 46 | - **echo** ([Rust version](./test/rust-cases/echo/src/lib.rs), [Python version](./test/python-cases/echo/app.py)): a `wasi:http@0.3.0-draft` component, capable of either echoing the request body back to the client without buffering, or else piping the request body to an outgoing request and then streaming the response body back to the client. 47 | - **router** ([Rust version](./test/rust-cases/router/src/lib.rs)): a `wasi:http@0.3.0-draft` component which composes with all of the above components and dispatches requests to them, as well as proxying outbound requests via the host. 48 | 49 | See also [test/src/lib.rs](./test/src/lib.rs), which uses generated host bindings to test the above examples. 50 | 51 | #### Building and running the examples 52 | 53 | To build the CLI from source using this Git repository, [install Rust](https://rustup.rs/) and run: 54 | 55 | ```shell 56 | git submodule update --init --recursive 57 | cargo build --release --manifest-path cli/Cargo.toml 58 | ``` 59 | 60 | Then you can run the command using e.g.: 61 | 62 | ```shell 63 | ./target/release/isyswasfa --help 64 | ``` 65 | 66 | To build the Rust guest examples, you'll need to make sure you have the `wasm32-wasi` Rust target installed, along with a recent version of `wasm-tools`: 67 | 68 | ```shell 69 | rustup target add wasm32-wasi 70 | cargo install wasm-tools 71 | ``` 72 | 73 | We can build the Rust `hash-all` example using: 74 | 75 | ```shell 76 | cargo build --release --target wasm32-wasi --manifest-path test/rust-cases/hash-all/Cargo.toml 77 | curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/v18.0.2/wasi_snapshot_preview1.reactor.wasm 78 | wasm-tools component new --adapt wasi_snapshot_preview1.reactor.wasm test/rust-cases/target/wasm32-wasi/release/hash_all.wasm -o hash-all.wasm 79 | ``` 80 | 81 | And finally we can run it: 82 | 83 | ```shell 84 | ./target/release/isyswasfa serve hash-all.wasm 85 | ``` 86 | 87 | While that's running, we can send it a request from another terminal: 88 | 89 | ```shell 90 | curl -i \ 91 | -H 'url: https://webassembly.github.io/spec/core/' \ 92 | -H 'url: https://www.w3.org/groups/wg/wasm/' \ 93 | -H 'url: https://bytecodealliance.org/' \ 94 | http://127.0.0.1:8080/ 95 | ``` 96 | 97 | To build the Python examples, you'll need to use `componentize-py`, which you can install with `pip`: 98 | 99 | ```shell 100 | pip install componentize-py==0.13.0 101 | ``` 102 | 103 | Then build and run the Python `hash-all` example: 104 | 105 | ```shell 106 | componentize-py --isyswasfa=-echo -d wit -w proxy componentize -p test/python-cases/hash-all app -o hash-all.wasm 107 | ./target/release/isyswasfa serve hash-all.wasm 108 | ``` 109 | 110 | #### Composing Rust and Python components 111 | 112 | In addition to the tools we built above, we can build `wasm-compose` and use it to compose the Python `hash-all` component with the Rust `middleware` component. Here, we use a lightly-patched version which supports exposing exports from multiple subcomponents: 113 | 114 | ```shell 115 | cargo build --release --manifest-path wasm-tools/Cargo.toml 116 | ``` 117 | 118 | Then we build the `middleware` component (reusing the `wasi_snapshot_preview1.reactor.wasm` file we downloaded above): 119 | 120 | ```shell 121 | cargo build --release --target wasm32-wasi --manifest-path test/rust-cases/middleware/Cargo.toml 122 | wasm-tools component new --adapt wasi_snapshot_preview1.reactor.wasm test/rust-cases/target/wasm32-wasi/debug/middleware.wasm -o middleware.wasm 123 | ``` 124 | 125 | And compose it with the `hash-all` component we built above, then run the result: 126 | 127 | ```shell 128 | ./wasm-tools/target/release/wasm-tools compose middleware.wasm -d hash-all.wasm -o composed.wasm 129 | ./target/release/isyswasfa serve composed.wasm 130 | ``` 131 | 132 | This time, we'll send a request with a `accept-encoding: deflate` header, which tells the `middleware` component to compress the response body: 133 | 134 | ```shell 135 | curl -i --compressed \ 136 | -H 'accept-encoding: deflate' \ 137 | -H 'url: https://webassembly.github.io/spec/core/' \ 138 | -H 'url: https://www.w3.org/groups/wg/wasm/' \ 139 | -H 'url: https://bytecodealliance.org/' \ 140 | http://127.0.0.1:8080/ 141 | ``` 142 | 143 | Viola! 144 | 145 | ### How it works 146 | 147 | I've lightly modified the `wit-bindgen`, and `wasmtime-wit-bindgen`, and `componentize-py` code generators to support an `isyswasfa` configuration option. When that option is enabled, the code generators "asyncify" a subset of imported and exported functions by splitting each one into two functions: one for initiating a task, and other for retrieving the result when the task has completed. For example: 148 | 149 | - `foo: func(s: string) -> string` becomes: 150 | - `foo-isyswasfa-start: func(s: string) -> result`, where `pending` is a resource handle representing an asynchronous task, returned if a result is not immediately available, and 151 | - `foo-isyswasfa-result: func(r: ready) -> string`, where `ready` is a resource handle representing the completion of an asynchronous task. 152 | 153 | These two functions become part of the new component type seen by the host or composition tool, while the user-visible generated code presents only a single `async` function to the application developer. 154 | 155 | In addition, the guest binding generator exports a function named `isyswasfa-poll$SUFFIX`, where `$SUFFIX` represents a unique string that must differ from any other component the current component might be composed with. In the case of composition, each subcomponent will export its own such function. The host will use these functions to send and receive events to and from the component, keeping track of which subcomponents are waiting for which tasks, where to route cancellation requests and confirmations, etc. 156 | 157 | (TODO: add a step-by-step example with a diagram) 158 | 159 | Although WASI 0.2 imports are *not* transformed as described above, the `isyswasfa-host` and `isyswasfa-guest` support libraries have special support for `wasi:io/poll.pollable` handles such that they can be concurrently `await`ed by the guest and multiplexed by the host, allowing e.g. `monotonic_clock::subscribe_duration(ns).await` to do just what you'd expect. 160 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "isyswasfa" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = { version = "1.0.79", features = ["backtrace"] } 8 | clap = { version = "4.5.2", features = ["derive"] } 9 | tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros", "process", "fs", "time"] } 10 | wasmtime = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa", features = ["component-model"] } 11 | wasmtime-wasi = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa" } 12 | isyswasfa-host = { path = "../host" } 13 | isyswasfa-http = { path = "../http" } 14 | hyper = { version = "1.2.0", features = ["server", "http1"] } 15 | http-body-util = "0.1.0" 16 | bytes = "1.5.0" 17 | async-trait = "0.1.77" 18 | futures = "0.3.30" 19 | http = "1.1.0" 20 | hyper-util = { version = "0.1.3", features = ["tokio"] } 21 | reqwest = { version = "0.11.24", features = ["stream"] } 22 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | wasmtime::component::bindgen!({ 4 | path: "../wit", 5 | world: "proxy", 6 | isyswasfa: true, 7 | with: { 8 | "wasi:clocks/monotonic-clock": wasmtime_wasi::preview2::bindings::wasi::clocks::monotonic_clock, 9 | "wasi:io/error": wasmtime_wasi::preview2::bindings::wasi::io::error, 10 | "wasi:io/streams": wasmtime_wasi::preview2::bindings::wasi::io::streams, 11 | "isyswasfa:io/pipe": isyswasfa_host::isyswasfa_pipe_interface, 12 | "wasi:http/types": isyswasfa_http::wasi::http::types, 13 | } 14 | }); 15 | 16 | use { 17 | anyhow::{anyhow, bail, Error, Result}, 18 | bytes::Bytes, 19 | clap::Parser, 20 | futures::{ 21 | channel::{mpsc, oneshot}, 22 | future::FutureExt, 23 | sink::SinkExt, 24 | stream::{FuturesUnordered, StreamExt, TryStreamExt}, 25 | }, 26 | http_body_util::{combinators::BoxBody, BodyExt, StreamBody}, 27 | hyper::{body::Frame, server::conn::http1, service}, 28 | hyper_util::rt::tokio::TokioIo, 29 | isyswasfa_host::{InputStream, IsyswasfaCtx, IsyswasfaView, ReceiverStream}, 30 | isyswasfa_http::{ 31 | wasi::http::types::{ErrorCode, Method, Scheme}, 32 | Body, Fields, FieldsReceiver, Request, Response, WasiHttpView, 33 | }, 34 | std::{ 35 | future::Future, 36 | net::{IpAddr, Ipv4Addr}, 37 | ops::Deref, 38 | path::PathBuf, 39 | sync::Arc, 40 | }, 41 | tokio::{fs, net::TcpListener}, 42 | wasmtime::{ 43 | component::{Component, InstancePre, Linker, Resource, ResourceTable}, 44 | Config, Engine, Store, 45 | }, 46 | wasmtime_wasi::preview2::{command, StreamError, WasiCtx, WasiCtxBuilder, WasiView}, 47 | }; 48 | 49 | const MAX_READ_SIZE: usize = 64 * 1024; 50 | 51 | /// A utility to experiment with WASI 0.3.0-style composable concurrency 52 | #[derive(clap::Parser, Debug)] 53 | #[command(author, version, about)] 54 | struct Options { 55 | #[command(subcommand)] 56 | command: Command, 57 | } 58 | 59 | #[derive(clap::Subcommand, Debug)] 60 | enum Command { 61 | /// Run an HTTP server, passing requests to the specified component. 62 | Serve(Serve), 63 | } 64 | 65 | #[derive(clap::Args, Debug)] 66 | struct Serve { 67 | /// The component used to handle incoming HTTP requests. 68 | /// 69 | /// The component must export `wasi:http/handler@0.3.0-draft`. 70 | component: PathBuf, 71 | } 72 | 73 | struct Ctx { 74 | wasi: WasiCtx, 75 | isyswasfa: IsyswasfaCtx, 76 | client: reqwest::Client, 77 | } 78 | 79 | impl WasiView for Ctx { 80 | fn table(&mut self) -> &mut ResourceTable { 81 | self.isyswasfa.table() 82 | } 83 | fn ctx(&mut self) -> &mut WasiCtx { 84 | &mut self.wasi 85 | } 86 | } 87 | 88 | impl WasiHttpView for Ctx { 89 | fn table(&mut self) -> &mut ResourceTable { 90 | self.isyswasfa.table() 91 | } 92 | 93 | fn send_request( 94 | &mut self, 95 | request: Resource, 96 | ) -> wasmtime::Result< 97 | impl Future< 98 | Output = impl FnOnce( 99 | &mut Self, 100 | ) 101 | -> wasmtime::Result, ErrorCode>> 102 | + 'static, 103 | > + Send 104 | + 'static, 105 | > { 106 | let request = WasiHttpView::table(self).delete(request)?; 107 | let client = self.client.clone(); 108 | Ok(async move { 109 | let response = send_request(client, request).await; 110 | move |self_: &mut Self| { 111 | Ok(match response? { 112 | Ok(response) => Ok(WasiHttpView::table(self_).push(response)?), 113 | Err(e) => Err(e), 114 | }) 115 | } 116 | }) 117 | } 118 | } 119 | 120 | impl IsyswasfaView for Ctx { 121 | fn isyswasfa(&mut self) -> &mut IsyswasfaCtx { 122 | &mut self.isyswasfa 123 | } 124 | } 125 | 126 | async fn send_request( 127 | client: reqwest::Client, 128 | mut request: Request, 129 | ) -> wasmtime::Result> { 130 | if request.options.is_some() { 131 | bail!("todo: handle outgoing request options"); 132 | } 133 | 134 | if request.body.trailers.is_some() { 135 | bail!("todo: handle outgoing request trailers"); 136 | } 137 | 138 | let method = match request.method { 139 | Method::Get => reqwest::Method::GET, 140 | Method::Head => reqwest::Method::HEAD, 141 | Method::Post => reqwest::Method::POST, 142 | Method::Put => reqwest::Method::PUT, 143 | Method::Delete => reqwest::Method::DELETE, 144 | Method::Connect => reqwest::Method::CONNECT, 145 | Method::Options => reqwest::Method::OPTIONS, 146 | Method::Trace => reqwest::Method::TRACE, 147 | Method::Patch => reqwest::Method::PATCH, 148 | Method::Other(s) => match reqwest::Method::from_bytes(s.as_bytes()) { 149 | Ok(method) => method, 150 | Err(e) => { 151 | // TODO: map errors more precisely 152 | return Ok(Err(ErrorCode::InternalError(Some(format!("{e:?}"))))); 153 | } 154 | }, 155 | }; 156 | let scheme = request.scheme.unwrap_or(Scheme::Http); 157 | let authority = if let Some(authority) = request.authority { 158 | authority 159 | } else { 160 | match scheme { 161 | Scheme::Http => ":80", 162 | Scheme::Https => ":443", 163 | _ => bail!("unable to determine authority for {scheme:?}"), 164 | } 165 | .into() 166 | }; 167 | let path = request.path_with_query.unwrap_or_else(|| "/".into()); 168 | 169 | let InputStream::Host(mut request_rx) = request.body.stream.take().unwrap() else { 170 | todo!("handle non-`InputStream::Host` case"); 171 | }; 172 | 173 | let (mut request_body_tx, request_body_rx) = mpsc::channel(1); 174 | 175 | tokio::spawn( 176 | async move { 177 | loop { 178 | match request_rx.read(MAX_READ_SIZE) { 179 | Ok(bytes) if bytes.is_empty() => request_rx.ready().await, 180 | Ok(bytes) => request_body_tx.send(bytes).await?, 181 | Err(StreamError::Closed) => break Ok(()), 182 | Err(e) => break Err(anyhow!("error reading response body: {e:?}")), 183 | } 184 | } 185 | } 186 | .map(|result| { 187 | if let Err(e) = result { 188 | eprintln!("error streaming request body: {e:?}"); 189 | } 190 | }), 191 | ); 192 | 193 | let response = client 194 | .request(method, format!("{scheme}://{authority}{path}")) 195 | .body(reqwest::Body::wrap_stream( 196 | request_body_rx.map(Ok::<_, Error>), 197 | )) 198 | .send() 199 | .await 200 | .map_err(|e| { 201 | // TODO: map errors more precisely 202 | ErrorCode::InternalError(Some(format!("{e:?}"))) 203 | })?; 204 | 205 | let status_code = response.status().as_u16(); 206 | let headers = Fields( 207 | response 208 | .headers() 209 | .iter() 210 | .map(|(k, v)| (k.as_str().into(), v.as_bytes().into())) 211 | .collect(), 212 | ); 213 | 214 | let (mut response_body_tx, response_body_rx) = mpsc::channel(1); 215 | 216 | tokio::spawn( 217 | async move { 218 | let mut stream = response.bytes_stream(); 219 | 220 | while let Some(chunk) = stream.try_next().await? { 221 | response_body_tx.send(chunk).await?; 222 | } 223 | 224 | Ok::<_, Error>(()) 225 | } 226 | .map(|result| { 227 | if let Err(e) = result { 228 | eprintln!("error streaming response body: {e:?}"); 229 | } 230 | }), 231 | ); 232 | 233 | Ok(Ok(Response { 234 | status_code, 235 | headers, 236 | body: Body { 237 | stream: Some(InputStream::Host(Box::new(ReceiverStream::new( 238 | response_body_rx, 239 | )))), 240 | // TODO: handle response trailers 241 | trailers: None, 242 | }, 243 | })) 244 | } 245 | 246 | async fn handle_request( 247 | engine: &Engine, 248 | pre: &InstancePre, 249 | component_bytes: &[u8], 250 | request: hyper::Request, 251 | ) -> Result>> { 252 | let mut store = Store::new( 253 | engine, 254 | Ctx { 255 | wasi: WasiCtxBuilder::new().inherit_stdio().build(), 256 | isyswasfa: IsyswasfaCtx::new(), 257 | client: reqwest::Client::new(), 258 | }, 259 | ); 260 | 261 | let (service, instance) = Proxy::instantiate_pre(&mut store, pre).await?; 262 | 263 | isyswasfa_host::load_poll_funcs(&mut store, component_bytes, &instance)?; 264 | 265 | let (mut request_body_tx, request_body_rx) = mpsc::channel(1); 266 | 267 | let (request_trailers_tx, request_trailers_rx) = oneshot::channel(); 268 | 269 | let wasi_request = WasiHttpView::table(store.data_mut()).push(Request { 270 | method: match request.method() { 271 | &http::Method::GET => Method::Get, 272 | &http::Method::POST => Method::Post, 273 | &http::Method::PUT => Method::Put, 274 | &http::Method::DELETE => Method::Delete, 275 | &http::Method::PATCH => Method::Patch, 276 | &http::Method::HEAD => Method::Head, 277 | &http::Method::OPTIONS => Method::Options, 278 | request => Method::Other(request.as_str().into()), 279 | }, 280 | scheme: request.uri().scheme().map(|scheme| match scheme.as_str() { 281 | "http" => Scheme::Http, 282 | "https" => Scheme::Https, 283 | _ => Scheme::Other(scheme.as_str().into()), 284 | }), 285 | path_with_query: request.uri().path_and_query().map(|p| p.as_str().into()), 286 | authority: request.uri().authority().map(|a| a.as_str().into()), 287 | headers: Fields( 288 | request 289 | .headers() 290 | .iter() 291 | .map(|(k, v)| (k.as_str().into(), v.as_bytes().into())) 292 | .collect(), 293 | ), 294 | body: Body { 295 | stream: Some(InputStream::Host(Box::new(ReceiverStream::new( 296 | request_body_rx, 297 | )))), 298 | trailers: Some(FieldsReceiver(request_trailers_rx)), 299 | }, 300 | options: None, 301 | })?; 302 | 303 | let pipe_request_body = async move { 304 | let mut body = request.into_body(); 305 | 306 | let mut request_trailers_tx = Some(request_trailers_tx); 307 | while let Some(frame) = body.frame().await { 308 | match frame?.into_data() { 309 | Ok(chunk) => request_body_tx.send(chunk).await?, 310 | Err(frame) => match frame.into_trailers() { 311 | Ok(trailers) => drop( 312 | request_trailers_tx 313 | .take() 314 | .ok_or_else(|| anyhow!("more than one set of trailers received"))? 315 | .send(Fields( 316 | trailers 317 | .iter() 318 | .map(|(k, v)| (k.as_str().into(), v.as_bytes().into())) 319 | .collect(), 320 | )), 321 | ), 322 | Err(_) => unreachable!(), 323 | }, 324 | } 325 | } 326 | 327 | Ok::<_, Error>(None) 328 | }; 329 | 330 | let call_handle = async move { 331 | let response = service 332 | .wasi_http_handler() 333 | .call_handle(&mut store, wasi_request) 334 | .await??; 335 | 336 | Ok::<_, Error>(Some((response, store))) 337 | }; 338 | 339 | let mut futures = FuturesUnordered::new(); 340 | futures.push(pipe_request_body.boxed()); 341 | futures.push(call_handle.boxed()); 342 | 343 | while let Some(event) = futures.try_next().await? { 344 | if let Some((response, mut store)) = event { 345 | let response = WasiHttpView::table(store.data_mut()).delete(response)?; 346 | 347 | let mut body = response.body; 348 | 349 | let InputStream::Host(mut response_rx) = body.stream.take().unwrap() else { 350 | unreachable!(); 351 | }; 352 | 353 | let (mut response_body_tx, response_body_rx) = mpsc::channel(1); 354 | 355 | futures.push( 356 | async move { 357 | isyswasfa_host::poll_loop_until(&mut store, async move { 358 | loop { 359 | match response_rx.read(MAX_READ_SIZE) { 360 | Ok(bytes) if bytes.is_empty() => response_rx.ready().await, 361 | Ok(bytes) => response_body_tx.send(Ok(Frame::data(bytes))).await?, 362 | Err(StreamError::Closed) => break Ok(()), 363 | Err(e) => break Err(anyhow!("error reading response body: {e:?}")), 364 | } 365 | }?; 366 | 367 | if let Some(trailers) = body.trailers.take() { 368 | if let Ok(trailers) = trailers.0.await { 369 | response_body_tx 370 | .send(Ok(Frame::trailers( 371 | trailers 372 | .0 373 | .into_iter() 374 | .map(|(k, v)| Ok((k.try_into()?, v.try_into()?))) 375 | .collect::>()?, 376 | ))) 377 | .await?; 378 | } 379 | } 380 | 381 | Ok::<_, Error>(()) 382 | }) 383 | .await??; 384 | 385 | Ok(None) 386 | } 387 | .boxed(), 388 | ); 389 | 390 | tokio::spawn( 391 | async move { 392 | while let Some(event) = futures.try_next().await? { 393 | assert!(event.is_none()); 394 | } 395 | 396 | Ok::<_, Error>(()) 397 | } 398 | .map(|result| { 399 | if let Err(e) = result { 400 | eprintln!("error sending response body: {e:?}"); 401 | } 402 | }), 403 | ); 404 | 405 | let mut builder = hyper::Response::builder().status(response.status_code); 406 | for (k, v) in response.headers.0 { 407 | builder = builder.header(k, v); 408 | } 409 | return Ok(builder.body(BoxBody::new(StreamBody::new(response_body_rx)))?); 410 | } 411 | } 412 | 413 | unreachable!() 414 | } 415 | 416 | #[tokio::main] 417 | async fn main() -> Result<()> { 418 | let Options { 419 | command: Command::Serve(Serve { component }), 420 | } = Options::parse(); 421 | 422 | let mut config = Config::new(); 423 | config.wasm_component_model(true); 424 | config.async_support(true); 425 | 426 | let engine = Engine::new(&config)?; 427 | 428 | let component_bytes = fs::read(component).await?; 429 | 430 | let component = Component::new(&engine, &component_bytes)?; 431 | 432 | let mut linker = Linker::new(&engine); 433 | 434 | command::add_to_linker(&mut linker)?; 435 | isyswasfa_host::add_to_linker(&mut linker)?; 436 | isyswasfa_http::add_to_linker(&mut linker)?; 437 | 438 | let pre = linker.instantiate_pre(&component)?; 439 | 440 | let listener = TcpListener::bind((IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8080)).await?; 441 | 442 | let state = Arc::new((engine, pre, component_bytes)); 443 | 444 | eprintln!("Serving HTTP on http://{}/", listener.local_addr()?); 445 | 446 | loop { 447 | let (stream, _) = listener.accept().await?; 448 | let state = state.clone(); 449 | tokio::task::spawn(async move { 450 | if let Err(e) = http1::Builder::new() 451 | .keep_alive(true) 452 | .serve_connection( 453 | TokioIo::new(stream), 454 | service::service_fn(move |request: hyper::Request| { 455 | let state = state.clone(); 456 | async move { 457 | let (engine, pre, component_bytes) = state.deref(); 458 | handle_request(engine, pre, component_bytes, request).await 459 | } 460 | }), 461 | ) 462 | .await 463 | { 464 | eprintln!("error: {e:?}"); 465 | } 466 | }); 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /guest/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.79" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" 10 | 11 | [[package]] 12 | name = "autocfg" 13 | version = "1.1.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "2.4.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 22 | 23 | [[package]] 24 | name = "by_address" 25 | version = "1.1.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "bf8dba2868114ed769a1f2590fc9ae5eb331175b44313b6c9b922f8f7ca813d0" 28 | 29 | [[package]] 30 | name = "equivalent" 31 | version = "1.0.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 34 | 35 | [[package]] 36 | name = "futures" 37 | version = "0.3.30" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 40 | dependencies = [ 41 | "futures-channel", 42 | "futures-core", 43 | "futures-executor", 44 | "futures-io", 45 | "futures-sink", 46 | "futures-task", 47 | "futures-util", 48 | ] 49 | 50 | [[package]] 51 | name = "futures-channel" 52 | version = "0.3.30" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 55 | dependencies = [ 56 | "futures-core", 57 | "futures-sink", 58 | ] 59 | 60 | [[package]] 61 | name = "futures-core" 62 | version = "0.3.30" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 65 | 66 | [[package]] 67 | name = "futures-executor" 68 | version = "0.3.30" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 71 | dependencies = [ 72 | "futures-core", 73 | "futures-task", 74 | "futures-util", 75 | ] 76 | 77 | [[package]] 78 | name = "futures-io" 79 | version = "0.3.30" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 82 | 83 | [[package]] 84 | name = "futures-macro" 85 | version = "0.3.30" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 88 | dependencies = [ 89 | "proc-macro2", 90 | "quote", 91 | "syn", 92 | ] 93 | 94 | [[package]] 95 | name = "futures-sink" 96 | version = "0.3.30" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 99 | 100 | [[package]] 101 | name = "futures-task" 102 | version = "0.3.30" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 105 | 106 | [[package]] 107 | name = "futures-util" 108 | version = "0.3.30" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 111 | dependencies = [ 112 | "futures-channel", 113 | "futures-core", 114 | "futures-io", 115 | "futures-macro", 116 | "futures-sink", 117 | "futures-task", 118 | "memchr", 119 | "pin-project-lite", 120 | "pin-utils", 121 | "slab", 122 | ] 123 | 124 | [[package]] 125 | name = "hashbrown" 126 | version = "0.14.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 129 | 130 | [[package]] 131 | name = "heck" 132 | version = "0.4.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 135 | dependencies = [ 136 | "unicode-segmentation", 137 | ] 138 | 139 | [[package]] 140 | name = "id-arena" 141 | version = "2.2.1" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 144 | 145 | [[package]] 146 | name = "indexmap" 147 | version = "2.1.0" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 150 | dependencies = [ 151 | "equivalent", 152 | "hashbrown", 153 | "serde", 154 | ] 155 | 156 | [[package]] 157 | name = "isyswasfa-guest" 158 | version = "0.1.0" 159 | dependencies = [ 160 | "by_address", 161 | "futures", 162 | "once_cell", 163 | "wit-bindgen", 164 | ] 165 | 166 | [[package]] 167 | name = "isyswasfa-transform" 168 | version = "0.1.0" 169 | dependencies = [ 170 | "wit-parser", 171 | ] 172 | 173 | [[package]] 174 | name = "itoa" 175 | version = "1.0.10" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 178 | 179 | [[package]] 180 | name = "leb128" 181 | version = "0.2.5" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 184 | 185 | [[package]] 186 | name = "log" 187 | version = "0.4.20" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 190 | 191 | [[package]] 192 | name = "memchr" 193 | version = "2.7.1" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 196 | 197 | [[package]] 198 | name = "once_cell" 199 | version = "1.19.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 202 | 203 | [[package]] 204 | name = "pin-project-lite" 205 | version = "0.2.13" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 208 | 209 | [[package]] 210 | name = "pin-utils" 211 | version = "0.1.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 214 | 215 | [[package]] 216 | name = "proc-macro2" 217 | version = "1.0.78" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 220 | dependencies = [ 221 | "unicode-ident", 222 | ] 223 | 224 | [[package]] 225 | name = "quote" 226 | version = "1.0.35" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 229 | dependencies = [ 230 | "proc-macro2", 231 | ] 232 | 233 | [[package]] 234 | name = "ryu" 235 | version = "1.0.16" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 238 | 239 | [[package]] 240 | name = "semver" 241 | version = "1.0.21" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" 244 | 245 | [[package]] 246 | name = "serde" 247 | version = "1.0.195" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" 250 | dependencies = [ 251 | "serde_derive", 252 | ] 253 | 254 | [[package]] 255 | name = "serde_derive" 256 | version = "1.0.195" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" 259 | dependencies = [ 260 | "proc-macro2", 261 | "quote", 262 | "syn", 263 | ] 264 | 265 | [[package]] 266 | name = "serde_json" 267 | version = "1.0.111" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" 270 | dependencies = [ 271 | "itoa", 272 | "ryu", 273 | "serde", 274 | ] 275 | 276 | [[package]] 277 | name = "slab" 278 | version = "0.4.9" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 281 | dependencies = [ 282 | "autocfg", 283 | ] 284 | 285 | [[package]] 286 | name = "smallvec" 287 | version = "1.13.1" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 290 | 291 | [[package]] 292 | name = "spdx" 293 | version = "0.10.3" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "62bde1398b09b9f93fc2fc9b9da86e362693e999d3a54a8ac47a99a5a73f638b" 296 | dependencies = [ 297 | "smallvec", 298 | ] 299 | 300 | [[package]] 301 | name = "syn" 302 | version = "2.0.48" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 305 | dependencies = [ 306 | "proc-macro2", 307 | "quote", 308 | "unicode-ident", 309 | ] 310 | 311 | [[package]] 312 | name = "unicode-ident" 313 | version = "1.0.12" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 316 | 317 | [[package]] 318 | name = "unicode-segmentation" 319 | version = "1.10.1" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 322 | 323 | [[package]] 324 | name = "unicode-xid" 325 | version = "0.2.4" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 328 | 329 | [[package]] 330 | name = "wasm-encoder" 331 | version = "0.39.0" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d" 334 | dependencies = [ 335 | "leb128", 336 | ] 337 | 338 | [[package]] 339 | name = "wasm-encoder" 340 | version = "0.40.0" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "d162eb64168969ae90e8668ca0593b0e47667e315aa08e717a9c9574d700d826" 343 | dependencies = [ 344 | "leb128", 345 | ] 346 | 347 | [[package]] 348 | name = "wasm-metadata" 349 | version = "0.10.16" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "0b313e616ef69d1b4c64155451439db26d1923e8bbc13d451ec24cf14579632e" 352 | dependencies = [ 353 | "anyhow", 354 | "indexmap", 355 | "serde", 356 | "serde_derive", 357 | "serde_json", 358 | "spdx", 359 | "wasm-encoder 0.40.0", 360 | "wasmparser 0.120.0", 361 | ] 362 | 363 | [[package]] 364 | name = "wasmparser" 365 | version = "0.119.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "8c35daf77afb4f9b14016625144a391085ec2ca99ca9cc53ed291bb53ab5278d" 368 | dependencies = [ 369 | "bitflags", 370 | "indexmap", 371 | "semver", 372 | ] 373 | 374 | [[package]] 375 | name = "wasmparser" 376 | version = "0.120.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "e9148127f39cbffe43efee8d5442b16ecdba21567785268daa1ec9e134389705" 379 | dependencies = [ 380 | "bitflags", 381 | "indexmap", 382 | "semver", 383 | ] 384 | 385 | [[package]] 386 | name = "wit-bindgen" 387 | version = "0.16.0" 388 | dependencies = [ 389 | "bitflags", 390 | "wit-bindgen-rust-macro", 391 | ] 392 | 393 | [[package]] 394 | name = "wit-bindgen-core" 395 | version = "0.16.0" 396 | dependencies = [ 397 | "anyhow", 398 | "indexmap", 399 | "wit-component", 400 | "wit-parser", 401 | ] 402 | 403 | [[package]] 404 | name = "wit-bindgen-rust" 405 | version = "0.16.0" 406 | dependencies = [ 407 | "anyhow", 408 | "heck", 409 | "wasm-metadata", 410 | "wit-bindgen-core", 411 | "wit-component", 412 | ] 413 | 414 | [[package]] 415 | name = "wit-bindgen-rust-macro" 416 | version = "0.16.0" 417 | dependencies = [ 418 | "anyhow", 419 | "isyswasfa-transform", 420 | "proc-macro2", 421 | "quote", 422 | "syn", 423 | "wit-bindgen-core", 424 | "wit-bindgen-rust", 425 | "wit-component", 426 | ] 427 | 428 | [[package]] 429 | name = "wit-component" 430 | version = "0.19.1" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "429e3c06fba3a7566aab724ae3ffff3152ede5399d44789e7dd11f5421292859" 433 | dependencies = [ 434 | "anyhow", 435 | "bitflags", 436 | "indexmap", 437 | "log", 438 | "serde", 439 | "serde_derive", 440 | "serde_json", 441 | "wasm-encoder 0.39.0", 442 | "wasm-metadata", 443 | "wasmparser 0.119.0", 444 | "wit-parser", 445 | ] 446 | 447 | [[package]] 448 | name = "wit-parser" 449 | version = "0.13.1" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "df4913a2219096373fd6512adead1fb77ecdaa59d7fc517972a7d30b12f625be" 452 | dependencies = [ 453 | "anyhow", 454 | "id-arena", 455 | "indexmap", 456 | "log", 457 | "semver", 458 | "serde", 459 | "serde_derive", 460 | "serde_json", 461 | "unicode-xid", 462 | ] 463 | -------------------------------------------------------------------------------- /guest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "isyswasfa-guest" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | by_address = "1.1.0" 8 | futures = "0.3.30" 9 | once_cell = "1.19.0" 10 | wit-bindgen = { path = "../wit-bindgen/crates/guest-rust" } 11 | -------------------------------------------------------------------------------- /guest/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod bindings { 4 | wit_bindgen::generate!({ 5 | path: "../wit", 6 | inline: " 7 | package foo:foo; 8 | world foo { 9 | use isyswasfa:isyswasfa/isyswasfa.{poll-input, poll-output}; 10 | 11 | import isyswasfa:isyswasfa/isyswasfa; 12 | import isyswasfa:io/poll; 13 | import wasi:io/streams@0.2.0; 14 | 15 | export dummy: func(input: poll-input) -> poll-output; 16 | } 17 | " 18 | }); 19 | 20 | struct World; 21 | 22 | export!(World); 23 | 24 | impl Guest for World { 25 | fn dummy(_input: PollInput) -> PollOutput { 26 | unreachable!() 27 | } 28 | } 29 | } 30 | 31 | use { 32 | bindings::{ 33 | isyswasfa::{ 34 | io::poll, 35 | isyswasfa::isyswasfa::{ 36 | self, Cancel, Pending, PollInput, PollInputCancel, PollInputListening, 37 | PollInputReady, PollOutput, PollOutputListen, PollOutputPending, PollOutputReady, 38 | Ready, 39 | }, 40 | }, 41 | wasi::io::{ 42 | poll::Pollable, 43 | streams::{InputStream, OutputStream, StreamError}, 44 | }, 45 | }, 46 | by_address::ByAddress, 47 | futures::{ 48 | channel::oneshot, 49 | future::FutureExt, 50 | sink::{self, Sink}, 51 | stream::{self, Stream}, 52 | }, 53 | once_cell::sync::Lazy, 54 | std::{ 55 | any::Any, 56 | cell::RefCell, 57 | collections::HashSet, 58 | future::Future, 59 | future::IntoFuture, 60 | mem, 61 | ops::{Deref, DerefMut}, 62 | pin::Pin, 63 | ptr, 64 | rc::Rc, 65 | sync::Arc, 66 | task::{Context, Poll, Wake}, 67 | }, 68 | }; 69 | 70 | pub use bindings::{ 71 | isyswasfa::isyswasfa::isyswasfa as isyswasfa_interface, 72 | wasi::io::{poll as poll_interface, streams as streams_interface}, 73 | }; 74 | 75 | struct FutureStateWaker(Rc>); 76 | 77 | unsafe impl Send for FutureStateWaker {} 78 | unsafe impl Sync for FutureStateWaker {} 79 | 80 | impl Wake for FutureStateWaker { 81 | fn wake(self: Arc) { 82 | insert_pollable(self.0.clone()) 83 | } 84 | } 85 | 86 | type BoxFuture = Pin> + 'static>>; 87 | 88 | enum CancelState { 89 | Pending, 90 | Cancel, 91 | Listening(Cancel), 92 | } 93 | 94 | struct CancelOnDrop(Rc>); 95 | 96 | impl Drop for CancelOnDrop { 97 | fn drop(&mut self) { 98 | match mem::replace(self.0.borrow_mut().deref_mut(), CancelState::Cancel) { 99 | CancelState::Pending | CancelState::Cancel => {} 100 | CancelState::Listening(cancel) => push_output(PollOutput::Cancel(cancel)), 101 | } 102 | } 103 | } 104 | 105 | struct PendingState { 106 | pending: Pending, 107 | tx: oneshot::Sender, 108 | cancel_state: Rc>, 109 | } 110 | 111 | type Pollables = HashSet>>>; 112 | 113 | static mut POLLABLES: Lazy = Lazy::new(HashSet::new); 114 | 115 | fn insert_pollable(pollable: Rc>) { 116 | unsafe { POLLABLES.insert(ByAddress(pollable)) }; 117 | } 118 | 119 | fn take_pollables() -> Pollables { 120 | unsafe { mem::take(POLLABLES.deref_mut()) } 121 | } 122 | 123 | static mut POLL_OUTPUT: Vec = Vec::new(); 124 | 125 | fn push_output(output: PollOutput) { 126 | unsafe { POLL_OUTPUT.push(output) } 127 | } 128 | 129 | fn take_output() -> Vec { 130 | unsafe { mem::take(&mut *ptr::addr_of_mut!(POLL_OUTPUT)) } 131 | } 132 | 133 | static mut PENDING: Vec = Vec::new(); 134 | 135 | fn push_pending(pending_state: PendingState) { 136 | unsafe { PENDING.push(pending_state) } 137 | } 138 | 139 | fn set_pending(pending: Vec) { 140 | unsafe { 141 | PENDING = pending; 142 | } 143 | } 144 | 145 | fn take_pending() -> Vec { 146 | unsafe { mem::take(&mut *ptr::addr_of_mut!(PENDING)) } 147 | } 148 | 149 | fn clear_pending() { 150 | unsafe { PENDING.clear() } 151 | } 152 | 153 | struct ListenState { 154 | tx: oneshot::Sender, 155 | future_state: Rc>, 156 | cancel_state: Rc>, 157 | } 158 | 159 | enum FutureState { 160 | Pending { 161 | ready: Option, 162 | future: BoxFuture, 163 | cancel_states: Vec>>, 164 | }, 165 | Cancelled(Option), 166 | Ready(Option>), 167 | } 168 | 169 | impl Drop for FutureState { 170 | fn drop(&mut self) { 171 | match self { 172 | Self::Pending { .. } => (), 173 | Self::Cancelled(cancel) => { 174 | push_output(PollOutput::CancelComplete(cancel.take().unwrap())) 175 | } 176 | Self::Ready(ready) => assert!(ready.is_none()), 177 | } 178 | } 179 | } 180 | 181 | fn push_listens(future_state: &Rc>) { 182 | for pending in take_pending() { 183 | push_output(PollOutput::Listen(PollOutputListen { 184 | pending: pending.pending, 185 | state: u32::try_from(Box::into_raw(Box::new(ListenState { 186 | tx: pending.tx, 187 | cancel_state: pending.cancel_state, 188 | future_state: future_state.clone(), 189 | })) as usize) 190 | .unwrap(), 191 | })); 192 | } 193 | } 194 | 195 | pub fn first_poll(future: impl Future + 'static) -> Result { 196 | do_first_poll(Box::pin(future.map(|v| Box::new(v) as Box))) 197 | .map(|result| *result.downcast().unwrap()) 198 | } 199 | 200 | fn do_first_poll(mut future: BoxFuture) -> Result, Pending> { 201 | let future_state = Rc::new(RefCell::new(FutureState::Ready(None))); 202 | 203 | match future.as_mut().poll(&mut Context::from_waker( 204 | &Arc::new(FutureStateWaker(future_state.clone())).into(), 205 | )) { 206 | Poll::Pending => { 207 | let (pending, cancel, ready) = isyswasfa::make_task(); 208 | *future_state.borrow_mut() = FutureState::Pending { 209 | ready: Some(ready), 210 | future, 211 | cancel_states: Vec::new(), 212 | }; 213 | 214 | push_listens(&future_state); 215 | 216 | push_output(PollOutput::Pending(PollOutputPending { 217 | cancel, 218 | state: u32::try_from(Rc::into_raw(future_state) as usize).unwrap(), 219 | })); 220 | 221 | Err(pending) 222 | } 223 | Poll::Ready(result) => { 224 | clear_pending(); 225 | Ok(result) 226 | } 227 | } 228 | } 229 | 230 | pub fn spawn(future: impl Future + 'static) { 231 | let pending = take_pending(); 232 | drop(first_poll(future)); 233 | set_pending(pending); 234 | } 235 | 236 | pub fn get_ready(ready: Ready) -> T { 237 | match unsafe { Rc::from_raw(ready.state() as usize as *const RefCell) } 238 | .borrow_mut() 239 | .deref_mut() 240 | { 241 | FutureState::Ready(value) => *value.take().unwrap().downcast().unwrap(), 242 | _ => unreachable!(), 243 | } 244 | } 245 | 246 | fn cancel_all(cancels: &[Rc>]) { 247 | for cancel in cancels { 248 | match mem::replace(cancel.borrow_mut().deref_mut(), CancelState::Cancel) { 249 | CancelState::Pending | CancelState::Cancel => {} 250 | CancelState::Listening(cancel) => push_output(PollOutput::Cancel(cancel)), 251 | } 252 | } 253 | } 254 | 255 | pub fn poll(input: Vec) -> Vec { 256 | for input in input { 257 | match input { 258 | PollInput::Listening(PollInputListening { state, cancel }) => { 259 | let listen_state = 260 | unsafe { (state as usize as *const ListenState).as_ref().unwrap() }; 261 | 262 | let listening = match listen_state.cancel_state.borrow().deref() { 263 | CancelState::Pending => true, 264 | CancelState::Cancel => false, 265 | CancelState::Listening(_) => unreachable!(), 266 | }; 267 | 268 | if listening { 269 | match listen_state.future_state.borrow_mut().deref_mut() { 270 | FutureState::Pending { cancel_states, .. } => { 271 | cancel_states.push(listen_state.cancel_state.clone()) 272 | } 273 | _ => unreachable!(), 274 | } 275 | 276 | *listen_state.cancel_state.borrow_mut() = CancelState::Listening(cancel) 277 | } else { 278 | push_output(PollOutput::Cancel(cancel)); 279 | } 280 | } 281 | PollInput::Ready(PollInputReady { state, ready }) => { 282 | let listen_state = *unsafe { Box::from_raw(state as usize as *mut ListenState) }; 283 | 284 | match mem::replace( 285 | listen_state.cancel_state.borrow_mut().deref_mut(), 286 | CancelState::Cancel, 287 | ) { 288 | CancelState::Pending | CancelState::Listening(_) => { 289 | drop(listen_state.tx.send(ready)) 290 | } 291 | CancelState::Cancel => {} 292 | } 293 | 294 | insert_pollable(listen_state.future_state); 295 | } 296 | PollInput::Cancel(PollInputCancel { state, cancel }) => { 297 | let future_state = 298 | unsafe { Rc::from_raw(state as usize as *const RefCell) }; 299 | 300 | let mut old = mem::replace( 301 | future_state.borrow_mut().deref_mut(), 302 | FutureState::Cancelled(Some(cancel)), 303 | ); 304 | 305 | match &mut old { 306 | FutureState::Pending { cancel_states, .. } => cancel_all(cancel_states), 307 | FutureState::Cancelled(_) => unreachable!(), 308 | FutureState::Ready(ready) => drop(ready.take()), 309 | } 310 | } 311 | PollInput::CancelComplete(state) => unsafe { 312 | drop(Box::from_raw(state as usize as *mut ListenState)) 313 | }, 314 | } 315 | } 316 | 317 | loop { 318 | let pollables = take_pollables(); 319 | 320 | if pollables.is_empty() { 321 | break take_output(); 322 | } else { 323 | for ByAddress(future_state) in pollables { 324 | let poll = match future_state.borrow_mut().deref_mut() { 325 | FutureState::Pending { future, .. } => { 326 | future.as_mut().poll(&mut Context::from_waker( 327 | &Arc::new(FutureStateWaker(future_state.clone())).into(), 328 | )) 329 | } 330 | _ => continue, 331 | }; 332 | 333 | match poll { 334 | Poll::Pending => push_listens(&future_state), 335 | Poll::Ready(result) => { 336 | clear_pending(); 337 | 338 | let mut old = mem::replace( 339 | future_state.borrow_mut().deref_mut(), 340 | FutureState::Ready(Some(result)), 341 | ); 342 | 343 | let FutureState::Pending { 344 | ready, 345 | cancel_states, 346 | .. 347 | } = &mut old 348 | else { 349 | unreachable!() 350 | }; 351 | 352 | cancel_all(cancel_states); 353 | 354 | push_output(PollOutput::Ready(PollOutputReady { 355 | ready: ready.take().unwrap(), 356 | state: u32::try_from(Rc::into_raw(future_state) as usize).unwrap(), 357 | })); 358 | } 359 | } 360 | } 361 | } 362 | } 363 | } 364 | 365 | pub async fn await_ready(pending: Pending) -> Ready { 366 | let (tx, rx) = oneshot::channel(); 367 | let cancel_state = Rc::new(RefCell::new(CancelState::Pending)); 368 | push_pending(PendingState { 369 | pending, 370 | tx, 371 | cancel_state: cancel_state.clone(), 372 | }); 373 | let _cancel_on_drop = CancelOnDrop(cancel_state); 374 | rx.await.unwrap() 375 | } 376 | 377 | impl IntoFuture for Pollable { 378 | type Output = (); 379 | // TODO: use a custom future here to avoid the overhead of boxing 380 | type IntoFuture = Pin + 'static>>; 381 | 382 | fn into_future(self) -> Self::IntoFuture { 383 | let v = poll::block_isyswasfa_start(&self); 384 | Box::pin(async move { 385 | let _self = self; 386 | match v { 387 | Ok(()) => (), 388 | Err(pending) => poll::block_isyswasfa_result(await_ready(pending).await), 389 | } 390 | }) 391 | } 392 | } 393 | 394 | const READ_SIZE: u64 = 64 * 1024; 395 | 396 | pub fn sink(stream: OutputStream) -> impl Sink, Error = StreamError> { 397 | Box::pin(sink::unfold(stream, { 398 | move |stream, chunk: Vec| async move { 399 | let mut offset = 0; 400 | let mut flushing = false; 401 | 402 | loop { 403 | match stream.check_write() { 404 | Ok(0) => stream.subscribe().await, 405 | Ok(count) => { 406 | if offset == chunk.len() { 407 | if flushing { 408 | break Ok(stream); 409 | } else { 410 | stream.flush().expect("stream should be flushable"); 411 | flushing = true; 412 | } 413 | } else { 414 | let count = usize::try_from(count).unwrap().min(chunk.len() - offset); 415 | 416 | match stream.write(&chunk[offset..][..count]) { 417 | Ok(()) => { 418 | offset += count; 419 | } 420 | Err(e) => break Err(e), 421 | } 422 | } 423 | } 424 | Err(e) => break Err(e), 425 | } 426 | } 427 | } 428 | })) 429 | } 430 | 431 | pub fn stream(stream: InputStream) -> impl Stream, StreamError>> { 432 | Box::pin(stream::unfold(stream, |stream| async move { 433 | loop { 434 | match stream.read(READ_SIZE) { 435 | Ok(buffer) => { 436 | if buffer.is_empty() { 437 | stream.subscribe().await; 438 | } else { 439 | break Some((Ok(buffer), stream)); 440 | } 441 | } 442 | Err(StreamError::Closed) => break None, 443 | Err(e) => break Some((Err(e), stream)), 444 | } 445 | } 446 | })) 447 | } 448 | 449 | pub async fn copy(rx: &InputStream, tx: &OutputStream) -> Result<(), StreamError> { 450 | // TODO: use `OutputStream::splice` 451 | while let Some(chunk) = read(rx, READ_SIZE).await? { 452 | write_all(tx, &chunk).await?; 453 | } 454 | Ok(()) 455 | } 456 | 457 | pub async fn write_all(tx: &OutputStream, chunk: &[u8]) -> Result<(), StreamError> { 458 | let mut offset = 0; 459 | while offset < chunk.len() { 460 | let count = usize::try_from(tx.check_write()?) 461 | .unwrap() 462 | .min(chunk.len() - offset); 463 | 464 | if count > 0 { 465 | tx.write(&chunk[offset..][..count])?; 466 | offset += count 467 | } else { 468 | tx.subscribe().await 469 | } 470 | } 471 | Ok(()) 472 | } 473 | 474 | pub async fn read(rx: &InputStream, max: u64) -> Result>, StreamError> { 475 | loop { 476 | match rx.read(max) { 477 | Ok(chunk) if chunk.is_empty() => rx.subscribe().await, 478 | Ok(chunk) => break Ok(Some(chunk)), 479 | Err(StreamError::Closed) => break Ok(None), 480 | Err(error) => break Err(error), 481 | } 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /host/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "isyswasfa-host" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.79" 8 | async-trait = "0.1.77" 9 | bytes = "1.5.0" 10 | futures = "0.3.30" 11 | once_cell = "1.19.0" 12 | tracing = "0.1.40" 13 | wasmparser = "0.120.0" 14 | wasmtime = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa", features = ["component-model"] } 15 | wasmtime-wasi = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa" } 16 | -------------------------------------------------------------------------------- /http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "isyswasfa-http" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-trait = "0.1.77" 8 | wasmtime = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa", features = ["component-model"] } 9 | wasmtime-wasi = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa" } 10 | isyswasfa-host = { path = "../host" } 11 | futures = "0.3.30" 12 | anyhow = "1.0.80" 13 | -------------------------------------------------------------------------------- /http/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | wasmtime::component::bindgen!({ 4 | path: "../wit", 5 | interfaces: " 6 | import wasi:http/types@0.3.0-draft; 7 | import wasi:http/handler@0.3.0-draft; 8 | ", 9 | isyswasfa: true, 10 | with: { 11 | "wasi:io/error": wasmtime_wasi::preview2::bindings::wasi::io::error, 12 | "wasi:io/streams": wasmtime_wasi::preview2::bindings::wasi::io::streams, 13 | "wasi:http/types/body": Body, 14 | "wasi:http/types/request": Request, 15 | "wasi:http/types/request-options": RequestOptions, 16 | "wasi:http/types/response": Response, 17 | "wasi:http/types/fields": Fields, 18 | "wasi:http/types/isyswasfa-receiver-own-trailers": FieldsReceiver, 19 | "wasi:http/types/isyswasfa-sender-own-trailers": FieldsSender, 20 | } 21 | }); 22 | 23 | use { 24 | anyhow::anyhow, 25 | futures::channel::oneshot, 26 | isyswasfa_host::IsyswasfaView, 27 | std::{fmt, future::Future, mem}, 28 | wasi::http::types::{ErrorCode, HeaderError, Method, RequestOptionsError, Scheme}, 29 | wasmtime::component::{Linker, Resource, ResourceTable}, 30 | wasmtime_wasi::preview2::{bindings::wasi::io::error::Error, InputStream}, 31 | }; 32 | 33 | impl fmt::Display for Scheme { 34 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 35 | write!( 36 | f, 37 | "{}", 38 | match self { 39 | Scheme::Http => "http", 40 | Scheme::Https => "https", 41 | Scheme::Other(s) => s, 42 | } 43 | ) 44 | } 45 | } 46 | 47 | pub trait WasiHttpView: IsyswasfaView { 48 | fn table(&mut self) -> &mut ResourceTable; 49 | 50 | fn send_request( 51 | &mut self, 52 | request: Resource, 53 | ) -> wasmtime::Result< 54 | impl Future< 55 | Output = impl FnOnce( 56 | &mut Self, 57 | ) 58 | -> wasmtime::Result, ErrorCode>> 59 | + 'static, 60 | > + Send 61 | + 'static, 62 | >; 63 | } 64 | 65 | pub struct Body { 66 | pub stream: Option, 67 | pub trailers: Option, 68 | } 69 | 70 | #[derive(Clone)] 71 | pub struct Fields(pub Vec<(String, Vec)>); 72 | 73 | pub struct FieldsSender(pub oneshot::Sender); 74 | 75 | pub struct FieldsReceiver(pub oneshot::Receiver); 76 | 77 | #[derive(Default, Copy, Clone)] 78 | pub struct RequestOptions { 79 | pub connect_timeout: Option, 80 | pub first_byte_timeout: Option, 81 | pub between_bytes_timeout: Option, 82 | } 83 | 84 | pub struct Request { 85 | pub method: Method, 86 | pub scheme: Option, 87 | pub path_with_query: Option, 88 | pub authority: Option, 89 | pub headers: Fields, 90 | pub body: Body, 91 | pub options: Option, 92 | } 93 | 94 | pub struct Response { 95 | pub status_code: u16, 96 | pub headers: Fields, 97 | pub body: Body, 98 | } 99 | 100 | impl wasi::http::types::HostIsyswasfaSenderOwnTrailers for T { 101 | fn send( 102 | &mut self, 103 | this: Resource, 104 | fields: Resource, 105 | ) -> wasmtime::Result<()> { 106 | let sender = self.table().delete(this)?; 107 | let fields = self.table().delete(fields)?; 108 | _ = sender.0.send(fields); 109 | Ok(()) 110 | } 111 | 112 | fn drop(&mut self, this: Resource) -> wasmtime::Result<()> { 113 | self.table().delete(this)?; 114 | Ok(()) 115 | } 116 | } 117 | 118 | impl wasi::http::types::HostIsyswasfaReceiverOwnTrailers for T { 119 | fn receive( 120 | &mut self, 121 | this: Resource, 122 | ) -> wasmtime::Result< 123 | impl Future< 124 | Output = impl FnOnce(&mut Self) -> wasmtime::Result>> + 'static, 125 | > + Send 126 | + 'static, 127 | > { 128 | let receiver = self.table().delete(this)?; 129 | Ok(async move { 130 | let fields = receiver.0.await; 131 | move |self_: &mut Self| { 132 | Ok(if let Ok(fields) = fields { 133 | Some(self_.table().push(fields)?) 134 | } else { 135 | None 136 | }) 137 | } 138 | }) 139 | } 140 | 141 | fn drop(&mut self, this: Resource) -> wasmtime::Result<()> { 142 | self.table().delete(this)?; 143 | Ok(()) 144 | } 145 | } 146 | 147 | impl wasi::http::types::HostFields for T { 148 | fn new(&mut self) -> wasmtime::Result> { 149 | Ok(self.table().push(Fields(Vec::new()))?) 150 | } 151 | 152 | fn from_list( 153 | &mut self, 154 | list: Vec<(String, Vec)>, 155 | ) -> wasmtime::Result, HeaderError>> { 156 | Ok(Ok(self.table().push(Fields(list))?)) 157 | } 158 | 159 | fn get(&mut self, this: Resource, key: String) -> wasmtime::Result>> { 160 | Ok(self 161 | .table() 162 | .get(&this)? 163 | .0 164 | .iter() 165 | .filter(|(k, _)| *k == key) 166 | .map(|(_, v)| v.clone()) 167 | .collect()) 168 | } 169 | 170 | fn has(&mut self, this: Resource, key: String) -> wasmtime::Result { 171 | Ok(self.table().get(&this)?.0.iter().any(|(k, _)| *k == key)) 172 | } 173 | 174 | fn set( 175 | &mut self, 176 | this: Resource, 177 | key: String, 178 | values: Vec>, 179 | ) -> wasmtime::Result> { 180 | let fields = self.table().get_mut(&this)?; 181 | fields.0.retain(|(k, _)| *k != key); 182 | fields 183 | .0 184 | .extend(values.into_iter().map(|v| (key.clone(), v))); 185 | Ok(Ok(())) 186 | } 187 | 188 | fn delete( 189 | &mut self, 190 | this: Resource, 191 | key: String, 192 | ) -> wasmtime::Result>, HeaderError>> { 193 | let fields = self.table().get_mut(&this)?; 194 | let (matched, unmatched) = mem::take(&mut fields.0) 195 | .into_iter() 196 | .partition(|(k, _)| *k == key); 197 | fields.0 = unmatched; 198 | Ok(Ok(matched.into_iter().map(|(_, v)| v).collect())) 199 | } 200 | 201 | fn append( 202 | &mut self, 203 | this: Resource, 204 | key: String, 205 | value: Vec, 206 | ) -> wasmtime::Result> { 207 | self.table().get_mut(&this)?.0.push((key, value)); 208 | Ok(Ok(())) 209 | } 210 | 211 | fn entries(&mut self, this: Resource) -> wasmtime::Result)>> { 212 | Ok(self.table().get(&this)?.0.clone()) 213 | } 214 | 215 | fn clone(&mut self, this: Resource) -> wasmtime::Result> { 216 | let entries = self.table().get(&this)?.0.clone(); 217 | Ok(self.table().push(Fields(entries))?) 218 | } 219 | 220 | fn drop(&mut self, this: Resource) -> wasmtime::Result<()> { 221 | self.table().delete(this)?; 222 | Ok(()) 223 | } 224 | } 225 | 226 | impl wasi::http::types::HostBody for T { 227 | fn new( 228 | &mut self, 229 | stream: Resource, 230 | trailers: Option>, 231 | ) -> wasmtime::Result> { 232 | let stream = self.table().delete(stream)?; 233 | let trailers = if let Some(trailers) = trailers { 234 | Some(self.table().delete(trailers)?) 235 | } else { 236 | None 237 | }; 238 | 239 | Ok(self.table().push(Body { 240 | stream: Some(stream), 241 | trailers, 242 | })?) 243 | } 244 | 245 | fn stream( 246 | &mut self, 247 | this: Resource, 248 | ) -> wasmtime::Result, ()>> { 249 | // TODO: This should return a child handle 250 | let stream = self.table().get_mut(&this)?.stream.take().ok_or_else(|| { 251 | anyhow!("todo: allow wasi:http/types#body.stream to be called multiple times") 252 | })?; 253 | 254 | Ok(Ok(self.table().push(stream)?)) 255 | } 256 | 257 | fn finish( 258 | &mut self, 259 | this: Resource, 260 | ) -> wasmtime::Result< 261 | impl Future< 262 | Output = impl FnOnce( 263 | &mut Self, 264 | ) 265 | -> wasmtime::Result>, ErrorCode>> 266 | + 'static, 267 | > + Send 268 | + 'static, 269 | > { 270 | let trailers = self.table().delete(this)?.trailers; 271 | Ok(async move { 272 | let trailers = if let Some(trailers) = trailers { 273 | if let Ok(trailers) = trailers.0.await { 274 | Some(trailers) 275 | } else { 276 | None 277 | } 278 | } else { 279 | None 280 | }; 281 | 282 | move |self_: &mut Self| { 283 | Ok(Ok(if let Some(trailers) = trailers { 284 | Some(self_.table().push(trailers)?) 285 | } else { 286 | None 287 | })) 288 | } 289 | }) 290 | } 291 | 292 | fn drop(&mut self, this: Resource) -> wasmtime::Result<()> { 293 | self.table().delete(this)?; 294 | Ok(()) 295 | } 296 | } 297 | 298 | impl wasi::http::types::HostRequest for T { 299 | fn new( 300 | &mut self, 301 | headers: Resource, 302 | body: Resource, 303 | options: Option>, 304 | ) -> wasmtime::Result> { 305 | let headers = self.table().delete(headers)?; 306 | let body = self.table().delete(body)?; 307 | let options = if let Some(options) = options { 308 | Some(self.table().delete(options)?) 309 | } else { 310 | None 311 | }; 312 | 313 | Ok(self.table().push(Request { 314 | method: Method::Get, 315 | scheme: None, 316 | path_with_query: None, 317 | authority: None, 318 | headers, 319 | body, 320 | options, 321 | })?) 322 | } 323 | 324 | fn method(&mut self, this: Resource) -> wasmtime::Result { 325 | Ok(self.table().get(&this)?.method.clone()) 326 | } 327 | 328 | fn set_method( 329 | &mut self, 330 | this: Resource, 331 | method: Method, 332 | ) -> wasmtime::Result> { 333 | self.table().get_mut(&this)?.method = method; 334 | Ok(Ok(())) 335 | } 336 | 337 | fn scheme(&mut self, this: Resource) -> wasmtime::Result> { 338 | Ok(self.table().get(&this)?.scheme.clone()) 339 | } 340 | 341 | fn set_scheme( 342 | &mut self, 343 | this: Resource, 344 | scheme: Option, 345 | ) -> wasmtime::Result> { 346 | self.table().get_mut(&this)?.scheme = scheme; 347 | Ok(Ok(())) 348 | } 349 | 350 | fn path_with_query(&mut self, this: Resource) -> wasmtime::Result> { 351 | Ok(self.table().get(&this)?.path_with_query.clone()) 352 | } 353 | 354 | fn set_path_with_query( 355 | &mut self, 356 | this: Resource, 357 | path_with_query: Option, 358 | ) -> wasmtime::Result> { 359 | self.table().get_mut(&this)?.path_with_query = path_with_query; 360 | Ok(Ok(())) 361 | } 362 | 363 | fn authority(&mut self, this: Resource) -> wasmtime::Result> { 364 | Ok(self.table().get(&this)?.authority.clone()) 365 | } 366 | 367 | fn set_authority( 368 | &mut self, 369 | this: Resource, 370 | authority: Option, 371 | ) -> wasmtime::Result> { 372 | self.table().get_mut(&this)?.authority = authority; 373 | Ok(Ok(())) 374 | } 375 | 376 | fn options( 377 | &mut self, 378 | this: Resource, 379 | ) -> wasmtime::Result>> { 380 | // TODO: This should return an immutable child handle 381 | let options = self.table().get(&this)?.options; 382 | Ok(if let Some(options) = options { 383 | Some(self.table().push(options)?) 384 | } else { 385 | None 386 | }) 387 | } 388 | 389 | fn headers(&mut self, this: Resource) -> wasmtime::Result> { 390 | // TODO: This should return an immutable child handle 391 | let headers = self.table().get(&this)?.headers.clone(); 392 | Ok(self.table().push(headers)?) 393 | } 394 | 395 | fn body(&mut self, _this: Resource) -> wasmtime::Result> { 396 | Err(anyhow!("todo: implement wasi:http/types#request.body")) 397 | } 398 | 399 | fn into_parts( 400 | &mut self, 401 | this: Resource, 402 | ) -> wasmtime::Result<(Resource, Resource)> { 403 | let request = self.table().delete(this)?; 404 | let headers = self.table().push(request.headers)?; 405 | let body = self.table().push(request.body)?; 406 | Ok((headers, body)) 407 | } 408 | 409 | fn drop(&mut self, this: Resource) -> wasmtime::Result<()> { 410 | self.table().delete(this)?; 411 | Ok(()) 412 | } 413 | } 414 | 415 | impl wasi::http::types::HostResponse for T { 416 | fn new( 417 | &mut self, 418 | headers: Resource, 419 | body: Resource, 420 | ) -> wasmtime::Result> { 421 | let headers = self.table().delete(headers)?; 422 | let body = self.table().delete(body)?; 423 | 424 | Ok(self.table().push(Response { 425 | status_code: 200, 426 | headers, 427 | body, 428 | })?) 429 | } 430 | 431 | fn status_code(&mut self, this: Resource) -> wasmtime::Result { 432 | Ok(self.table().get(&this)?.status_code) 433 | } 434 | 435 | fn set_status_code( 436 | &mut self, 437 | this: Resource, 438 | status_code: u16, 439 | ) -> wasmtime::Result> { 440 | self.table().get_mut(&this)?.status_code = status_code; 441 | Ok(Ok(())) 442 | } 443 | 444 | fn headers(&mut self, this: Resource) -> wasmtime::Result> { 445 | // TODO: This should return an immutable child handle 446 | let headers = self.table().get(&this)?.headers.clone(); 447 | Ok(self.table().push(headers)?) 448 | } 449 | 450 | fn body(&mut self, _this: Resource) -> wasmtime::Result> { 451 | Err(anyhow!("todo: implement wasi:http/types#response.body")) 452 | } 453 | 454 | fn into_parts( 455 | &mut self, 456 | this: Resource, 457 | ) -> wasmtime::Result<(Resource, Resource)> { 458 | let response = self.table().delete(this)?; 459 | let headers = self.table().push(response.headers)?; 460 | let body = self.table().push(response.body)?; 461 | Ok((headers, body)) 462 | } 463 | 464 | fn drop(&mut self, this: Resource) -> wasmtime::Result<()> { 465 | self.table().delete(this)?; 466 | Ok(()) 467 | } 468 | } 469 | 470 | impl wasi::http::types::HostRequestOptions for T { 471 | fn new(&mut self) -> wasmtime::Result> { 472 | Ok(self.table().push(RequestOptions::default())?) 473 | } 474 | 475 | fn connect_timeout(&mut self, this: Resource) -> wasmtime::Result> { 476 | Ok(self.table().get(&this)?.connect_timeout) 477 | } 478 | 479 | fn set_connect_timeout( 480 | &mut self, 481 | this: Resource, 482 | connect_timeout: Option, 483 | ) -> wasmtime::Result> { 484 | self.table().get_mut(&this)?.connect_timeout = connect_timeout; 485 | Ok(Ok(())) 486 | } 487 | 488 | fn first_byte_timeout( 489 | &mut self, 490 | this: Resource, 491 | ) -> wasmtime::Result> { 492 | Ok(self.table().get(&this)?.first_byte_timeout) 493 | } 494 | 495 | fn set_first_byte_timeout( 496 | &mut self, 497 | this: Resource, 498 | first_byte_timeout: Option, 499 | ) -> wasmtime::Result> { 500 | self.table().get_mut(&this)?.first_byte_timeout = first_byte_timeout; 501 | Ok(Ok(())) 502 | } 503 | 504 | fn between_bytes_timeout( 505 | &mut self, 506 | this: Resource, 507 | ) -> wasmtime::Result> { 508 | Ok(self.table().get(&this)?.between_bytes_timeout) 509 | } 510 | 511 | fn set_between_bytes_timeout( 512 | &mut self, 513 | this: Resource, 514 | between_bytes_timeout: Option, 515 | ) -> wasmtime::Result> { 516 | self.table().get_mut(&this)?.between_bytes_timeout = between_bytes_timeout; 517 | Ok(Ok(())) 518 | } 519 | 520 | fn drop(&mut self, this: Resource) -> wasmtime::Result<()> { 521 | self.table().delete(this)?; 522 | Ok(()) 523 | } 524 | } 525 | 526 | impl wasi::http::types::Host for T { 527 | fn isyswasfa_pipe_own_trailers( 528 | &mut self, 529 | ) -> wasmtime::Result<(Resource, Resource)> { 530 | let (tx, rx) = oneshot::channel(); 531 | let tx = self.table().push(FieldsSender(tx))?; 532 | let rx = self.table().push(FieldsReceiver(rx))?; 533 | Ok((tx, rx)) 534 | } 535 | 536 | fn http_error_code(&mut self, _error: Resource) -> wasmtime::Result> { 537 | Err(anyhow!("todo: implement wasi:http/types#http-error-code")) 538 | } 539 | } 540 | 541 | impl wasi::http::handler::Host for T { 542 | fn handle( 543 | &mut self, 544 | request: Resource, 545 | ) -> wasmtime::Result< 546 | impl Future< 547 | Output = impl FnOnce( 548 | &mut Self, 549 | ) 550 | -> wasmtime::Result, ErrorCode>> 551 | + 'static, 552 | > + Send 553 | + 'static, 554 | > { 555 | self.send_request(request) 556 | } 557 | } 558 | 559 | pub fn add_to_linker(linker: &mut Linker) -> wasmtime::Result<()> { 560 | wasi::http::types::add_to_linker(linker, |ctx| ctx)?; 561 | wasi::http::handler::add_to_linker(linker, |ctx| ctx) 562 | } 563 | -------------------------------------------------------------------------------- /test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "isyswasfa-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dev-dependencies] 7 | anyhow = { version = "1.0.79", features = ["backtrace"] } 8 | async-trait = "0.1.77" 9 | tokio = { version = "1.35.1", features = ["rt-multi-thread", "macros", "process", "fs", "time"] } 10 | wasmparser = "0.119.0" 11 | wasmtime = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa", features = ["component-model"] } 12 | wasmtime-wasi = { git = "https://github.com/dicej/wasmtime", branch = "isyswasfa" } 13 | isyswasfa-host = { path = "../host" } 14 | isyswasfa-http = { path = "../http" } 15 | wit-component = "0.20.0" 16 | reqwest = "0.11.23" 17 | bytes = "1.5.0" 18 | futures = "0.3.30" 19 | wasm-compose = { path = "../wasm-tools/crates/wasm-compose" } 20 | tempfile = "3.9.0" 21 | indexmap = "2.2.1" 22 | flate2 = "1.0.28" 23 | componentize-py = { git = "https://github.com/bytecodealliance/componentize-py", branch = "isyswasfa-and-stub-wasi" } 24 | sha2 = "0.10.8" 25 | hex = "0.4.3" 26 | pretty_env_logger = "0.5.0" 27 | -------------------------------------------------------------------------------- /test/python-cases/echo/app.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import hashlib 3 | import isyswasfa_guest 4 | 5 | from proxy import exports 6 | from proxy.imports import pipe, handler 7 | from proxy.imports.streams import OutputStream 8 | from proxy.imports.types import ( 9 | Request, Response, Fields, Body, Scheme, Scheme_Http, Scheme_Https, Scheme_Other, Method_Post 10 | ) 11 | from isyswasfa_guest.streams import Sink, Stream 12 | from urllib import parse 13 | 14 | class Handler(exports.Handler): 15 | async def handle(self, request: Request) -> Response: 16 | headers = request.headers().entries() 17 | method = request.method() 18 | path = request.path_with_query() 19 | filtered_headers = Fields.from_list(list(filter(lambda pair: pair[0] == "content-type", headers))) 20 | 21 | if isinstance(method, Method_Post) and path == "/echo": 22 | # Echo the request body back to the client without buffering. 23 | return Response(filtered_headers, Request.into_parts(request)[1]) 24 | elif isinstance(method, Method_Post) and path == "/double-echo": 25 | # Pipe the request body to an outgoing request and stream the response back to the client. 26 | urls = list(map(lambda pair: str(pair[1], "utf-8"), filter(lambda pair: pair[0] == "url", headers))) 27 | if len(urls) == 1: 28 | url = urls[0] 29 | request = Request(filtered_headers, Request.into_parts(request)[1], None) 30 | request.set_method(method) 31 | 32 | url_parsed = parse.urlparse(url) 33 | 34 | match url_parsed.scheme: 35 | case "http": 36 | scheme: Scheme = Scheme_Http() 37 | case "https": 38 | scheme = Scheme_Https() 39 | case _: 40 | scheme = Scheme_Other(url_parsed.scheme) 41 | 42 | request.set_scheme(scheme) 43 | request.set_authority(url_parsed.netloc) 44 | request.set_path_with_query(url_parsed.path) 45 | 46 | return await handler.handle(request) 47 | else: 48 | return respond(400) 49 | else: 50 | return respond(405) 51 | 52 | def respond(status: int) -> Response: 53 | response = Response(Fields(), Body(pipe.make_pipe()[1], None)) 54 | response.set_status_code(status) 55 | return response 56 | -------------------------------------------------------------------------------- /test/python-cases/hash-all/app.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import hashlib 3 | import isyswasfa_guest 4 | 5 | from proxy import exports 6 | from proxy.imports import pipe, handler 7 | from proxy.imports.streams import OutputStream 8 | from proxy.imports.types import Request, Response, Fields, Body, Scheme, Scheme_Http, Scheme_Https, Scheme_Other 9 | from isyswasfa_guest.streams import Sink, Stream 10 | from typing import Tuple 11 | from urllib import parse 12 | 13 | class Handler(exports.Handler): 14 | async def handle(self, request: Request) -> Response: 15 | """ 16 | Send outgoing GET requests concurrently to the URLs specified as headers and stream the hashes of the 17 | response bodies as they arrive. 18 | """ 19 | response_body_tx, response_body_rx = pipe.make_pipe() 20 | 21 | isyswasfa_guest.spawn(hash_all(request, response_body_tx)) 22 | 23 | return Response(Fields.from_list([("content-type", b"text/plain")]), Body(response_body_rx, None)) 24 | 25 | async def hash_all(request: Request, tx: OutputStream): 26 | with Sink(tx) as sink: 27 | headers = request.headers().entries() 28 | 29 | urls = map(lambda pair: str(pair[1], "utf-8"), filter(lambda pair: pair[0] == "url", headers)) 30 | 31 | for result in asyncio.as_completed(map(sha256, urls)): 32 | url, sha = await result 33 | await sink.send(bytes(f"{url}: {sha}\n", "utf-8")) 34 | 35 | async def sha256(url: str) -> Tuple[str, str]: 36 | """Download the contents of the specified URL, computing the SHA-256 37 | incrementally as the response body arrives. 38 | 39 | This returns a tuple of the original URL and either the hex-encoded hash or 40 | an error message. 41 | """ 42 | url_parsed = parse.urlparse(url) 43 | 44 | match url_parsed.scheme: 45 | case "http": 46 | scheme: Scheme = Scheme_Http() 47 | case "https": 48 | scheme = Scheme_Https() 49 | case _: 50 | scheme = Scheme_Other(url_parsed.scheme) 51 | 52 | request = Request(Fields(), Body(pipe.make_pipe()[1], None), None) 53 | request.set_scheme(scheme) 54 | request.set_authority(url_parsed.netloc) 55 | request.set_path_with_query(url_parsed.path) 56 | 57 | response = await handler.handle(request) 58 | status = response.status_code() 59 | if status < 200 or status > 299: 60 | return url, f"unexpected status: {status}" 61 | 62 | _headers, body = Response.into_parts(response) 63 | 64 | with Stream(body.stream()) as stream: 65 | hasher = hashlib.sha256() 66 | while True: 67 | chunk = await stream.next() 68 | if chunk is None: 69 | return url, hasher.hexdigest() 70 | else: 71 | hasher.update(chunk) 72 | -------------------------------------------------------------------------------- /test/python-cases/round-trip/app.py: -------------------------------------------------------------------------------- 1 | from round_trip import exports 2 | from round_trip.imports import baz, isyswasfa_io_poll, monotonic_clock 3 | 4 | class Baz(exports.Baz): 5 | async def foo(self, s: str) -> str: 6 | await isyswasfa_io_poll.block(monotonic_clock.subscribe_duration(10_000_000)) 7 | 8 | v = await baz.foo(f"{s} - entered guest") 9 | 10 | return f"{v} - exited guest" 11 | -------------------------------------------------------------------------------- /test/python-cases/service/app.py: -------------------------------------------------------------------------------- 1 | from proxy import exports 2 | from proxy.imports.types import Request, Response 3 | 4 | class Handler(exports.Handler): 5 | async def handle(self, request: Request) -> Response: 6 | """Return a response which echoes the request headers, body, and trailers.""" 7 | headers, body = Request.into_parts(request) 8 | return Response(headers, body) 9 | -------------------------------------------------------------------------------- /test/rust-cases/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "anyhow" 13 | version = "1.0.81" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" 16 | 17 | [[package]] 18 | name = "async-trait" 19 | version = "0.1.78" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" 22 | dependencies = [ 23 | "proc-macro2", 24 | "quote", 25 | "syn", 26 | ] 27 | 28 | [[package]] 29 | name = "autocfg" 30 | version = "1.1.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 33 | 34 | [[package]] 35 | name = "bitflags" 36 | version = "2.5.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 39 | 40 | [[package]] 41 | name = "block-buffer" 42 | version = "0.10.4" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 45 | dependencies = [ 46 | "generic-array", 47 | ] 48 | 49 | [[package]] 50 | name = "by_address" 51 | version = "1.1.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "bf8dba2868114ed769a1f2590fc9ae5eb331175b44313b6c9b922f8f7ca813d0" 54 | 55 | [[package]] 56 | name = "cfg-if" 57 | version = "1.0.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 60 | 61 | [[package]] 62 | name = "cpufeatures" 63 | version = "0.2.12" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 66 | dependencies = [ 67 | "libc", 68 | ] 69 | 70 | [[package]] 71 | name = "crc32fast" 72 | version = "1.4.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" 75 | dependencies = [ 76 | "cfg-if", 77 | ] 78 | 79 | [[package]] 80 | name = "crypto-common" 81 | version = "0.1.6" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 84 | dependencies = [ 85 | "generic-array", 86 | "typenum", 87 | ] 88 | 89 | [[package]] 90 | name = "digest" 91 | version = "0.10.7" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 94 | dependencies = [ 95 | "block-buffer", 96 | "crypto-common", 97 | ] 98 | 99 | [[package]] 100 | name = "echo" 101 | version = "0.1.0" 102 | dependencies = [ 103 | "async-trait", 104 | "futures", 105 | "isyswasfa-guest", 106 | "url", 107 | "wit-bindgen", 108 | ] 109 | 110 | [[package]] 111 | name = "equivalent" 112 | version = "1.0.1" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 115 | 116 | [[package]] 117 | name = "flate2" 118 | version = "1.0.28" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 121 | dependencies = [ 122 | "crc32fast", 123 | "miniz_oxide", 124 | ] 125 | 126 | [[package]] 127 | name = "form_urlencoded" 128 | version = "1.2.1" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 131 | dependencies = [ 132 | "percent-encoding", 133 | ] 134 | 135 | [[package]] 136 | name = "futures" 137 | version = "0.3.30" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 140 | dependencies = [ 141 | "futures-channel", 142 | "futures-core", 143 | "futures-executor", 144 | "futures-io", 145 | "futures-sink", 146 | "futures-task", 147 | "futures-util", 148 | ] 149 | 150 | [[package]] 151 | name = "futures-channel" 152 | version = "0.3.30" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 155 | dependencies = [ 156 | "futures-core", 157 | "futures-sink", 158 | ] 159 | 160 | [[package]] 161 | name = "futures-core" 162 | version = "0.3.30" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 165 | 166 | [[package]] 167 | name = "futures-executor" 168 | version = "0.3.30" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 171 | dependencies = [ 172 | "futures-core", 173 | "futures-task", 174 | "futures-util", 175 | ] 176 | 177 | [[package]] 178 | name = "futures-io" 179 | version = "0.3.30" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 182 | 183 | [[package]] 184 | name = "futures-macro" 185 | version = "0.3.30" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 188 | dependencies = [ 189 | "proc-macro2", 190 | "quote", 191 | "syn", 192 | ] 193 | 194 | [[package]] 195 | name = "futures-sink" 196 | version = "0.3.30" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 199 | 200 | [[package]] 201 | name = "futures-task" 202 | version = "0.3.30" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 205 | 206 | [[package]] 207 | name = "futures-util" 208 | version = "0.3.30" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 211 | dependencies = [ 212 | "futures-channel", 213 | "futures-core", 214 | "futures-io", 215 | "futures-macro", 216 | "futures-sink", 217 | "futures-task", 218 | "memchr", 219 | "pin-project-lite", 220 | "pin-utils", 221 | "slab", 222 | ] 223 | 224 | [[package]] 225 | name = "generic-array" 226 | version = "0.14.7" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 229 | dependencies = [ 230 | "typenum", 231 | "version_check", 232 | ] 233 | 234 | [[package]] 235 | name = "hash-all" 236 | version = "0.1.0" 237 | dependencies = [ 238 | "anyhow", 239 | "async-trait", 240 | "futures", 241 | "hex", 242 | "isyswasfa-guest", 243 | "sha2", 244 | "url", 245 | "wit-bindgen", 246 | ] 247 | 248 | [[package]] 249 | name = "hashbrown" 250 | version = "0.14.3" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 253 | 254 | [[package]] 255 | name = "heck" 256 | version = "0.4.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 259 | dependencies = [ 260 | "unicode-segmentation", 261 | ] 262 | 263 | [[package]] 264 | name = "hex" 265 | version = "0.4.3" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 268 | 269 | [[package]] 270 | name = "id-arena" 271 | version = "2.2.1" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 274 | 275 | [[package]] 276 | name = "idna" 277 | version = "0.5.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 280 | dependencies = [ 281 | "unicode-bidi", 282 | "unicode-normalization", 283 | ] 284 | 285 | [[package]] 286 | name = "indexmap" 287 | version = "2.2.5" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" 290 | dependencies = [ 291 | "equivalent", 292 | "hashbrown", 293 | "serde", 294 | ] 295 | 296 | [[package]] 297 | name = "isyswasfa-guest" 298 | version = "0.1.0" 299 | dependencies = [ 300 | "by_address", 301 | "futures", 302 | "once_cell", 303 | "wit-bindgen", 304 | ] 305 | 306 | [[package]] 307 | name = "isyswasfa-transform" 308 | version = "0.1.0" 309 | source = "git+https://github.com/dicej/isyswasfa-transform#db4fb62de494f96cb68daf081d02e36ae32354fa" 310 | dependencies = [ 311 | "wit-parser", 312 | ] 313 | 314 | [[package]] 315 | name = "itoa" 316 | version = "1.0.10" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 319 | 320 | [[package]] 321 | name = "leb128" 322 | version = "0.2.5" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 325 | 326 | [[package]] 327 | name = "libc" 328 | version = "0.2.153" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 331 | 332 | [[package]] 333 | name = "log" 334 | version = "0.4.21" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 337 | 338 | [[package]] 339 | name = "memchr" 340 | version = "2.7.1" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 343 | 344 | [[package]] 345 | name = "middleware" 346 | version = "0.1.0" 347 | dependencies = [ 348 | "async-trait", 349 | "flate2", 350 | "isyswasfa-guest", 351 | "wit-bindgen", 352 | ] 353 | 354 | [[package]] 355 | name = "miniz_oxide" 356 | version = "0.7.2" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 359 | dependencies = [ 360 | "adler", 361 | ] 362 | 363 | [[package]] 364 | name = "once_cell" 365 | version = "1.19.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 368 | 369 | [[package]] 370 | name = "percent-encoding" 371 | version = "2.3.1" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 374 | 375 | [[package]] 376 | name = "pin-project-lite" 377 | version = "0.2.13" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 380 | 381 | [[package]] 382 | name = "pin-utils" 383 | version = "0.1.0" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 386 | 387 | [[package]] 388 | name = "proc-macro2" 389 | version = "1.0.79" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 392 | dependencies = [ 393 | "unicode-ident", 394 | ] 395 | 396 | [[package]] 397 | name = "quote" 398 | version = "1.0.35" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 401 | dependencies = [ 402 | "proc-macro2", 403 | ] 404 | 405 | [[package]] 406 | name = "round-trip" 407 | version = "0.1.0" 408 | dependencies = [ 409 | "async-trait", 410 | "isyswasfa-guest", 411 | "wit-bindgen", 412 | ] 413 | 414 | [[package]] 415 | name = "router" 416 | version = "0.1.0" 417 | dependencies = [ 418 | "async-trait", 419 | "isyswasfa-guest", 420 | "url", 421 | "wit-bindgen", 422 | ] 423 | 424 | [[package]] 425 | name = "ryu" 426 | version = "1.0.17" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 429 | 430 | [[package]] 431 | name = "semver" 432 | version = "1.0.22" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" 435 | 436 | [[package]] 437 | name = "serde" 438 | version = "1.0.197" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 441 | dependencies = [ 442 | "serde_derive", 443 | ] 444 | 445 | [[package]] 446 | name = "serde_derive" 447 | version = "1.0.197" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 450 | dependencies = [ 451 | "proc-macro2", 452 | "quote", 453 | "syn", 454 | ] 455 | 456 | [[package]] 457 | name = "serde_json" 458 | version = "1.0.114" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" 461 | dependencies = [ 462 | "itoa", 463 | "ryu", 464 | "serde", 465 | ] 466 | 467 | [[package]] 468 | name = "service" 469 | version = "0.1.0" 470 | dependencies = [ 471 | "async-trait", 472 | "isyswasfa-guest", 473 | "wit-bindgen", 474 | ] 475 | 476 | [[package]] 477 | name = "sha2" 478 | version = "0.10.8" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 481 | dependencies = [ 482 | "cfg-if", 483 | "cpufeatures", 484 | "digest", 485 | ] 486 | 487 | [[package]] 488 | name = "slab" 489 | version = "0.4.9" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 492 | dependencies = [ 493 | "autocfg", 494 | ] 495 | 496 | [[package]] 497 | name = "smallvec" 498 | version = "1.13.1" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 501 | 502 | [[package]] 503 | name = "spdx" 504 | version = "0.10.4" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9" 507 | dependencies = [ 508 | "smallvec", 509 | ] 510 | 511 | [[package]] 512 | name = "syn" 513 | version = "2.0.53" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" 516 | dependencies = [ 517 | "proc-macro2", 518 | "quote", 519 | "unicode-ident", 520 | ] 521 | 522 | [[package]] 523 | name = "tinyvec" 524 | version = "1.6.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 527 | dependencies = [ 528 | "tinyvec_macros", 529 | ] 530 | 531 | [[package]] 532 | name = "tinyvec_macros" 533 | version = "0.1.1" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 536 | 537 | [[package]] 538 | name = "typenum" 539 | version = "1.17.0" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 542 | 543 | [[package]] 544 | name = "unicode-bidi" 545 | version = "0.3.15" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 548 | 549 | [[package]] 550 | name = "unicode-ident" 551 | version = "1.0.12" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 554 | 555 | [[package]] 556 | name = "unicode-normalization" 557 | version = "0.1.23" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 560 | dependencies = [ 561 | "tinyvec", 562 | ] 563 | 564 | [[package]] 565 | name = "unicode-segmentation" 566 | version = "1.11.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 569 | 570 | [[package]] 571 | name = "unicode-xid" 572 | version = "0.2.4" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 575 | 576 | [[package]] 577 | name = "url" 578 | version = "2.5.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 581 | dependencies = [ 582 | "form_urlencoded", 583 | "idna", 584 | "percent-encoding", 585 | ] 586 | 587 | [[package]] 588 | name = "version_check" 589 | version = "0.9.4" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 592 | 593 | [[package]] 594 | name = "wasm-encoder" 595 | version = "0.201.0" 596 | source = "git+https://github.com/dicej/wasm-tools?branch=adapt-world-imports#a3e308dbe3b1555c7873ce199adf81e76ea01224" 597 | dependencies = [ 598 | "leb128", 599 | ] 600 | 601 | [[package]] 602 | name = "wasm-metadata" 603 | version = "0.201.0" 604 | source = "git+https://github.com/dicej/wasm-tools?branch=adapt-world-imports#a3e308dbe3b1555c7873ce199adf81e76ea01224" 605 | dependencies = [ 606 | "anyhow", 607 | "indexmap", 608 | "serde", 609 | "serde_derive", 610 | "serde_json", 611 | "spdx", 612 | "wasm-encoder", 613 | "wasmparser", 614 | ] 615 | 616 | [[package]] 617 | name = "wasmparser" 618 | version = "0.201.0" 619 | source = "git+https://github.com/dicej/wasm-tools?branch=adapt-world-imports#a3e308dbe3b1555c7873ce199adf81e76ea01224" 620 | dependencies = [ 621 | "bitflags", 622 | "indexmap", 623 | "semver", 624 | ] 625 | 626 | [[package]] 627 | name = "wit-bindgen" 628 | version = "0.22.0" 629 | dependencies = [ 630 | "wit-bindgen-rt", 631 | "wit-bindgen-rust-macro", 632 | ] 633 | 634 | [[package]] 635 | name = "wit-bindgen-core" 636 | version = "0.22.0" 637 | dependencies = [ 638 | "anyhow", 639 | "indexmap", 640 | "wit-component", 641 | "wit-parser", 642 | ] 643 | 644 | [[package]] 645 | name = "wit-bindgen-rt" 646 | version = "0.22.0" 647 | dependencies = [ 648 | "bitflags", 649 | ] 650 | 651 | [[package]] 652 | name = "wit-bindgen-rust" 653 | version = "0.22.0" 654 | dependencies = [ 655 | "anyhow", 656 | "heck", 657 | "indexmap", 658 | "wasm-metadata", 659 | "wit-bindgen-core", 660 | "wit-component", 661 | ] 662 | 663 | [[package]] 664 | name = "wit-bindgen-rust-macro" 665 | version = "0.22.0" 666 | dependencies = [ 667 | "anyhow", 668 | "isyswasfa-transform", 669 | "proc-macro2", 670 | "quote", 671 | "syn", 672 | "wit-bindgen-core", 673 | "wit-bindgen-rust", 674 | ] 675 | 676 | [[package]] 677 | name = "wit-component" 678 | version = "0.201.0" 679 | source = "git+https://github.com/dicej/wasm-tools?branch=adapt-world-imports#a3e308dbe3b1555c7873ce199adf81e76ea01224" 680 | dependencies = [ 681 | "anyhow", 682 | "bitflags", 683 | "indexmap", 684 | "log", 685 | "serde", 686 | "serde_derive", 687 | "serde_json", 688 | "wasm-encoder", 689 | "wasm-metadata", 690 | "wasmparser", 691 | "wit-parser", 692 | ] 693 | 694 | [[package]] 695 | name = "wit-parser" 696 | version = "0.201.0" 697 | source = "git+https://github.com/dicej/wasm-tools?branch=adapt-world-imports#a3e308dbe3b1555c7873ce199adf81e76ea01224" 698 | dependencies = [ 699 | "anyhow", 700 | "id-arena", 701 | "indexmap", 702 | "log", 703 | "semver", 704 | "serde", 705 | "serde_derive", 706 | "serde_json", 707 | "unicode-xid", 708 | "wasmparser", 709 | ] 710 | -------------------------------------------------------------------------------- /test/rust-cases/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "echo", 5 | "hash-all", 6 | "middleware", 7 | "service", 8 | "round-trip", 9 | "router" 10 | ] 11 | 12 | [workspace.dependencies] 13 | async-trait = "0.1.77" 14 | wit-bindgen = { path = "../../wit-bindgen/crates/guest-rust" } 15 | isyswasfa-guest = { path = "../../guest" } 16 | -------------------------------------------------------------------------------- /test/rust-cases/echo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-trait = { workspace = true} 8 | wit-bindgen = { workspace = true} 9 | isyswasfa-guest = { workspace = true} 10 | url = "2.5.0" 11 | futures = "0.3.30" 12 | 13 | [lib] 14 | crate-type = ["cdylib"] 15 | 16 | -------------------------------------------------------------------------------- /test/rust-cases/echo/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | #[allow(warnings)] 4 | mod bindings { 5 | wit_bindgen::generate!({ 6 | path: "../../../wit", 7 | world: "proxy", 8 | isyswasfa: "-echo" 9 | }); 10 | 11 | use super::Component; 12 | impl Guest for Component {} 13 | export!(Component); 14 | } 15 | 16 | use { 17 | async_trait::async_trait, 18 | bindings::{ 19 | exports::wasi::http::handler::Guest, 20 | isyswasfa::io::pipe, 21 | wasi::http::{ 22 | handler, 23 | types::{Body, ErrorCode, Fields, Method, Request, Response, Scheme}, 24 | }, 25 | }, 26 | url::Url, 27 | }; 28 | 29 | struct Component; 30 | 31 | #[async_trait(?Send)] 32 | impl Guest for Component { 33 | /// Return a response which either echoes the request body and trailers or forwards the request body to an 34 | /// external URL and forwards response to the client. 35 | async fn handle(request: Request) -> Result { 36 | let headers = request.headers().entries(); 37 | let method = request.method(); 38 | let filtered_headers = Fields::from_list( 39 | &headers 40 | .iter() 41 | .filter(|(k, _)| k == "content-type") 42 | .map(|(k, v)| (k.to_owned(), v.to_owned())) 43 | .collect::>(), 44 | ) 45 | .unwrap(); 46 | 47 | match (&method, request.path_with_query().as_deref()) { 48 | (Method::Post, Some("/echo")) => { 49 | // Echo the request body without buffering it. 50 | 51 | Ok(Response::new( 52 | filtered_headers, 53 | Request::into_parts(request).1, 54 | )) 55 | } 56 | 57 | (Method::Post, Some("/double-echo")) => { 58 | // Pipe the request body to an outgoing request and stream the response back to the client. 59 | 60 | if let Some(url) = headers.iter().find_map(|(k, v)| { 61 | (k == "url") 62 | .then_some(v) 63 | .and_then(|v| std::str::from_utf8(v).ok()) 64 | .and_then(|v| Url::parse(v).ok()) 65 | }) { 66 | let request = 67 | Request::new(filtered_headers, Request::into_parts(request).1, None); 68 | 69 | request.set_method(&method).unwrap(); 70 | request.set_path_with_query(Some(url.path())).unwrap(); 71 | request 72 | .set_scheme(Some(&match url.scheme() { 73 | "http" => Scheme::Http, 74 | "https" => Scheme::Https, 75 | scheme => Scheme::Other(scheme.into()), 76 | })) 77 | .unwrap(); 78 | request.set_authority(Some(url.authority())).unwrap(); 79 | 80 | handler::handle(request).await 81 | } else { 82 | Ok(respond(400)) 83 | } 84 | } 85 | 86 | _ => Ok(respond(405)), 87 | } 88 | } 89 | } 90 | 91 | fn respond(status: u16) -> Response { 92 | let response = Response::new(Fields::new(), Body::new(pipe::make_pipe().1, None)); 93 | response.set_status_code(status).unwrap(); 94 | response 95 | } 96 | -------------------------------------------------------------------------------- /test/rust-cases/hash-all/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hash-all" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-trait = { workspace = true} 8 | wit-bindgen = { workspace = true} 9 | isyswasfa-guest = { workspace = true} 10 | sha2 = "0.10.8" 11 | anyhow = "1.0.80" 12 | url = "2.5.0" 13 | futures = "0.3.30" 14 | hex = "0.4.3" 15 | 16 | [lib] 17 | crate-type = ["cdylib"] 18 | 19 | -------------------------------------------------------------------------------- /test/rust-cases/hash-all/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | #[allow(warnings)] 4 | mod bindings { 5 | wit_bindgen::generate!({ 6 | path: "../../../wit", 7 | world: "proxy", 8 | isyswasfa: "-hash-all" 9 | }); 10 | 11 | use super::Component; 12 | impl Guest for Component {} 13 | export!(Component); 14 | } 15 | 16 | use { 17 | anyhow::{anyhow, bail, Result}, 18 | async_trait::async_trait, 19 | bindings::{ 20 | exports::wasi::http::handler::Guest, 21 | isyswasfa::io::pipe, 22 | wasi::http::{ 23 | handler, 24 | types::{Body, ErrorCode, Fields, Request, Response, Scheme}, 25 | }, 26 | }, 27 | futures::{ 28 | sink::SinkExt, 29 | stream::{self, StreamExt, TryStreamExt}, 30 | }, 31 | url::Url, 32 | }; 33 | 34 | const MAX_CONCURRENCY: usize = 16; 35 | 36 | struct Component; 37 | 38 | #[async_trait(?Send)] 39 | impl Guest for Component { 40 | /// Send outgoing GET requests concurrently to the URLs specified as headers and stream the hashes of the 41 | /// response bodies as they arrive. 42 | async fn handle(request: Request) -> Result { 43 | let (response_body_tx, response_body_rx) = pipe::make_pipe(); 44 | let mut response_body_tx = isyswasfa_guest::sink(response_body_tx); 45 | 46 | isyswasfa_guest::spawn(async move { 47 | let headers = request.headers().entries(); 48 | 49 | let urls = headers.iter().filter_map(|(k, v)| { 50 | (k == "url") 51 | .then_some(v) 52 | .and_then(|v| std::str::from_utf8(v).ok()) 53 | .and_then(|v| Url::parse(v).ok()) 54 | }); 55 | 56 | let results = urls.map(|url| async move { 57 | let result = hash(&url).await; 58 | (url, result) 59 | }); 60 | 61 | let mut results = stream::iter(results).buffer_unordered(MAX_CONCURRENCY); 62 | 63 | while let Some((url, result)) = results.next().await { 64 | let payload = match result { 65 | Ok(hash) => format!("{url}: {hash}\n"), 66 | Err(e) => format!("{url}: {e:?}\n"), 67 | } 68 | .into_bytes(); 69 | 70 | if let Err(e) = response_body_tx.send(payload).await { 71 | eprintln!("Error sending payload: {e}"); 72 | } 73 | } 74 | }); 75 | 76 | Ok(Response::new( 77 | Fields::from_list(&[("content-type".to_string(), b"text/plain".to_vec())]).unwrap(), 78 | Body::new(response_body_rx, None), 79 | )) 80 | } 81 | } 82 | 83 | async fn hash(url: &Url) -> Result { 84 | let request = Request::new(Fields::new(), Body::new(pipe::make_pipe().1, None), None); 85 | 86 | request 87 | .set_path_with_query(Some(url.path())) 88 | .map_err(|()| anyhow!("failed to set path_with_query"))?; 89 | 90 | request 91 | .set_scheme(Some(&match url.scheme() { 92 | "http" => Scheme::Http, 93 | "https" => Scheme::Https, 94 | scheme => Scheme::Other(scheme.into()), 95 | })) 96 | .map_err(|()| anyhow!("failed to set scheme"))?; 97 | 98 | request 99 | .set_authority(Some(&format!( 100 | "{}{}", 101 | url.host_str().unwrap_or(""), 102 | if let Some(port) = url.port() { 103 | format!(":{port}") 104 | } else { 105 | String::new() 106 | } 107 | ))) 108 | .map_err(|()| anyhow!("failed to set authority"))?; 109 | 110 | let response = handler::handle(request).await?; 111 | 112 | let status = response.status_code(); 113 | 114 | if !(200..300).contains(&status) { 115 | bail!("unexpected status: {status}"); 116 | } 117 | 118 | let (_, body) = Response::into_parts(response); 119 | let mut stream = isyswasfa_guest::stream(body.stream().unwrap()); 120 | 121 | use sha2::Digest; 122 | let mut hasher = sha2::Sha256::new(); 123 | while let Some(chunk) = stream.try_next().await? { 124 | hasher.update(&chunk); 125 | } 126 | 127 | Ok(hex::encode(hasher.finalize())) 128 | } 129 | -------------------------------------------------------------------------------- /test/rust-cases/middleware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middleware" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-trait = { workspace = true} 8 | wit-bindgen = { workspace = true} 9 | isyswasfa-guest = { workspace = true} 10 | flate2 = "1.0.28" 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | -------------------------------------------------------------------------------- /test/rust-cases/middleware/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | #[allow(warnings)] 4 | mod bindings { 5 | wit_bindgen::generate!({ 6 | path: "../../../wit", 7 | world: "proxy", 8 | isyswasfa: "-middleware" 9 | }); 10 | 11 | use super::Component; 12 | impl Guest for Component {} 13 | export!(Component); 14 | } 15 | 16 | use { 17 | async_trait::async_trait, 18 | bindings::{ 19 | exports::wasi::http::handler::Guest, 20 | isyswasfa::io::pipe, 21 | wasi::http::{ 22 | handler, 23 | types::{ 24 | self, Body, ErrorCode, Headers, IsyswasfaSenderOwnTrailers, Request, Response, 25 | }, 26 | }, 27 | }, 28 | flate2::{ 29 | write::{DeflateDecoder, DeflateEncoder}, 30 | Compression, 31 | }, 32 | std::io::Write, 33 | }; 34 | 35 | struct Component; 36 | 37 | #[async_trait(?Send)] 38 | impl Guest for Component { 39 | /// Forward the specified request to the imported `wasi:http/handler`, transparently decoding the request body 40 | /// if it is `deflate`d and then encoding the response body if the client has provided an `accept-encoding: 41 | /// deflate` header. 42 | async fn handle(request: Request) -> Result { 43 | // First, extract the parts of the request and check for (and remove) headers pertaining to body encodings. 44 | let method = request.method(); 45 | let scheme = request.scheme(); 46 | let path_with_query = request.path_with_query(); 47 | let authority = request.authority(); 48 | let mut accept_deflated = false; 49 | let mut content_deflated = false; 50 | let (headers, body) = Request::into_parts(request); 51 | let mut headers = headers.entries(); 52 | headers.retain(|(k, v)| match (k.as_str(), v.as_slice()) { 53 | ("accept-encoding", b"deflate") => { 54 | accept_deflated = true; 55 | false 56 | } 57 | ("content-encoding", b"deflate") => { 58 | content_deflated = true; 59 | false 60 | } 61 | _ => true, 62 | }); 63 | 64 | let body = if content_deflated { 65 | // Next, spawn a task to pipe and decode the original request body and trailers into a new request 66 | // we'll create below. This will run concurrently with any code in the imported `wasi:http/handler`. 67 | let (trailers_tx, trailers_rx) = types::isyswasfa_pipe_own_trailers(); 68 | let (pipe_tx, pipe_rx) = pipe::make_pipe(); 69 | 70 | isyswasfa_guest::spawn(async move { 71 | { 72 | let body_rx = body.stream().unwrap(); 73 | 74 | let mut decoder = DeflateDecoder::new(Vec::new()); 75 | 76 | while let Some(chunk) = 77 | isyswasfa_guest::read(&body_rx, 64 * 1024).await.unwrap() 78 | { 79 | decoder.write_all(&chunk).unwrap(); 80 | isyswasfa_guest::write_all(&pipe_tx, decoder.get_ref()) 81 | .await 82 | .unwrap(); 83 | decoder.get_mut().clear() 84 | } 85 | 86 | isyswasfa_guest::write_all(&pipe_tx, &decoder.finish().unwrap()) 87 | .await 88 | .unwrap(); 89 | } 90 | 91 | if let Some(trailers) = Body::finish(body).await.unwrap() { 92 | IsyswasfaSenderOwnTrailers::send(trailers_tx, trailers); 93 | } 94 | }); 95 | 96 | Body::new(pipe_rx, Some(trailers_rx)) 97 | } else { 98 | body 99 | }; 100 | 101 | // While the above task (if any) is running, synthesize a request from the parts collected above and pass 102 | // it to the imported `wasi:http/handler`. 103 | let my_request = Request::new(Headers::from_list(&headers).unwrap(), body, None); 104 | my_request.set_method(&method).unwrap(); 105 | my_request.set_scheme(scheme.as_ref()).unwrap(); 106 | my_request 107 | .set_path_with_query(path_with_query.as_deref()) 108 | .unwrap(); 109 | my_request.set_authority(authority.as_deref()).unwrap(); 110 | 111 | let response = handler::handle(my_request).await?; 112 | 113 | // Now that we have the response, extract the parts, adding an extra header if we'll be encoding the body. 114 | let status_code = response.status_code(); 115 | let (headers, body) = Response::into_parts(response); 116 | let mut headers = headers.entries(); 117 | if accept_deflated { 118 | headers.push(("content-encoding".into(), b"deflate".into())); 119 | } 120 | 121 | let body = if accept_deflated { 122 | // Spawn another task; this one is to pipe (and optionally encode) the original response body and 123 | // trailers into a new response we'll create below. This will run concurrently with the caller's code 124 | // (i.e. it won't necessarily complete before we return a value). 125 | let (trailers_tx, trailers_rx) = types::isyswasfa_pipe_own_trailers(); 126 | let (pipe_tx, pipe_rx) = pipe::make_pipe(); 127 | 128 | isyswasfa_guest::spawn(async move { 129 | { 130 | let body_rx = body.stream().unwrap(); 131 | 132 | let mut encoder = DeflateEncoder::new(Vec::new(), Compression::fast()); 133 | 134 | while let Some(chunk) = 135 | isyswasfa_guest::read(&body_rx, 64 * 1024).await.unwrap() 136 | { 137 | encoder.write_all(&chunk).unwrap(); 138 | isyswasfa_guest::write_all(&pipe_tx, encoder.get_ref()) 139 | .await 140 | .unwrap(); 141 | encoder.get_mut().clear() 142 | } 143 | 144 | isyswasfa_guest::write_all(&pipe_tx, &encoder.finish().unwrap()) 145 | .await 146 | .unwrap(); 147 | } 148 | 149 | if let Some(trailers) = Body::finish(body).await.unwrap() { 150 | IsyswasfaSenderOwnTrailers::send(trailers_tx, trailers); 151 | } 152 | }); 153 | 154 | Body::new(pipe_rx, Some(trailers_rx)) 155 | } else { 156 | body 157 | }; 158 | 159 | // While the above tasks (if any) are running, synthesize a response from the parts collected above and 160 | // return it. 161 | let my_response = Response::new(Headers::from_list(&headers).unwrap(), body); 162 | my_response.set_status_code(status_code).unwrap(); 163 | 164 | Ok(my_response) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /test/rust-cases/round-trip/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "round-trip" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-trait = { workspace = true} 8 | wit-bindgen = { workspace = true} 9 | isyswasfa-guest = { workspace = true} 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | -------------------------------------------------------------------------------- /test/rust-cases/round-trip/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | #[allow(warnings)] 4 | mod bindings { 5 | wit_bindgen::generate!({ 6 | path: "../../../wit", 7 | world: "round-trip", 8 | isyswasfa: "-round-trip" 9 | }); 10 | 11 | use super::Component; 12 | impl Guest for Component {} 13 | export!(Component); 14 | } 15 | 16 | use { 17 | async_trait::async_trait, 18 | bindings::{ 19 | component::test::baz, exports::component::test::baz::Guest as Baz, 20 | wasi::clocks::monotonic_clock, 21 | }, 22 | }; 23 | 24 | struct Component; 25 | 26 | #[async_trait(?Send)] 27 | impl Baz for Component { 28 | async fn foo(s: String) -> String { 29 | monotonic_clock::subscribe_duration(10_000_000).await; 30 | 31 | format!( 32 | "{} - exited guest", 33 | baz::foo(&format!("{s} - entered guest")).await 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/rust-cases/router/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "router" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-trait = { workspace = true} 8 | wit-bindgen = { workspace = true} 9 | isyswasfa-guest = { workspace = true} 10 | url = "2.5.0" 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | -------------------------------------------------------------------------------- /test/rust-cases/router/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | #[allow(warnings)] 4 | mod bindings { 5 | wit_bindgen::generate!({ 6 | path: "../../../wit", 7 | world: "router", 8 | isyswasfa: "-router" 9 | }); 10 | 11 | use super::Component; 12 | impl Guest for Component {} 13 | export!(Component); 14 | } 15 | 16 | use { 17 | async_trait::async_trait, 18 | bindings::{ 19 | echo, 20 | exports::wasi::http::handler::Guest, 21 | hash_all, 22 | isyswasfa::io::pipe, 23 | middleware, service, 24 | wasi::http::{ 25 | handler, 26 | types::{Body, ErrorCode, Fields, Request, Response, Scheme}, 27 | }, 28 | }, 29 | url::Url, 30 | }; 31 | 32 | struct Component; 33 | 34 | #[async_trait(?Send)] 35 | impl Guest for Component { 36 | async fn handle(request: Request) -> Result { 37 | match request.path_with_query().as_deref() { 38 | Some("/echo" | "/double-echo") => echo::handle(request).await, 39 | Some("/proxy") => { 40 | let method = request.method(); 41 | let (headers, body) = Request::into_parts(request); 42 | let urls = headers.delete(&"url".into()).unwrap(); 43 | if let Some(url) = urls 44 | .first() 45 | .and_then(|v| std::str::from_utf8(v).ok()) 46 | .and_then(|v| Url::parse(v).ok()) 47 | { 48 | let request = Request::new(headers, body, None); 49 | request.set_method(&method).unwrap(); 50 | request.set_path_with_query(Some(url.path())).unwrap(); 51 | request 52 | .set_scheme(Some(&match url.scheme() { 53 | "http" => Scheme::Http, 54 | "https" => Scheme::Https, 55 | scheme => Scheme::Other(scheme.into()), 56 | })) 57 | .unwrap(); 58 | request.set_authority(Some(url.authority())).unwrap(); 59 | 60 | handler::handle(request).await 61 | } else { 62 | Ok(respond(400)) 63 | } 64 | } 65 | Some("/hash-all") => hash_all::handle(request).await, 66 | Some("/service") => service::handle(request).await, 67 | Some("/middleware") => middleware::handle(request).await, 68 | _ => Ok(respond(405)), 69 | } 70 | } 71 | } 72 | 73 | fn respond(status: u16) -> Response { 74 | let response = Response::new(Fields::new(), Body::new(pipe::make_pipe().1, None)); 75 | response.set_status_code(status).unwrap(); 76 | response 77 | } 78 | -------------------------------------------------------------------------------- /test/rust-cases/service/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "service" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-trait = { workspace = true} 8 | wit-bindgen = { workspace = true} 9 | isyswasfa-guest = { workspace = true} 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | -------------------------------------------------------------------------------- /test/rust-cases/service/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | #[allow(warnings)] 4 | mod bindings { 5 | wit_bindgen::generate!({ 6 | path: "../../../wit", 7 | world: "proxy", 8 | isyswasfa: "-service" 9 | }); 10 | 11 | use super::Component; 12 | impl Guest for Component {} 13 | export!(Component); 14 | } 15 | 16 | use { 17 | async_trait::async_trait, 18 | bindings::{ 19 | exports::wasi::http::handler::Guest, 20 | wasi::http::types::{ErrorCode, Request, Response}, 21 | }, 22 | }; 23 | 24 | struct Component; 25 | 26 | #[async_trait(?Send)] 27 | impl Guest for Component { 28 | /// Return a response which echoes the request headers, body, and trailers. 29 | async fn handle(request: Request) -> Result { 30 | let (headers, body) = Request::into_parts(request); 31 | 32 | Ok(Response::new(headers, body)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/wasi-http-handler/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.79" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" 10 | 11 | [[package]] 12 | name = "async-trait" 13 | version = "0.1.77" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" 16 | dependencies = [ 17 | "proc-macro2", 18 | "quote", 19 | "syn", 20 | ] 21 | 22 | [[package]] 23 | name = "autocfg" 24 | version = "1.1.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 27 | 28 | [[package]] 29 | name = "bitflags" 30 | version = "2.4.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 33 | 34 | [[package]] 35 | name = "by_address" 36 | version = "1.1.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "bf8dba2868114ed769a1f2590fc9ae5eb331175b44313b6c9b922f8f7ca813d0" 39 | 40 | [[package]] 41 | name = "equivalent" 42 | version = "1.0.1" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 45 | 46 | [[package]] 47 | name = "futures" 48 | version = "0.3.30" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 51 | dependencies = [ 52 | "futures-channel", 53 | "futures-core", 54 | "futures-executor", 55 | "futures-io", 56 | "futures-sink", 57 | "futures-task", 58 | "futures-util", 59 | ] 60 | 61 | [[package]] 62 | name = "futures-channel" 63 | version = "0.3.30" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 66 | dependencies = [ 67 | "futures-core", 68 | "futures-sink", 69 | ] 70 | 71 | [[package]] 72 | name = "futures-core" 73 | version = "0.3.30" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 76 | 77 | [[package]] 78 | name = "futures-executor" 79 | version = "0.3.30" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 82 | dependencies = [ 83 | "futures-core", 84 | "futures-task", 85 | "futures-util", 86 | ] 87 | 88 | [[package]] 89 | name = "futures-io" 90 | version = "0.3.30" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 93 | 94 | [[package]] 95 | name = "futures-macro" 96 | version = "0.3.30" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 99 | dependencies = [ 100 | "proc-macro2", 101 | "quote", 102 | "syn", 103 | ] 104 | 105 | [[package]] 106 | name = "futures-sink" 107 | version = "0.3.30" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 110 | 111 | [[package]] 112 | name = "futures-task" 113 | version = "0.3.30" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 116 | 117 | [[package]] 118 | name = "futures-util" 119 | version = "0.3.30" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 122 | dependencies = [ 123 | "futures-channel", 124 | "futures-core", 125 | "futures-io", 126 | "futures-macro", 127 | "futures-sink", 128 | "futures-task", 129 | "memchr", 130 | "pin-project-lite", 131 | "pin-utils", 132 | "slab", 133 | ] 134 | 135 | [[package]] 136 | name = "hashbrown" 137 | version = "0.14.3" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 140 | 141 | [[package]] 142 | name = "heck" 143 | version = "0.4.1" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 146 | dependencies = [ 147 | "unicode-segmentation", 148 | ] 149 | 150 | [[package]] 151 | name = "id-arena" 152 | version = "2.2.1" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" 155 | 156 | [[package]] 157 | name = "indexmap" 158 | version = "2.1.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 161 | dependencies = [ 162 | "equivalent", 163 | "hashbrown", 164 | "serde", 165 | ] 166 | 167 | [[package]] 168 | name = "isyswasfa-guest" 169 | version = "0.1.0" 170 | dependencies = [ 171 | "by_address", 172 | "futures", 173 | "once_cell", 174 | "wit-bindgen", 175 | ] 176 | 177 | [[package]] 178 | name = "isyswasfa-transform" 179 | version = "0.1.0" 180 | dependencies = [ 181 | "wit-parser", 182 | ] 183 | 184 | [[package]] 185 | name = "itoa" 186 | version = "1.0.10" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 189 | 190 | [[package]] 191 | name = "leb128" 192 | version = "0.2.5" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 195 | 196 | [[package]] 197 | name = "log" 198 | version = "0.4.20" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 201 | 202 | [[package]] 203 | name = "memchr" 204 | version = "2.7.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 207 | 208 | [[package]] 209 | name = "once_cell" 210 | version = "1.19.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 213 | 214 | [[package]] 215 | name = "pin-project-lite" 216 | version = "0.2.13" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 219 | 220 | [[package]] 221 | name = "pin-utils" 222 | version = "0.1.0" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 225 | 226 | [[package]] 227 | name = "proc-macro2" 228 | version = "1.0.78" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 231 | dependencies = [ 232 | "unicode-ident", 233 | ] 234 | 235 | [[package]] 236 | name = "quote" 237 | version = "1.0.35" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 240 | dependencies = [ 241 | "proc-macro2", 242 | ] 243 | 244 | [[package]] 245 | name = "ryu" 246 | version = "1.0.16" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" 249 | 250 | [[package]] 251 | name = "semver" 252 | version = "1.0.21" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" 255 | 256 | [[package]] 257 | name = "serde" 258 | version = "1.0.196" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" 261 | dependencies = [ 262 | "serde_derive", 263 | ] 264 | 265 | [[package]] 266 | name = "serde_derive" 267 | version = "1.0.196" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" 270 | dependencies = [ 271 | "proc-macro2", 272 | "quote", 273 | "syn", 274 | ] 275 | 276 | [[package]] 277 | name = "serde_json" 278 | version = "1.0.112" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed" 281 | dependencies = [ 282 | "itoa", 283 | "ryu", 284 | "serde", 285 | ] 286 | 287 | [[package]] 288 | name = "slab" 289 | version = "0.4.9" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 292 | dependencies = [ 293 | "autocfg", 294 | ] 295 | 296 | [[package]] 297 | name = "smallvec" 298 | version = "1.13.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 301 | 302 | [[package]] 303 | name = "spdx" 304 | version = "0.10.3" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "62bde1398b09b9f93fc2fc9b9da86e362693e999d3a54a8ac47a99a5a73f638b" 307 | dependencies = [ 308 | "smallvec", 309 | ] 310 | 311 | [[package]] 312 | name = "syn" 313 | version = "2.0.48" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 316 | dependencies = [ 317 | "proc-macro2", 318 | "quote", 319 | "unicode-ident", 320 | ] 321 | 322 | [[package]] 323 | name = "unicode-ident" 324 | version = "1.0.12" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 327 | 328 | [[package]] 329 | name = "unicode-segmentation" 330 | version = "1.10.1" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 333 | 334 | [[package]] 335 | name = "unicode-xid" 336 | version = "0.2.4" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 339 | 340 | [[package]] 341 | name = "wasi-http-handler" 342 | version = "0.1.0" 343 | dependencies = [ 344 | "async-trait", 345 | "isyswasfa-guest", 346 | "wit-bindgen", 347 | ] 348 | 349 | [[package]] 350 | name = "wasm-encoder" 351 | version = "0.39.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d" 354 | dependencies = [ 355 | "leb128", 356 | ] 357 | 358 | [[package]] 359 | name = "wasm-encoder" 360 | version = "0.40.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "d162eb64168969ae90e8668ca0593b0e47667e315aa08e717a9c9574d700d826" 363 | dependencies = [ 364 | "leb128", 365 | ] 366 | 367 | [[package]] 368 | name = "wasm-metadata" 369 | version = "0.10.16" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "0b313e616ef69d1b4c64155451439db26d1923e8bbc13d451ec24cf14579632e" 372 | dependencies = [ 373 | "anyhow", 374 | "indexmap", 375 | "serde", 376 | "serde_derive", 377 | "serde_json", 378 | "spdx", 379 | "wasm-encoder 0.40.0", 380 | "wasmparser 0.120.0", 381 | ] 382 | 383 | [[package]] 384 | name = "wasmparser" 385 | version = "0.119.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "8c35daf77afb4f9b14016625144a391085ec2ca99ca9cc53ed291bb53ab5278d" 388 | dependencies = [ 389 | "bitflags", 390 | "indexmap", 391 | "semver", 392 | ] 393 | 394 | [[package]] 395 | name = "wasmparser" 396 | version = "0.120.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "e9148127f39cbffe43efee8d5442b16ecdba21567785268daa1ec9e134389705" 399 | dependencies = [ 400 | "bitflags", 401 | "indexmap", 402 | "semver", 403 | ] 404 | 405 | [[package]] 406 | name = "wit-bindgen" 407 | version = "0.16.0" 408 | dependencies = [ 409 | "bitflags", 410 | "wit-bindgen-rust-macro", 411 | ] 412 | 413 | [[package]] 414 | name = "wit-bindgen-core" 415 | version = "0.16.0" 416 | dependencies = [ 417 | "anyhow", 418 | "indexmap", 419 | "wit-component", 420 | "wit-parser", 421 | ] 422 | 423 | [[package]] 424 | name = "wit-bindgen-rust" 425 | version = "0.16.0" 426 | dependencies = [ 427 | "anyhow", 428 | "heck", 429 | "wasm-metadata", 430 | "wit-bindgen-core", 431 | "wit-component", 432 | ] 433 | 434 | [[package]] 435 | name = "wit-bindgen-rust-macro" 436 | version = "0.16.0" 437 | dependencies = [ 438 | "anyhow", 439 | "isyswasfa-transform", 440 | "proc-macro2", 441 | "quote", 442 | "syn", 443 | "wit-bindgen-core", 444 | "wit-bindgen-rust", 445 | "wit-component", 446 | ] 447 | 448 | [[package]] 449 | name = "wit-component" 450 | version = "0.19.1" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "429e3c06fba3a7566aab724ae3ffff3152ede5399d44789e7dd11f5421292859" 453 | dependencies = [ 454 | "anyhow", 455 | "bitflags", 456 | "indexmap", 457 | "log", 458 | "serde", 459 | "serde_derive", 460 | "serde_json", 461 | "wasm-encoder 0.39.0", 462 | "wasm-metadata", 463 | "wasmparser 0.119.0", 464 | "wit-parser", 465 | ] 466 | 467 | [[package]] 468 | name = "wit-parser" 469 | version = "0.13.1" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "df4913a2219096373fd6512adead1fb77ecdaa59d7fc517972a7d30b12f625be" 472 | dependencies = [ 473 | "anyhow", 474 | "id-arena", 475 | "indexmap", 476 | "log", 477 | "semver", 478 | "serde", 479 | "serde_derive", 480 | "serde_json", 481 | "unicode-xid", 482 | ] 483 | -------------------------------------------------------------------------------- /test/wasi-http-handler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasi-http-handler" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | async-trait = "0.1.77" 8 | wit-bindgen = { path = "../../wit-bindgen/crates/guest-rust" } 9 | isyswasfa-guest = { path = "../../guest" } 10 | 11 | [lib] 12 | crate-type = ["cdylib"] 13 | 14 | [workspace] 15 | -------------------------------------------------------------------------------- /test/wasi-http-handler/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod bindings { 2 | wit_bindgen::generate!({ 3 | path: "../../wit", 4 | world: "wasi-http-handler", 5 | isyswasfa: "-wasi-http", 6 | exports: { 7 | "component:test/incoming-handler": super::Component 8 | } 9 | }); 10 | } 11 | 12 | use { 13 | async_trait::async_trait, 14 | bindings::{ 15 | exports::component::test::incoming_handler::Guest, 16 | wasi::http::types::{ 17 | Fields, IncomingBody, IncomingRequest, OutgoingBody, OutgoingResponse, 18 | }, 19 | }, 20 | }; 21 | 22 | struct Component; 23 | 24 | #[async_trait(?Send)] 25 | impl Guest for Component { 26 | async fn handle(request: IncomingRequest) -> OutgoingResponse { 27 | let response = OutgoingResponse::new(Fields::new()); 28 | let response_body = response.body().unwrap(); 29 | 30 | isyswasfa_guest::spawn(async move { 31 | let request_body = request.consume().unwrap(); 32 | isyswasfa_guest::copy( 33 | &request_body.stream().unwrap(), 34 | &response_body.write().unwrap(), 35 | ) 36 | .await 37 | .unwrap(); 38 | IncomingBody::finish(request_body); 39 | OutgoingBody::finish(response_body, None).unwrap(); 40 | }); 41 | 42 | response 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /wit/deps/cli/command.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world command { 4 | include imports; 5 | 6 | export run; 7 | } 8 | -------------------------------------------------------------------------------- /wit/deps/cli/environment.wit: -------------------------------------------------------------------------------- 1 | interface environment { 2 | /// Get the POSIX-style environment variables. 3 | /// 4 | /// Each environment variable is provided as a pair of string variable names 5 | /// and string value. 6 | /// 7 | /// Morally, these are a value import, but until value imports are available 8 | /// in the component model, this import function should return the same 9 | /// values each time it is called. 10 | get-environment: func() -> list>; 11 | 12 | /// Get the POSIX-style arguments to the program. 13 | get-arguments: func() -> list; 14 | 15 | /// Return a path that programs should use as their initial current working 16 | /// directory, interpreting `.` as shorthand for this. 17 | initial-cwd: func() -> option; 18 | } 19 | -------------------------------------------------------------------------------- /wit/deps/cli/exit.wit: -------------------------------------------------------------------------------- 1 | interface exit { 2 | /// Exit the current instance and any linked instances. 3 | exit: func(status: result); 4 | } 5 | -------------------------------------------------------------------------------- /wit/deps/cli/imports.wit: -------------------------------------------------------------------------------- 1 | package wasi:cli@0.2.0; 2 | 3 | world imports { 4 | include wasi:clocks/imports@0.2.0; 5 | include wasi:filesystem/imports@0.2.0; 6 | include wasi:sockets/imports@0.2.0; 7 | include wasi:random/imports@0.2.0; 8 | include wasi:io/imports@0.2.0; 9 | 10 | import environment; 11 | import exit; 12 | import stdin; 13 | import stdout; 14 | import stderr; 15 | import terminal-input; 16 | import terminal-output; 17 | import terminal-stdin; 18 | import terminal-stdout; 19 | import terminal-stderr; 20 | } 21 | -------------------------------------------------------------------------------- /wit/deps/cli/run.wit: -------------------------------------------------------------------------------- 1 | interface run { 2 | /// Run the program. 3 | run: func() -> result; 4 | } 5 | -------------------------------------------------------------------------------- /wit/deps/cli/stdio.wit: -------------------------------------------------------------------------------- 1 | interface stdin { 2 | use wasi:io/streams@0.2.0.{input-stream}; 3 | 4 | get-stdin: func() -> input-stream; 5 | } 6 | 7 | interface stdout { 8 | use wasi:io/streams@0.2.0.{output-stream}; 9 | 10 | get-stdout: func() -> output-stream; 11 | } 12 | 13 | interface stderr { 14 | use wasi:io/streams@0.2.0.{output-stream}; 15 | 16 | get-stderr: func() -> output-stream; 17 | } 18 | -------------------------------------------------------------------------------- /wit/deps/cli/terminal.wit: -------------------------------------------------------------------------------- 1 | /// Terminal input. 2 | /// 3 | /// In the future, this may include functions for disabling echoing, 4 | /// disabling input buffering so that keyboard events are sent through 5 | /// immediately, querying supported features, and so on. 6 | interface terminal-input { 7 | /// The input side of a terminal. 8 | resource terminal-input; 9 | } 10 | 11 | /// Terminal output. 12 | /// 13 | /// In the future, this may include functions for querying the terminal 14 | /// size, being notified of terminal size changes, querying supported 15 | /// features, and so on. 16 | interface terminal-output { 17 | /// The output side of a terminal. 18 | resource terminal-output; 19 | } 20 | 21 | /// An interface providing an optional `terminal-input` for stdin as a 22 | /// link-time authority. 23 | interface terminal-stdin { 24 | use terminal-input.{terminal-input}; 25 | 26 | /// If stdin is connected to a terminal, return a `terminal-input` handle 27 | /// allowing further interaction with it. 28 | get-terminal-stdin: func() -> option; 29 | } 30 | 31 | /// An interface providing an optional `terminal-output` for stdout as a 32 | /// link-time authority. 33 | interface terminal-stdout { 34 | use terminal-output.{terminal-output}; 35 | 36 | /// If stdout is connected to a terminal, return a `terminal-output` handle 37 | /// allowing further interaction with it. 38 | get-terminal-stdout: func() -> option; 39 | } 40 | 41 | /// An interface providing an optional `terminal-output` for stderr as a 42 | /// link-time authority. 43 | interface terminal-stderr { 44 | use terminal-output.{terminal-output}; 45 | 46 | /// If stderr is connected to a terminal, return a `terminal-output` handle 47 | /// allowing further interaction with it. 48 | get-terminal-stderr: func() -> option; 49 | } 50 | -------------------------------------------------------------------------------- /wit/deps/clocks/monotonic-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Monotonic Clock is a clock API intended to let users measure elapsed 3 | /// time. 4 | /// 5 | /// It is intended to be portable at least between Unix-family platforms and 6 | /// Windows. 7 | /// 8 | /// A monotonic clock is a clock which has an unspecified initial value, and 9 | /// successive reads of the clock will produce non-decreasing values. 10 | /// 11 | /// It is intended for measuring elapsed time. 12 | interface monotonic-clock { 13 | use wasi:io/poll@0.2.0.{pollable}; 14 | 15 | /// An instant in time, in nanoseconds. An instant is relative to an 16 | /// unspecified initial value, and can only be compared to instances from 17 | /// the same monotonic-clock. 18 | type instant = u64; 19 | 20 | /// A duration of time, in nanoseconds. 21 | type duration = u64; 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// The clock is monotonic, therefore calling this function repeatedly will 26 | /// produce a sequence of non-decreasing values. 27 | now: func() -> instant; 28 | 29 | /// Query the resolution of the clock. Returns the duration of time 30 | /// corresponding to a clock tick. 31 | resolution: func() -> duration; 32 | 33 | /// Create a `pollable` which will resolve once the specified instant 34 | /// occured. 35 | subscribe-instant: func( 36 | when: instant, 37 | ) -> pollable; 38 | 39 | /// Create a `pollable` which will resolve once the given duration has 40 | /// elapsed, starting at the time at which this function was called. 41 | /// occured. 42 | subscribe-duration: func( 43 | when: duration, 44 | ) -> pollable; 45 | } 46 | -------------------------------------------------------------------------------- /wit/deps/clocks/wall-clock.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | /// WASI Wall Clock is a clock API intended to let users query the current 3 | /// time. The name "wall" makes an analogy to a "clock on the wall", which 4 | /// is not necessarily monotonic as it may be reset. 5 | /// 6 | /// It is intended to be portable at least between Unix-family platforms and 7 | /// Windows. 8 | /// 9 | /// A wall clock is a clock which measures the date and time according to 10 | /// some external reference. 11 | /// 12 | /// External references may be reset, so this clock is not necessarily 13 | /// monotonic, making it unsuitable for measuring elapsed time. 14 | /// 15 | /// It is intended for reporting the current date and time for humans. 16 | interface wall-clock { 17 | /// A time and date in seconds plus nanoseconds. 18 | record datetime { 19 | seconds: u64, 20 | nanoseconds: u32, 21 | } 22 | 23 | /// Read the current value of the clock. 24 | /// 25 | /// This clock is not monotonic, therefore calling this function repeatedly 26 | /// will not necessarily produce a sequence of non-decreasing values. 27 | /// 28 | /// The returned timestamps represent the number of seconds since 29 | /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], 30 | /// also known as [Unix Time]. 31 | /// 32 | /// The nanoseconds field of the output is always less than 1000000000. 33 | /// 34 | /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 35 | /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time 36 | now: func() -> datetime; 37 | 38 | /// Query the resolution of the clock. 39 | /// 40 | /// The nanoseconds field of the output is always less than 1000000000. 41 | resolution: func() -> datetime; 42 | } 43 | -------------------------------------------------------------------------------- /wit/deps/clocks/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:clocks@0.2.0; 2 | 3 | world imports { 4 | import monotonic-clock; 5 | import wall-clock; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/filesystem/preopens.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | interface preopens { 4 | use types.{descriptor}; 5 | 6 | /// Return the set of preopened directories, and their path. 7 | get-directories: func() -> list>; 8 | } 9 | -------------------------------------------------------------------------------- /wit/deps/filesystem/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:filesystem@0.2.0; 2 | 3 | world imports { 4 | import types; 5 | import preopens; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/http/handler.wit: -------------------------------------------------------------------------------- 1 | /// This interface defines a handler of HTTP Requests. It may be imported by 2 | /// components which wish to send HTTP Requests and also exported by components 3 | /// which can respond to HTTP Requests. In addition, it may be used to pass 4 | /// a request from one component to another without any use of a network. 5 | interface handler { 6 | use types.{request, response, error-code}; 7 | 8 | /// When exported, this function may be called with either an incoming 9 | /// request read from the network or a request synthesized or forwarded by 10 | /// another component. 11 | /// 12 | /// When imported, this function may be used to either send an outgoing 13 | /// request over the network or pass it to another component. 14 | handle: func( 15 | request: request, 16 | ) -> result; 17 | } 18 | -------------------------------------------------------------------------------- /wit/deps/http/proxy.wit: -------------------------------------------------------------------------------- 1 | package wasi:http@0.3.0-draft; 2 | 3 | /// The `wasi:http/imports` world imports all the APIs for HTTP proxies. 4 | /// It is intended to be `include`d in other worlds. 5 | world imports { 6 | /// HTTP proxies have access to time and randomness. 7 | include wasi:clocks/imports@0.2.0; 8 | import wasi:random/random@0.2.0; 9 | 10 | /// Proxies have standard output and error streams which are expected to 11 | /// terminate in a developer-facing console provided by the host. 12 | import wasi:cli/stdout@0.2.0; 13 | import wasi:cli/stderr@0.2.0; 14 | 15 | /// TODO: this is a temporary workaround until component tooling is able to 16 | /// gracefully handle the absence of stdin. Hosts must return an eof stream 17 | /// for this import, which is what wasi-libc + tooling will do automatically 18 | /// when this import is properly removed. 19 | import wasi:cli/stdin@0.2.0; 20 | 21 | /// This is the default handler to use when user code simply wants to make an 22 | /// HTTP request (e.g., via `fetch()`). 23 | /// 24 | /// This may also be used to pass synthesized or forwarded requests to another 25 | /// component. 26 | import handler; 27 | } 28 | 29 | /// The `wasi:http/proxy` world captures a widely-implementable intersection of 30 | /// hosts that includes HTTP forward and reverse proxies. Components targeting 31 | /// this world may concurrently stream in and out any number of incoming and 32 | /// outgoing HTTP requests. 33 | world proxy { 34 | include imports; 35 | 36 | /// The host delivers incoming HTTP requests to a component by calling the 37 | /// `handle` function of this exported interface. A host may arbitrarily reuse 38 | /// or not reuse component instance when delivering incoming HTTP requests and 39 | /// thus a component must be able to handle 0..N calls to `handle`. 40 | /// 41 | /// This may also be used to receive synthesized or forwarded requests from 42 | /// another component. 43 | export handler; 44 | } 45 | -------------------------------------------------------------------------------- /wit/deps/http/types.wit: -------------------------------------------------------------------------------- 1 | /// This interface defines all of the types and methods for implementing HTTP 2 | /// Requests and Responses, as well as their headers, trailers, and bodies. 3 | interface types { 4 | use wasi:clocks/monotonic-clock@0.2.0.{duration}; 5 | use wasi:io/error@0.2.0.{error}; 6 | use wasi:io/streams@0.2.0.{input-stream}; 7 | 8 | /// This type corresponds to HTTP standard Methods. 9 | variant method { 10 | get, 11 | head, 12 | post, 13 | put, 14 | delete, 15 | connect, 16 | options, 17 | trace, 18 | patch, 19 | other(string) 20 | } 21 | 22 | /// This type corresponds to HTTP standard Related Schemes. 23 | variant scheme { 24 | HTTP, 25 | HTTPS, 26 | other(string) 27 | } 28 | 29 | /// These cases are inspired by the IANA HTTP Proxy Error Types: 30 | /// https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-http-proxy-error-types 31 | variant error-code { 32 | DNS-timeout, 33 | DNS-error(DNS-error-payload), 34 | destination-not-found, 35 | destination-unavailable, 36 | destination-IP-prohibited, 37 | destination-IP-unroutable, 38 | connection-refused, 39 | connection-terminated, 40 | connection-timeout, 41 | connection-read-timeout, 42 | connection-write-timeout, 43 | connection-limit-reached, 44 | TLS-protocol-error, 45 | TLS-certificate-error, 46 | TLS-alert-received(TLS-alert-received-payload), 47 | HTTP-request-denied, 48 | HTTP-request-length-required, 49 | HTTP-request-body-size(option), 50 | HTTP-request-method-invalid, 51 | HTTP-request-URI-invalid, 52 | HTTP-request-URI-too-long, 53 | HTTP-request-header-section-size(option), 54 | HTTP-request-header-size(option), 55 | HTTP-request-trailer-section-size(option), 56 | HTTP-request-trailer-size(field-size-payload), 57 | HTTP-response-incomplete, 58 | HTTP-response-header-section-size(option), 59 | HTTP-response-header-size(field-size-payload), 60 | HTTP-response-body-size(option), 61 | HTTP-response-trailer-section-size(option), 62 | HTTP-response-trailer-size(field-size-payload), 63 | HTTP-response-transfer-coding(option), 64 | HTTP-response-content-coding(option), 65 | HTTP-response-timeout, 66 | HTTP-upgrade-failed, 67 | HTTP-protocol-error, 68 | loop-detected, 69 | configuration-error, 70 | /// This is a catch-all error for anything that doesn't fit cleanly into a 71 | /// more specific case. It also includes an optional string for an 72 | /// unstructured description of the error. Users should not depend on the 73 | /// string for diagnosing errors, as it's not required to be consistent 74 | /// between implementations. 75 | internal-error(option) 76 | } 77 | 78 | /// Defines the case payload type for `DNS-error` above: 79 | record DNS-error-payload { 80 | rcode: option, 81 | info-code: option 82 | } 83 | 84 | /// Defines the case payload type for `TLS-alert-received` above: 85 | record TLS-alert-received-payload { 86 | alert-id: option, 87 | alert-message: option 88 | } 89 | 90 | /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above: 91 | record field-size-payload { 92 | field-name: option, 93 | field-size: option 94 | } 95 | 96 | /// Attempts to extract a http-related `error-code` from the stream `error` 97 | /// provided. 98 | /// 99 | /// Stream operations may fail with a stream `error` with more information 100 | /// about the operation that failed. This `error` can be passed to this 101 | /// function to see if there's http-related information about the error to 102 | /// return. 103 | /// 104 | /// Note that this function is fallible because not all stream errors are 105 | /// http-related errors. 106 | http-error-code: func(err: borrow) -> option; 107 | 108 | /// This type enumerates the different kinds of errors that may occur when 109 | /// setting or appending to a `fields` resource. 110 | variant header-error { 111 | /// This error indicates that a `field-key` or `field-value` was 112 | /// syntactically invalid when used with an operation that sets headers in a 113 | /// `fields`. 114 | invalid-syntax, 115 | 116 | /// This error indicates that a forbidden `field-key` was used when trying 117 | /// to set a header in a `fields`. 118 | forbidden, 119 | 120 | /// This error indicates that the operation on the `fields` was not 121 | /// permitted because the fields are immutable. 122 | immutable, 123 | } 124 | 125 | /// This type enumerates the different kinds of errors that may occur when 126 | /// setting fields of a `request-options` resource. 127 | variant request-options-error { 128 | /// Indicates the specified field is not supported by this implementation. 129 | not-supported, 130 | 131 | /// Indicates that the operation on the `request-options` was not permitted 132 | /// because it is immutable. 133 | immutable, 134 | } 135 | 136 | /// Field keys are always strings. 137 | type field-key = string; 138 | 139 | /// Field values should always be ASCII strings. However, in 140 | /// reality, HTTP implementations often have to interpret malformed values, 141 | /// so they are provided as a list of bytes. 142 | type field-value = list; 143 | 144 | /// This following block defines the `fields` resource which corresponds to 145 | /// HTTP standard Fields. Fields are a common representation used for both 146 | /// Headers and Trailers. 147 | /// 148 | /// A `fields` may be mutable or immutable. A `fields` created using the 149 | /// constructor, `from-list`, or `clone` will be mutable, but a `fields` 150 | /// resource given by other means (including, but not limited to, 151 | /// `request.headers`) might be be immutable. In an immutable fields, the 152 | /// `set`, `append`, and `delete` operations will fail with 153 | /// `header-error.immutable`. 154 | resource fields { 155 | 156 | /// Construct an empty HTTP Fields. 157 | /// 158 | /// The resulting `fields` is mutable. 159 | constructor(); 160 | 161 | /// Construct an HTTP Fields. 162 | /// 163 | /// The resulting `fields` is mutable. 164 | /// 165 | /// The list represents each key-value pair in the Fields. Keys 166 | /// which have multiple values are represented by multiple entries in this 167 | /// list with the same key. 168 | /// 169 | /// The tuple is a pair of the field key, represented as a string, and 170 | /// Value, represented as a list of bytes. In a valid Fields, all keys 171 | /// and values are valid UTF-8 strings. However, values are not always 172 | /// well-formed, so they are represented as a raw list of bytes. 173 | /// 174 | /// An error result will be returned if any header or value was 175 | /// syntactically invalid, or if a header was forbidden. 176 | from-list: static func( 177 | entries: list> 178 | ) -> result; 179 | 180 | /// Get all of the values corresponding to a key. If the key is not present 181 | /// in this `fields`, an empty list is returned. However, if the key is 182 | /// present but empty, this is represented by a list with one or more 183 | /// empty field-values present. 184 | get: func(name: field-key) -> list; 185 | 186 | /// Returns `true` when the key is present in this `fields`. If the key is 187 | /// syntactically invalid, `false` is returned. 188 | has: func(name: field-key) -> bool; 189 | 190 | /// Set all of the values for a key. Clears any existing values for that 191 | /// key, if they have been set. 192 | /// 193 | /// Fails with `header-error.immutable` if the `fields` are immutable. 194 | set: func(name: field-key, value: list) -> result<_, header-error>; 195 | 196 | /// Delete all values for a key. Does nothing if no values for the key 197 | /// exist. 198 | /// 199 | /// Returns any values previously corresponding to the key. 200 | /// 201 | /// Fails with `header-error.immutable` if the `fields` are immutable. 202 | delete: func(name: field-key) -> result, header-error>; 203 | 204 | /// Append a value for a key. Does not change or delete any existing 205 | /// values for that key. 206 | /// 207 | /// Fails with `header-error.immutable` if the `fields` are immutable. 208 | append: func(name: field-key, value: field-value) -> result<_, header-error>; 209 | 210 | /// Retrieve the full set of keys and values in the Fields. Like the 211 | /// constructor, the list represents each key-value pair. 212 | /// 213 | /// The outer list represents each key-value pair in the Fields. Keys 214 | /// which have multiple values are represented by multiple entries in this 215 | /// list with the same key. 216 | entries: func() -> list>; 217 | 218 | /// Make a deep copy of the Fields. Equivelant in behavior to calling the 219 | /// `fields` constructor on the return value of `entries`. The resulting 220 | /// `fields` is mutable. 221 | clone: func() -> fields; 222 | } 223 | 224 | /// Headers is an alias for Fields. 225 | type headers = fields; 226 | 227 | /// Trailers is an alias for Fields. 228 | type trailers = fields; 229 | 230 | /// Represents an HTTP Request or Response's Body. 231 | /// 232 | /// A body has both its contents - a stream of bytes - and a (possibly empty) 233 | /// set of trailers, indicating that the full contents of the body have been 234 | /// received. This resource represents the contents as a `stream` and the 235 | /// delivery of trailers as a `trailers`, and ensures that the user of this 236 | /// interface may only be consuming either the body contents or waiting on 237 | /// trailers at any given time. 238 | resource body { 239 | 240 | /// Construct a new `body` with the specified stream and trailers. 241 | constructor( 242 | %stream: stream, 243 | trailers: option> 244 | ); 245 | 246 | /// Returns the contents of the body, as a stream of bytes. 247 | /// 248 | /// This function may be called multiple times as long as any `stream`s 249 | /// returned by previous calls have been dropped first. 250 | %stream: func() -> result>; 251 | 252 | /// Takes ownership of `body`, and returns a `trailers`. This function will 253 | /// trap if a `stream` child is still alive. 254 | finish: static func(this: body) -> result, error-code>; 255 | } 256 | 257 | /// Represents an HTTP Request. 258 | resource request { 259 | 260 | /// Construct a new `request` with a default `method` of `GET`, and 261 | /// `none` values for `path-with-query`, `scheme`, and `authority`. 262 | /// 263 | /// * `headers` is the HTTP Headers for the Response. 264 | /// * `body` is the contents of the body, as a stream of bytes. 265 | /// * `trailers` is an optional `future` which resolves to the HTTP Trailers 266 | /// for the Response. 267 | /// * `options` is optional `request-options` to be used if the request is 268 | /// sent over a network connection. 269 | /// 270 | /// It is possible to construct, or manipulate with the accessor functions 271 | /// below, an `request` with an invalid combination of `scheme` 272 | /// and `authority`, or `headers` which are not permitted to be sent. 273 | /// It is the obligation of the `handler.handle` implementation 274 | /// to reject invalid constructions of `request`. 275 | constructor( 276 | headers: headers, 277 | body: body, 278 | options: option 279 | ); 280 | 281 | /// Get the Method for the Request. 282 | method: func() -> method; 283 | /// Set the Method for the Request. Fails if the string present in a 284 | /// `method.other` argument is not a syntactically valid method. 285 | set-method: func(method: method) -> result; 286 | 287 | /// Get the combination of the HTTP Path and Query for the Request. When 288 | /// `none`, this represents an empty Path and empty Query. 289 | path-with-query: func() -> option; 290 | /// Set the combination of the HTTP Path and Query for the Request. When 291 | /// `none`, this represents an empty Path and empty Query. Fails is the 292 | /// string given is not a syntactically valid path and query uri component. 293 | set-path-with-query: func(path-with-query: option) -> result; 294 | 295 | /// Get the HTTP Related Scheme for the Request. When `none`, the 296 | /// implementation may choose an appropriate default scheme. 297 | scheme: func() -> option; 298 | /// Set the HTTP Related Scheme for the Request. When `none`, the 299 | /// implementation may choose an appropriate default scheme. Fails if the 300 | /// string given is not a syntactically valid uri scheme. 301 | set-scheme: func(scheme: option) -> result; 302 | 303 | /// Get the HTTP Authority for the Request. A value of `none` may be used 304 | /// with Related Schemes which do not require an Authority. The HTTP and 305 | /// HTTPS schemes always require an authority. 306 | authority: func() -> option; 307 | /// Set the HTTP Authority for the Request. A value of `none` may be used 308 | /// with Related Schemes which do not require an Authority. The HTTP and 309 | /// HTTPS schemes always require an authority. Fails if the string given is 310 | /// not a syntactically valid uri authority. 311 | set-authority: func(authority: option) -> result; 312 | 313 | /// Get the `request-options` to be associated with this request 314 | /// 315 | /// The returned `request-options` resource is immutable: `set-*` operations 316 | /// will fail if invoked. 317 | /// 318 | /// This `request-options` resource is a child: it must be dropped before 319 | /// the parent `request` is dropped, or its ownership is transfered to 320 | /// another component by e.g. `handler.handle`. 321 | options: func() -> option; 322 | 323 | /// Get the headers associated with the Request. 324 | /// 325 | /// The returned `headers` resource is immutable: `set`, `append`, and 326 | /// `delete` operations will fail with `header-error.immutable`. 327 | /// 328 | /// This headers resource is a child: it must be dropped before the parent 329 | /// `request` is dropped, or its ownership is transfered to another 330 | /// component by e.g. `handler.handle`. 331 | headers: func() -> headers; 332 | 333 | /// Get the body associated with the Request. 334 | /// 335 | /// This body resource is a child: it must be dropped before the parent 336 | /// `request` is dropped, or its ownership is transfered to another 337 | /// component by e.g. `handler.handle`. 338 | body: func() -> body; 339 | 340 | /// Takes ownership of the `request` and returns the `headers` and `body`. 341 | into-parts: static func(this: request) -> tuple; 342 | } 343 | 344 | /// Parameters for making an HTTP Request. Each of these parameters is 345 | /// currently an optional timeout applicable to the transport layer of the 346 | /// HTTP protocol. 347 | /// 348 | /// These timeouts are separate from any the user may use to bound an 349 | /// asynchronous call. 350 | resource request-options { 351 | /// Construct a default `request-options` value. 352 | constructor(); 353 | 354 | /// The timeout for the initial connect to the HTTP Server. 355 | connect-timeout: func() -> option; 356 | 357 | /// Set the timeout for the initial connect to the HTTP Server. An error 358 | /// return value indicates that this timeout is not supported or that this 359 | /// handle is immutable. 360 | set-connect-timeout: func(duration: option) -> result<_, request-options-error>; 361 | 362 | /// The timeout for receiving the first byte of the Response body. 363 | first-byte-timeout: func() -> option; 364 | 365 | /// Set the timeout for receiving the first byte of the Response body. An 366 | /// error return value indicates that this timeout is not supported or that 367 | /// this handle is immutable. 368 | set-first-byte-timeout: func(duration: option) -> result<_, request-options-error>; 369 | 370 | /// The timeout for receiving subsequent chunks of bytes in the Response 371 | /// body stream. 372 | between-bytes-timeout: func() -> option; 373 | 374 | /// Set the timeout for receiving subsequent chunks of bytes in the Response 375 | /// body stream. An error return value indicates that this timeout is not 376 | /// supported or that this handle is immutable. 377 | set-between-bytes-timeout: func(duration: option) -> result<_, request-options-error>; 378 | } 379 | 380 | /// This type corresponds to the HTTP standard Status Code. 381 | type status-code = u16; 382 | 383 | /// Represents an HTTP Response. 384 | resource response { 385 | 386 | /// Construct an `response`, with a default `status-code` of `200`. If a 387 | /// different `status-code` is needed, it must be set via the 388 | /// `set-status-code` method. 389 | /// 390 | /// * `headers` is the HTTP Headers for the Response. 391 | /// * `body` is the contents of the body, as a stream of bytes. 392 | /// * `trailers` is an optional `future` which resolves to the HTTP Trailers 393 | /// for the Response. 394 | constructor( 395 | headers: headers, 396 | body: body, 397 | ); 398 | 399 | /// Get the HTTP Status Code for the Response. 400 | status-code: func() -> status-code; 401 | 402 | /// Set the HTTP Status Code for the Response. Fails if the status-code 403 | /// given is not a valid http status code. 404 | set-status-code: func(status-code: status-code) -> result; 405 | 406 | /// Get the headers associated with the Request. 407 | /// 408 | /// The returned `headers` resource is immutable: `set`, `append`, and 409 | /// `delete` operations will fail with `header-error.immutable`. 410 | /// 411 | /// This headers resource is a child: it must be dropped before the parent 412 | /// `response` is dropped, or its ownership is transfered to another 413 | /// component by e.g. `handler.handle`. 414 | headers: func() -> headers; 415 | 416 | /// Get the body associated with the Response. 417 | /// 418 | /// This body resource is a child: it must be dropped before the parent 419 | /// `response` is dropped, or its ownership is transfered to another 420 | /// component by e.g. `handler.handle`. 421 | body: func() -> body; 422 | 423 | /// Takes ownership of the `response` and returns the `headers` and `body`. 424 | into-parts: static func(this: response) -> tuple; 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /wit/deps/io/error.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | 4 | interface error { 5 | /// A resource which represents some error information. 6 | /// 7 | /// The only method provided by this resource is `to-debug-string`, 8 | /// which provides some human-readable information about the error. 9 | /// 10 | /// In the `wasi:io` package, this resource is returned through the 11 | /// `wasi:io/streams/stream-error` type. 12 | /// 13 | /// To provide more specific error information, other interfaces may 14 | /// provide functions to further "downcast" this error into more specific 15 | /// error information. For example, `error`s returned in streams derived 16 | /// from filesystem types to be described using the filesystem's own 17 | /// error-code type, using the function 18 | /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter 19 | /// `borrow` and returns 20 | /// `option`. 21 | /// 22 | /// The set of functions which can "downcast" an `error` into a more 23 | /// concrete type is open. 24 | resource error { 25 | /// Returns a string that is suitable to assist humans in debugging 26 | /// this error. 27 | /// 28 | /// WARNING: The returned string should not be consumed mechanically! 29 | /// It may change across platforms, hosts, or other implementation 30 | /// details. Parsing this string is a major platform-compatibility 31 | /// hazard. 32 | to-debug-string: func() -> string; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /wit/deps/io/poll.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | /// A poll API intended to let users wait for I/O events on multiple handles 4 | /// at once. 5 | interface poll { 6 | /// `pollable` represents a single I/O event which may be ready, or not. 7 | resource pollable { 8 | 9 | /// Return the readiness of a pollable. This function never blocks. 10 | /// 11 | /// Returns `true` when the pollable is ready, and `false` otherwise. 12 | ready: func() -> bool; 13 | 14 | /// `block` returns immediately if the pollable is ready, and otherwise 15 | /// blocks until ready. 16 | /// 17 | /// This function is equivalent to calling `poll.poll` on a list 18 | /// containing only this pollable. 19 | block: func(); 20 | } 21 | 22 | /// Poll for completion on a set of pollables. 23 | /// 24 | /// This function takes a list of pollables, which identify I/O sources of 25 | /// interest, and waits until one or more of the events is ready for I/O. 26 | /// 27 | /// The result `list` contains one or more indices of handles in the 28 | /// argument list that is ready for I/O. 29 | /// 30 | /// If the list contains more elements than can be indexed with a `u32` 31 | /// value, this function traps. 32 | /// 33 | /// A timeout can be implemented by adding a pollable from the 34 | /// wasi-clocks API to the list. 35 | /// 36 | /// This function does not return a `result`; polling in itself does not 37 | /// do any I/O so it doesn't fail. If any of the I/O sources identified by 38 | /// the pollables has an error, it is indicated by marking the source as 39 | /// being reaedy for I/O. 40 | poll: func(in: list>) -> list; 41 | } 42 | -------------------------------------------------------------------------------- /wit/deps/io/streams.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | /// WASI I/O is an I/O abstraction API which is currently focused on providing 4 | /// stream types. 5 | /// 6 | /// In the future, the component model is expected to add built-in stream types; 7 | /// when it does, they are expected to subsume this API. 8 | interface streams { 9 | use error.{error}; 10 | use poll.{pollable}; 11 | 12 | /// An error for input-stream and output-stream operations. 13 | variant stream-error { 14 | /// The last operation (a write or flush) failed before completion. 15 | /// 16 | /// More information is available in the `error` payload. 17 | last-operation-failed(error), 18 | /// The stream is closed: no more input will be accepted by the 19 | /// stream. A closed output-stream will return this error on all 20 | /// future operations. 21 | closed 22 | } 23 | 24 | /// An input bytestream. 25 | /// 26 | /// `input-stream`s are *non-blocking* to the extent practical on underlying 27 | /// platforms. I/O operations always return promptly; if fewer bytes are 28 | /// promptly available than requested, they return the number of bytes promptly 29 | /// available, which could even be zero. To wait for data to be available, 30 | /// use the `subscribe` function to obtain a `pollable` which can be polled 31 | /// for using `wasi:io/poll`. 32 | resource input-stream { 33 | /// Perform a non-blocking read from the stream. 34 | /// 35 | /// When the source of a `read` is binary data, the bytes from the source 36 | /// are returned verbatim. When the source of a `read` is known to the 37 | /// implementation to be text, bytes containing the UTF-8 encoding of the 38 | /// text are returned. 39 | /// 40 | /// This function returns a list of bytes containing the read data, 41 | /// when successful. The returned list will contain up to `len` bytes; 42 | /// it may return fewer than requested, but not more. The list is 43 | /// empty when no bytes are available for reading at this time. The 44 | /// pollable given by `subscribe` will be ready when more bytes are 45 | /// available. 46 | /// 47 | /// This function fails with a `stream-error` when the operation 48 | /// encounters an error, giving `last-operation-failed`, or when the 49 | /// stream is closed, giving `closed`. 50 | /// 51 | /// When the caller gives a `len` of 0, it represents a request to 52 | /// read 0 bytes. If the stream is still open, this call should 53 | /// succeed and return an empty list, or otherwise fail with `closed`. 54 | /// 55 | /// The `len` parameter is a `u64`, which could represent a list of u8 which 56 | /// is not possible to allocate in wasm32, or not desirable to allocate as 57 | /// as a return value by the callee. The callee may return a list of bytes 58 | /// less than `len` in size while more bytes are available for reading. 59 | read: func( 60 | /// The maximum number of bytes to read 61 | len: u64 62 | ) -> result, stream-error>; 63 | 64 | /// Read bytes from a stream, after blocking until at least one byte can 65 | /// be read. Except for blocking, behavior is identical to `read`. 66 | blocking-read: func( 67 | /// The maximum number of bytes to read 68 | len: u64 69 | ) -> result, stream-error>; 70 | 71 | /// Skip bytes from a stream. Returns number of bytes skipped. 72 | /// 73 | /// Behaves identical to `read`, except instead of returning a list 74 | /// of bytes, returns the number of bytes consumed from the stream. 75 | skip: func( 76 | /// The maximum number of bytes to skip. 77 | len: u64, 78 | ) -> result; 79 | 80 | /// Skip bytes from a stream, after blocking until at least one byte 81 | /// can be skipped. Except for blocking behavior, identical to `skip`. 82 | blocking-skip: func( 83 | /// The maximum number of bytes to skip. 84 | len: u64, 85 | ) -> result; 86 | 87 | /// Create a `pollable` which will resolve once either the specified stream 88 | /// has bytes available to read or the other end of the stream has been 89 | /// closed. 90 | /// The created `pollable` is a child resource of the `input-stream`. 91 | /// Implementations may trap if the `input-stream` is dropped before 92 | /// all derived `pollable`s created with this function are dropped. 93 | subscribe: func() -> pollable; 94 | } 95 | 96 | 97 | /// An output bytestream. 98 | /// 99 | /// `output-stream`s are *non-blocking* to the extent practical on 100 | /// underlying platforms. Except where specified otherwise, I/O operations also 101 | /// always return promptly, after the number of bytes that can be written 102 | /// promptly, which could even be zero. To wait for the stream to be ready to 103 | /// accept data, the `subscribe` function to obtain a `pollable` which can be 104 | /// polled for using `wasi:io/poll`. 105 | resource output-stream { 106 | /// Check readiness for writing. This function never blocks. 107 | /// 108 | /// Returns the number of bytes permitted for the next call to `write`, 109 | /// or an error. Calling `write` with more bytes than this function has 110 | /// permitted will trap. 111 | /// 112 | /// When this function returns 0 bytes, the `subscribe` pollable will 113 | /// become ready when this function will report at least 1 byte, or an 114 | /// error. 115 | check-write: func() -> result; 116 | 117 | /// Perform a write. This function never blocks. 118 | /// 119 | /// When the destination of a `write` is binary data, the bytes from 120 | /// `contents` are written verbatim. When the destination of a `write` is 121 | /// known to the implementation to be text, the bytes of `contents` are 122 | /// transcoded from UTF-8 into the encoding of the destination and then 123 | /// written. 124 | /// 125 | /// Precondition: check-write gave permit of Ok(n) and contents has a 126 | /// length of less than or equal to n. Otherwise, this function will trap. 127 | /// 128 | /// returns Err(closed) without writing if the stream has closed since 129 | /// the last call to check-write provided a permit. 130 | write: func( 131 | contents: list 132 | ) -> result<_, stream-error>; 133 | 134 | /// Perform a write of up to 4096 bytes, and then flush the stream. Block 135 | /// until all of these operations are complete, or an error occurs. 136 | /// 137 | /// This is a convenience wrapper around the use of `check-write`, 138 | /// `subscribe`, `write`, and `flush`, and is implemented with the 139 | /// following pseudo-code: 140 | /// 141 | /// ```text 142 | /// let pollable = this.subscribe(); 143 | /// while !contents.is_empty() { 144 | /// // Wait for the stream to become writable 145 | /// pollable.block(); 146 | /// let Ok(n) = this.check-write(); // eliding error handling 147 | /// let len = min(n, contents.len()); 148 | /// let (chunk, rest) = contents.split_at(len); 149 | /// this.write(chunk ); // eliding error handling 150 | /// contents = rest; 151 | /// } 152 | /// this.flush(); 153 | /// // Wait for completion of `flush` 154 | /// pollable.block(); 155 | /// // Check for any errors that arose during `flush` 156 | /// let _ = this.check-write(); // eliding error handling 157 | /// ``` 158 | blocking-write-and-flush: func( 159 | contents: list 160 | ) -> result<_, stream-error>; 161 | 162 | /// Request to flush buffered output. This function never blocks. 163 | /// 164 | /// This tells the output-stream that the caller intends any buffered 165 | /// output to be flushed. the output which is expected to be flushed 166 | /// is all that has been passed to `write` prior to this call. 167 | /// 168 | /// Upon calling this function, the `output-stream` will not accept any 169 | /// writes (`check-write` will return `ok(0)`) until the flush has 170 | /// completed. The `subscribe` pollable will become ready when the 171 | /// flush has completed and the stream can accept more writes. 172 | flush: func() -> result<_, stream-error>; 173 | 174 | /// Request to flush buffered output, and block until flush completes 175 | /// and stream is ready for writing again. 176 | blocking-flush: func() -> result<_, stream-error>; 177 | 178 | /// Create a `pollable` which will resolve once the output-stream 179 | /// is ready for more writing, or an error has occured. When this 180 | /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an 181 | /// error. 182 | /// 183 | /// If the stream is closed, this pollable is always ready immediately. 184 | /// 185 | /// The created `pollable` is a child resource of the `output-stream`. 186 | /// Implementations may trap if the `output-stream` is dropped before 187 | /// all derived `pollable`s created with this function are dropped. 188 | subscribe: func() -> pollable; 189 | 190 | /// Write zeroes to a stream. 191 | /// 192 | /// This should be used precisely like `write` with the exact same 193 | /// preconditions (must use check-write first), but instead of 194 | /// passing a list of bytes, you simply pass the number of zero-bytes 195 | /// that should be written. 196 | write-zeroes: func( 197 | /// The number of zero-bytes to write 198 | len: u64 199 | ) -> result<_, stream-error>; 200 | 201 | /// Perform a write of up to 4096 zeroes, and then flush the stream. 202 | /// Block until all of these operations are complete, or an error 203 | /// occurs. 204 | /// 205 | /// This is a convenience wrapper around the use of `check-write`, 206 | /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with 207 | /// the following pseudo-code: 208 | /// 209 | /// ```text 210 | /// let pollable = this.subscribe(); 211 | /// while num_zeroes != 0 { 212 | /// // Wait for the stream to become writable 213 | /// pollable.block(); 214 | /// let Ok(n) = this.check-write(); // eliding error handling 215 | /// let len = min(n, num_zeroes); 216 | /// this.write-zeroes(len); // eliding error handling 217 | /// num_zeroes -= len; 218 | /// } 219 | /// this.flush(); 220 | /// // Wait for completion of `flush` 221 | /// pollable.block(); 222 | /// // Check for any errors that arose during `flush` 223 | /// let _ = this.check-write(); // eliding error handling 224 | /// ``` 225 | blocking-write-zeroes-and-flush: func( 226 | /// The number of zero-bytes to write 227 | len: u64 228 | ) -> result<_, stream-error>; 229 | 230 | /// Read from one stream and write to another. 231 | /// 232 | /// The behavior of splice is equivelant to: 233 | /// 1. calling `check-write` on the `output-stream` 234 | /// 2. calling `read` on the `input-stream` with the smaller of the 235 | /// `check-write` permitted length and the `len` provided to `splice` 236 | /// 3. calling `write` on the `output-stream` with that read data. 237 | /// 238 | /// Any error reported by the call to `check-write`, `read`, or 239 | /// `write` ends the splice and reports that error. 240 | /// 241 | /// This function returns the number of bytes transferred; it may be less 242 | /// than `len`. 243 | splice: func( 244 | /// The stream to read from 245 | src: borrow, 246 | /// The number of bytes to splice 247 | len: u64, 248 | ) -> result; 249 | 250 | /// Read from one stream and write to another, with blocking. 251 | /// 252 | /// This is similar to `splice`, except that it blocks until the 253 | /// `output-stream` is ready for writing, and the `input-stream` 254 | /// is ready for reading, before performing the `splice`. 255 | blocking-splice: func( 256 | /// The stream to read from 257 | src: borrow, 258 | /// The number of bytes to splice 259 | len: u64, 260 | ) -> result; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /wit/deps/io/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:io@0.2.0; 2 | 3 | world imports { 4 | import streams; 5 | import poll; 6 | } 7 | -------------------------------------------------------------------------------- /wit/deps/isyswasfa-io/pipe.wit: -------------------------------------------------------------------------------- 1 | interface pipe { 2 | use wasi:io/streams@0.2.0.{input-stream, output-stream}; 3 | 4 | make-pipe: func() -> tuple; 5 | } 6 | -------------------------------------------------------------------------------- /wit/deps/isyswasfa-io/poll.wit: -------------------------------------------------------------------------------- 1 | package isyswasfa:io; 2 | 3 | interface poll { 4 | use wasi:io/poll@0.2.0.{pollable}; 5 | use isyswasfa:isyswasfa/isyswasfa.{pending, ready}; 6 | 7 | block-isyswasfa-start: func(pollable: borrow) -> result<_, pending>; 8 | block-isyswasfa-result: func(ready: ready); 9 | } 10 | -------------------------------------------------------------------------------- /wit/deps/isyswasfa/isyswasfa.wit: -------------------------------------------------------------------------------- 1 | package isyswasfa:isyswasfa; 2 | 3 | interface isyswasfa { 4 | type state = u32; 5 | 6 | resource pending; 7 | resource cancel; 8 | resource ready { 9 | state: func() -> state; 10 | } 11 | 12 | record poll-input-listening { 13 | state: state, 14 | cancel: cancel 15 | } 16 | 17 | record poll-input-ready { 18 | state: state, 19 | ready: ready 20 | } 21 | 22 | record poll-input-cancel { 23 | state: state, 24 | cancel: cancel 25 | } 26 | 27 | variant poll-input { 28 | listening(poll-input-listening), 29 | ready(poll-input-ready), 30 | cancel(poll-input-cancel), 31 | cancel-complete(state) 32 | } 33 | 34 | record poll-output-ready { 35 | state: state, 36 | ready: ready 37 | } 38 | 39 | record poll-output-listen { 40 | state: state, 41 | pending: pending 42 | } 43 | 44 | record poll-output-pending { 45 | state: state, 46 | cancel: cancel 47 | } 48 | 49 | variant poll-output { 50 | ready(poll-output-ready), 51 | listen(poll-output-listen), 52 | pending(poll-output-pending), 53 | cancel(cancel), 54 | cancel-complete(cancel) 55 | } 56 | 57 | make-task: func() -> tuple; 58 | } 59 | -------------------------------------------------------------------------------- /wit/deps/random/insecure-seed.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure-seed interface for seeding hash-map DoS resistance. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure-seed { 7 | /// Return a 128-bit value that may contain a pseudo-random value. 8 | /// 9 | /// The returned value is not required to be computed from a CSPRNG, and may 10 | /// even be entirely deterministic. Host implementations are encouraged to 11 | /// provide pseudo-random values to any program exposed to 12 | /// attacker-controlled content, to enable DoS protection built into many 13 | /// languages' hash-map implementations. 14 | /// 15 | /// This function is intended to only be called once, by a source language 16 | /// to initialize Denial Of Service (DoS) protection in its hash-map 17 | /// implementation. 18 | /// 19 | /// # Expected future evolution 20 | /// 21 | /// This will likely be changed to a value import, to prevent it from being 22 | /// called multiple times and potentially used for purposes other than DoS 23 | /// protection. 24 | insecure-seed: func() -> tuple; 25 | } 26 | -------------------------------------------------------------------------------- /wit/deps/random/insecure.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// The insecure interface for insecure pseudo-random numbers. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface insecure { 7 | /// Return `len` insecure pseudo-random bytes. 8 | /// 9 | /// This function is not cryptographically secure. Do not use it for 10 | /// anything related to security. 11 | /// 12 | /// There are no requirements on the values of the returned bytes, however 13 | /// implementations are encouraged to return evenly distributed values with 14 | /// a long period. 15 | get-insecure-random-bytes: func(len: u64) -> list; 16 | 17 | /// Return an insecure pseudo-random `u64` value. 18 | /// 19 | /// This function returns the same type of pseudo-random data as 20 | /// `get-insecure-random-bytes`, represented as a `u64`. 21 | get-insecure-random-u64: func() -> u64; 22 | } 23 | -------------------------------------------------------------------------------- /wit/deps/random/random.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | /// WASI Random is a random data API. 3 | /// 4 | /// It is intended to be portable at least between Unix-family platforms and 5 | /// Windows. 6 | interface random { 7 | /// Return `len` cryptographically-secure random or pseudo-random bytes. 8 | /// 9 | /// This function must produce data at least as cryptographically secure and 10 | /// fast as an adequately seeded cryptographically-secure pseudo-random 11 | /// number generator (CSPRNG). It must not block, from the perspective of 12 | /// the calling program, under any circumstances, including on the first 13 | /// request and on requests for numbers of bytes. The returned data must 14 | /// always be unpredictable. 15 | /// 16 | /// This function must always return fresh data. Deterministic environments 17 | /// must omit this function, rather than implementing it with deterministic 18 | /// data. 19 | get-random-bytes: func(len: u64) -> list; 20 | 21 | /// Return a cryptographically-secure random or pseudo-random `u64` value. 22 | /// 23 | /// This function returns the same type of data as `get-random-bytes`, 24 | /// represented as a `u64`. 25 | get-random-u64: func() -> u64; 26 | } 27 | -------------------------------------------------------------------------------- /wit/deps/random/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:random@0.2.0; 2 | 3 | world imports { 4 | import random; 5 | import insecure; 6 | import insecure-seed; 7 | } 8 | -------------------------------------------------------------------------------- /wit/deps/sockets/instance-network.wit: -------------------------------------------------------------------------------- 1 | 2 | /// This interface provides a value-export of the default network handle.. 3 | interface instance-network { 4 | use network.{network}; 5 | 6 | /// Get a handle to the default network. 7 | instance-network: func() -> network; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /wit/deps/sockets/ip-name-lookup.wit: -------------------------------------------------------------------------------- 1 | 2 | interface ip-name-lookup { 3 | use wasi:io/poll@0.2.0.{pollable}; 4 | use network.{network, error-code, ip-address}; 5 | 6 | 7 | /// Resolve an internet host name to a list of IP addresses. 8 | /// 9 | /// Unicode domain names are automatically converted to ASCII using IDNA encoding. 10 | /// If the input is an IP address string, the address is parsed and returned 11 | /// as-is without making any external requests. 12 | /// 13 | /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. 14 | /// 15 | /// This function never blocks. It either immediately fails or immediately 16 | /// returns successfully with a `resolve-address-stream` that can be used 17 | /// to (asynchronously) fetch the results. 18 | /// 19 | /// # Typical errors 20 | /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. 21 | /// 22 | /// # References: 23 | /// - 24 | /// - 25 | /// - 26 | /// - 27 | resolve-addresses: func(network: borrow, name: string) -> result; 28 | 29 | resource resolve-address-stream { 30 | /// Returns the next address from the resolver. 31 | /// 32 | /// This function should be called multiple times. On each call, it will 33 | /// return the next address in connection order preference. If all 34 | /// addresses have been exhausted, this function returns `none`. 35 | /// 36 | /// This function never returns IPv4-mapped IPv6 addresses. 37 | /// 38 | /// # Typical errors 39 | /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) 40 | /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) 41 | /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) 42 | /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) 43 | resolve-next-address: func() -> result, error-code>; 44 | 45 | /// Create a `pollable` which will resolve once the stream is ready for I/O. 46 | /// 47 | /// Note: this function is here for WASI Preview2 only. 48 | /// It's planned to be removed when `future` is natively supported in Preview3. 49 | subscribe: func() -> pollable; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /wit/deps/sockets/network.wit: -------------------------------------------------------------------------------- 1 | 2 | interface network { 3 | /// An opaque resource that represents access to (a subset of) the network. 4 | /// This enables context-based security for networking. 5 | /// There is no need for this to map 1:1 to a physical network interface. 6 | resource network; 7 | 8 | /// Error codes. 9 | /// 10 | /// In theory, every API can return any error code. 11 | /// In practice, API's typically only return the errors documented per API 12 | /// combined with a couple of errors that are always possible: 13 | /// - `unknown` 14 | /// - `access-denied` 15 | /// - `not-supported` 16 | /// - `out-of-memory` 17 | /// - `concurrency-conflict` 18 | /// 19 | /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. 20 | enum error-code { 21 | /// Unknown error 22 | unknown, 23 | 24 | /// Access denied. 25 | /// 26 | /// POSIX equivalent: EACCES, EPERM 27 | access-denied, 28 | 29 | /// The operation is not supported. 30 | /// 31 | /// POSIX equivalent: EOPNOTSUPP 32 | not-supported, 33 | 34 | /// One of the arguments is invalid. 35 | /// 36 | /// POSIX equivalent: EINVAL 37 | invalid-argument, 38 | 39 | /// Not enough memory to complete the operation. 40 | /// 41 | /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY 42 | out-of-memory, 43 | 44 | /// The operation timed out before it could finish completely. 45 | timeout, 46 | 47 | /// This operation is incompatible with another asynchronous operation that is already in progress. 48 | /// 49 | /// POSIX equivalent: EALREADY 50 | concurrency-conflict, 51 | 52 | /// Trying to finish an asynchronous operation that: 53 | /// - has not been started yet, or: 54 | /// - was already finished by a previous `finish-*` call. 55 | /// 56 | /// Note: this is scheduled to be removed when `future`s are natively supported. 57 | not-in-progress, 58 | 59 | /// The operation has been aborted because it could not be completed immediately. 60 | /// 61 | /// Note: this is scheduled to be removed when `future`s are natively supported. 62 | would-block, 63 | 64 | 65 | /// The operation is not valid in the socket's current state. 66 | invalid-state, 67 | 68 | /// A new socket resource could not be created because of a system limit. 69 | new-socket-limit, 70 | 71 | /// A bind operation failed because the provided address is not an address that the `network` can bind to. 72 | address-not-bindable, 73 | 74 | /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. 75 | address-in-use, 76 | 77 | /// The remote address is not reachable 78 | remote-unreachable, 79 | 80 | 81 | /// The TCP connection was forcefully rejected 82 | connection-refused, 83 | 84 | /// The TCP connection was reset. 85 | connection-reset, 86 | 87 | /// A TCP connection was aborted. 88 | connection-aborted, 89 | 90 | 91 | /// The size of a datagram sent to a UDP socket exceeded the maximum 92 | /// supported size. 93 | datagram-too-large, 94 | 95 | 96 | /// Name does not exist or has no suitable associated IP addresses. 97 | name-unresolvable, 98 | 99 | /// A temporary failure in name resolution occurred. 100 | temporary-resolver-failure, 101 | 102 | /// A permanent failure in name resolution occurred. 103 | permanent-resolver-failure, 104 | } 105 | 106 | enum ip-address-family { 107 | /// Similar to `AF_INET` in POSIX. 108 | ipv4, 109 | 110 | /// Similar to `AF_INET6` in POSIX. 111 | ipv6, 112 | } 113 | 114 | type ipv4-address = tuple; 115 | type ipv6-address = tuple; 116 | 117 | variant ip-address { 118 | ipv4(ipv4-address), 119 | ipv6(ipv6-address), 120 | } 121 | 122 | record ipv4-socket-address { 123 | /// sin_port 124 | port: u16, 125 | /// sin_addr 126 | address: ipv4-address, 127 | } 128 | 129 | record ipv6-socket-address { 130 | /// sin6_port 131 | port: u16, 132 | /// sin6_flowinfo 133 | flow-info: u32, 134 | /// sin6_addr 135 | address: ipv6-address, 136 | /// sin6_scope_id 137 | scope-id: u32, 138 | } 139 | 140 | variant ip-socket-address { 141 | ipv4(ipv4-socket-address), 142 | ipv6(ipv6-socket-address), 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /wit/deps/sockets/tcp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface tcp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use tcp.{tcp-socket}; 5 | 6 | /// Create a new TCP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` 13 | /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-tcp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /wit/deps/sockets/udp-create-socket.wit: -------------------------------------------------------------------------------- 1 | 2 | interface udp-create-socket { 3 | use network.{network, error-code, ip-address-family}; 4 | use udp.{udp-socket}; 5 | 6 | /// Create a new UDP socket. 7 | /// 8 | /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. 9 | /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. 10 | /// 11 | /// This function does not require a network capability handle. This is considered to be safe because 12 | /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, 13 | /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. 14 | /// 15 | /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. 16 | /// 17 | /// # Typical errors 18 | /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) 19 | /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) 20 | /// 21 | /// # References: 22 | /// - 23 | /// - 24 | /// - 25 | /// - 26 | create-udp-socket: func(address-family: ip-address-family) -> result; 27 | } 28 | -------------------------------------------------------------------------------- /wit/deps/sockets/udp.wit: -------------------------------------------------------------------------------- 1 | 2 | interface udp { 3 | use wasi:io/poll@0.2.0.{pollable}; 4 | use network.{network, error-code, ip-socket-address, ip-address-family}; 5 | 6 | /// A received datagram. 7 | record incoming-datagram { 8 | /// The payload. 9 | /// 10 | /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. 11 | data: list, 12 | 13 | /// The source address. 14 | /// 15 | /// This field is guaranteed to match the remote address the stream was initialized with, if any. 16 | /// 17 | /// Equivalent to the `src_addr` out parameter of `recvfrom`. 18 | remote-address: ip-socket-address, 19 | } 20 | 21 | /// A datagram to be sent out. 22 | record outgoing-datagram { 23 | /// The payload. 24 | data: list, 25 | 26 | /// The destination address. 27 | /// 28 | /// The requirements on this field depend on how the stream was initialized: 29 | /// - with a remote address: this field must be None or match the stream's remote address exactly. 30 | /// - without a remote address: this field is required. 31 | /// 32 | /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`. 33 | remote-address: option, 34 | } 35 | 36 | 37 | 38 | /// A UDP socket handle. 39 | resource udp-socket { 40 | /// Bind the socket to a specific network on the provided IP address and port. 41 | /// 42 | /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which 43 | /// network interface(s) to bind to. 44 | /// If the port is zero, the socket will be bound to a random free port. 45 | /// 46 | /// # Typical errors 47 | /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) 48 | /// - `invalid-state`: The socket is already bound. (EINVAL) 49 | /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) 50 | /// - `address-in-use`: Address is already in use. (EADDRINUSE) 51 | /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) 52 | /// - `not-in-progress`: A `bind` operation is not in progress. 53 | /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) 54 | /// 55 | /// # Implementors note 56 | /// Unlike in POSIX, in WASI the bind operation is async. This enables 57 | /// interactive WASI hosts to inject permission prompts. Runtimes that 58 | /// don't want to make use of this ability can simply call the native 59 | /// `bind` as part of either `start-bind` or `finish-bind`. 60 | /// 61 | /// # References 62 | /// - 63 | /// - 64 | /// - 65 | /// - 66 | start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; 67 | finish-bind: func() -> result<_, error-code>; 68 | 69 | /// Set up inbound & outbound communication channels, optionally to a specific peer. 70 | /// 71 | /// This function only changes the local socket configuration and does not generate any network traffic. 72 | /// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well, 73 | /// based on the best network path to `remote-address`. 74 | /// 75 | /// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer: 76 | /// - `send` can only be used to send to this destination. 77 | /// - `receive` will only return datagrams sent from the provided `remote-address`. 78 | /// 79 | /// This method may be called multiple times on the same socket to change its association, but 80 | /// only the most recently returned pair of streams will be operational. Implementations may trap if 81 | /// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again. 82 | /// 83 | /// The POSIX equivalent in pseudo-code is: 84 | /// ```text 85 | /// if (was previously connected) { 86 | /// connect(s, AF_UNSPEC) 87 | /// } 88 | /// if (remote_address is Some) { 89 | /// connect(s, remote_address) 90 | /// } 91 | /// ``` 92 | /// 93 | /// Unlike in POSIX, the socket must already be explicitly bound. 94 | /// 95 | /// # Typical errors 96 | /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) 97 | /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) 98 | /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) 99 | /// - `invalid-state`: The socket is not bound. 100 | /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) 101 | /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) 102 | /// - `connection-refused`: The connection was refused. (ECONNREFUSED) 103 | /// 104 | /// # References 105 | /// - 106 | /// - 107 | /// - 108 | /// - 109 | %stream: func(remote-address: option) -> result, error-code>; 110 | 111 | /// Get the current bound address. 112 | /// 113 | /// POSIX mentions: 114 | /// > If the socket has not been bound to a local name, the value 115 | /// > stored in the object pointed to by `address` is unspecified. 116 | /// 117 | /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. 118 | /// 119 | /// # Typical errors 120 | /// - `invalid-state`: The socket is not bound to any local address. 121 | /// 122 | /// # References 123 | /// - 124 | /// - 125 | /// - 126 | /// - 127 | local-address: func() -> result; 128 | 129 | /// Get the address the socket is currently streaming to. 130 | /// 131 | /// # Typical errors 132 | /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) 133 | /// 134 | /// # References 135 | /// - 136 | /// - 137 | /// - 138 | /// - 139 | remote-address: func() -> result; 140 | 141 | /// Whether this is a IPv4 or IPv6 socket. 142 | /// 143 | /// Equivalent to the SO_DOMAIN socket option. 144 | address-family: func() -> ip-address-family; 145 | 146 | /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. 147 | /// 148 | /// If the provided value is 0, an `invalid-argument` error is returned. 149 | /// 150 | /// # Typical errors 151 | /// - `invalid-argument`: (set) The TTL value must be 1 or higher. 152 | unicast-hop-limit: func() -> result; 153 | set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; 154 | 155 | /// The kernel buffer space reserved for sends/receives on this socket. 156 | /// 157 | /// If the provided value is 0, an `invalid-argument` error is returned. 158 | /// Any other value will never cause an error, but it might be silently clamped and/or rounded. 159 | /// I.e. after setting a value, reading the same setting back may return a different value. 160 | /// 161 | /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. 162 | /// 163 | /// # Typical errors 164 | /// - `invalid-argument`: (set) The provided value was 0. 165 | receive-buffer-size: func() -> result; 166 | set-receive-buffer-size: func(value: u64) -> result<_, error-code>; 167 | send-buffer-size: func() -> result; 168 | set-send-buffer-size: func(value: u64) -> result<_, error-code>; 169 | 170 | /// Create a `pollable` which will resolve once the socket is ready for I/O. 171 | /// 172 | /// Note: this function is here for WASI Preview2 only. 173 | /// It's planned to be removed when `future` is natively supported in Preview3. 174 | subscribe: func() -> pollable; 175 | } 176 | 177 | resource incoming-datagram-stream { 178 | /// Receive messages on the socket. 179 | /// 180 | /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. 181 | /// The returned list may contain fewer elements than requested, but never more. 182 | /// 183 | /// This function returns successfully with an empty list when either: 184 | /// - `max-results` is 0, or: 185 | /// - `max-results` is greater than 0, but no results are immediately available. 186 | /// This function never returns `error(would-block)`. 187 | /// 188 | /// # Typical errors 189 | /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) 190 | /// - `connection-refused`: The connection was refused. (ECONNREFUSED) 191 | /// 192 | /// # References 193 | /// - 194 | /// - 195 | /// - 196 | /// - 197 | /// - 198 | /// - 199 | /// - 200 | /// - 201 | receive: func(max-results: u64) -> result, error-code>; 202 | 203 | /// Create a `pollable` which will resolve once the stream is ready to receive again. 204 | /// 205 | /// Note: this function is here for WASI Preview2 only. 206 | /// It's planned to be removed when `future` is natively supported in Preview3. 207 | subscribe: func() -> pollable; 208 | } 209 | 210 | resource outgoing-datagram-stream { 211 | /// Check readiness for sending. This function never blocks. 212 | /// 213 | /// Returns the number of datagrams permitted for the next call to `send`, 214 | /// or an error. Calling `send` with more datagrams than this function has 215 | /// permitted will trap. 216 | /// 217 | /// When this function returns ok(0), the `subscribe` pollable will 218 | /// become ready when this function will report at least ok(1), or an 219 | /// error. 220 | /// 221 | /// Never returns `would-block`. 222 | check-send: func() -> result; 223 | 224 | /// Send messages on the socket. 225 | /// 226 | /// This function attempts to send all provided `datagrams` on the socket without blocking and 227 | /// returns how many messages were actually sent (or queued for sending). This function never 228 | /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned. 229 | /// 230 | /// This function semantically behaves the same as iterating the `datagrams` list and sequentially 231 | /// sending each individual datagram until either the end of the list has been reached or the first error occurred. 232 | /// If at least one datagram has been sent successfully, this function never returns an error. 233 | /// 234 | /// If the input list is empty, the function returns `ok(0)`. 235 | /// 236 | /// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if 237 | /// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted. 238 | /// 239 | /// # Typical errors 240 | /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) 241 | /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) 242 | /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) 243 | /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN) 244 | /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) 245 | /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) 246 | /// - `connection-refused`: The connection was refused. (ECONNREFUSED) 247 | /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) 248 | /// 249 | /// # References 250 | /// - 251 | /// - 252 | /// - 253 | /// - 254 | /// - 255 | /// - 256 | /// - 257 | /// - 258 | send: func(datagrams: list) -> result; 259 | 260 | /// Create a `pollable` which will resolve once the stream is ready to send again. 261 | /// 262 | /// Note: this function is here for WASI Preview2 only. 263 | /// It's planned to be removed when `future` is natively supported in Preview3. 264 | subscribe: func() -> pollable; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /wit/deps/sockets/world.wit: -------------------------------------------------------------------------------- 1 | package wasi:sockets@0.2.0; 2 | 3 | world imports { 4 | import instance-network; 5 | import network; 6 | import udp; 7 | import udp-create-socket; 8 | import tcp; 9 | import tcp-create-socket; 10 | import ip-name-lookup; 11 | } 12 | -------------------------------------------------------------------------------- /wit/test.wit: -------------------------------------------------------------------------------- 1 | package component:test; 2 | 3 | interface baz { 4 | foo: func(s: string) -> string; 5 | } 6 | 7 | world round-trip { 8 | import wasi:clocks/monotonic-clock@0.2.0; 9 | import baz; 10 | export baz; 11 | } 12 | 13 | world proxy { 14 | import isyswasfa:io/pipe; 15 | import wasi:http/handler@0.3.0-draft; 16 | export wasi:http/handler@0.3.0-draft; 17 | } 18 | 19 | world router { 20 | import isyswasfa:io/pipe; 21 | import wasi:http/handler@0.3.0-draft; 22 | export wasi:http/handler@0.3.0-draft; 23 | 24 | import echo: interface { 25 | use wasi:http/types@0.3.0-draft.{request, response, error-code}; 26 | 27 | handle: func(request: request) -> result; 28 | } 29 | 30 | import hash-all: interface { 31 | use wasi:http/types@0.3.0-draft.{request, response, error-code}; 32 | 33 | handle: func(request: request) -> result; 34 | } 35 | 36 | import service: interface { 37 | use wasi:http/types@0.3.0-draft.{request, response, error-code}; 38 | 39 | handle: func(request: request) -> result; 40 | } 41 | 42 | import middleware: interface { 43 | use wasi:http/types@0.3.0-draft.{request, response, error-code}; 44 | 45 | handle: func(request: request) -> result; 46 | } 47 | } 48 | --------------------------------------------------------------------------------