From 1fd48d6b76563576e62c45ad6acb32a7a49727c7 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 1 Nov 2023 11:35:56 -0600 Subject: [PATCH] various updates to support `wasi:http@0.2.0-rc-2023-10-18` This fixes various issues: - Broken generated code indentation for resources in some cases - Type annotations that refer to non-yet-declared types confuse CPython, so we disable them - However, MyPy has no trouble with them, so we enable them by default for the `bindings` subcommand - Support WIT version annotations (i.e. pass them through to the generated component) - This partially addresses #19, but doesn't support importing or exporting multiple versions of the same interface - Update the `http` example to match `wasi:http@0.2.0-rc-2023-10-18` - Update to Wasmtime 14 and the latest `wit-parser`, `wit-component`, etc. - and update the WASI preview 1 adapter to match This also bumps the version to 0.6.0. Note that I've had to remove the `matrix-math` example since `wasmtime-py` does not yet support resources. Although the example itself doesn't use them, the new WASI Preview 1 adapter pulls them in as WASI Preview 2 imports, and there's no feasible way to work around that. Ideally, we'd provide the option to allow users to supply their own adapter, in which case we could use a pre-resource version of the adapter. However, that won't work given that pre-initialization is central to how `componentize-py` works. Hopefully we can bring back this example in the future, e.g. when `wasmtime-py` adds support for resources. Signed-off-by: Joel Dice --- Cargo.lock | 314 ++++--- Cargo.toml | 14 +- .../wasi_snapshot_preview1.reactor.wasm | Bin 102611 -> 0 bytes adapters/README.md | 8 + .../wasi_snapshot_preview1.reactor.wasm | Bin 0 -> 109510 bytes build.rs | 2 +- examples/http/README.md | 24 +- examples/http/app.py | 87 +- examples/http/poll_loop.py | 113 ++- examples/http/spin.toml | 16 - examples/http/wit/command-extended.wit | 37 + examples/http/wit/deps.lock | 34 - examples/http/wit/deps.toml | 8 - examples/http/wit/deps/cli/command.wit | 7 + examples/http/wit/deps/cli/environment.wit | 18 + examples/http/wit/deps/cli/exit.wit | 4 + examples/http/wit/deps/cli/reactor.wit | 32 + examples/http/wit/deps/cli/run.wit | 4 + examples/http/wit/deps/cli/stdio.wit | 17 + examples/http/wit/deps/cli/terminal.wit | 47 + .../http/wit/deps/clocks/monotonic-clock.wit | 32 + examples/http/wit/deps/clocks/timezone.wit | 48 ++ examples/http/wit/deps/clocks/wall-clock.wit | 41 + examples/http/wit/deps/clocks/world.wit | 7 + .../http/wit/deps/filesystem/preopens.wit | 6 + examples/http/wit/deps/filesystem/types.wit | 810 ++++++++++++++++++ examples/http/wit/deps/filesystem/world.wit | 6 + .../http/wit/deps/http/incoming-handler.wit | 24 + .../http/wit/deps/http/outgoing-handler.wit | 20 + examples/http/wit/deps/http/proxy.wit | 34 + examples/http/wit/deps/http/types.wit | 214 +++++ examples/http/wit/deps/io/poll.wit | 34 + examples/http/wit/deps/io/streams.wit | 289 +++++++ examples/http/wit/deps/io/streams2.wit | 228 ----- examples/http/wit/deps/io/world.wit | 7 +- examples/http/wit/deps/logging/logging.wit | 37 + examples/http/wit/deps/logging/world.wit | 5 + examples/http/wit/deps/poll/poll2.wit | 49 -- examples/http/wit/deps/poll/world.wit | 5 - .../http/wit/deps/random/insecure-seed.wit | 24 + examples/http/wit/deps/random/insecure.wit | 21 + examples/http/wit/deps/random/random.wit | 25 + examples/http/wit/deps/random/world.wit | 7 + .../wit/deps/sockets/instance-network.wit | 9 + .../http/wit/deps/sockets/ip-name-lookup.wit | 61 ++ examples/http/wit/deps/sockets/network.wit | 146 ++++ .../wit/deps/sockets/tcp-create-socket.wit | 26 + examples/http/wit/deps/sockets/tcp.wit | 268 ++++++ .../wit/deps/sockets/udp-create-socket.wit | 26 + examples/http/wit/deps/sockets/udp.wit | 213 +++++ examples/http/wit/deps/sockets/world.wit | 11 + examples/http/wit/incoming-handler.wit | 26 - examples/http/wit/main.wit | 33 + examples/http/wit/outgoing-handler.wit | 18 - examples/http/wit/proxy.wit | 34 - examples/http/wit/test.wit | 46 + examples/http/wit/types2.wit | 187 ---- examples/matrix-math/README.md | 47 - examples/matrix-math/guest.py | 20 - examples/matrix-math/host.py | 61 -- examples/matrix-math/wit/matrix-math.wit | 7 - pyproject.toml | 2 +- runtime/src/lib.rs | 25 +- src/bindings.rs | 28 +- src/command.rs | 5 + src/lib.rs | 68 +- src/summary.rs | 134 +-- src/test.rs | 10 +- src/test/tests.rs | 1 + wit/deps/cli/environment.wit | 8 +- wit/init.wit | 2 +- 71 files changed, 3170 insertions(+), 1111 deletions(-) delete mode 100644 adapters/40c1f9b8/wasi_snapshot_preview1.reactor.wasm create mode 100644 adapters/README.md create mode 100644 adapters/e8766e49/wasi_snapshot_preview1.reactor.wasm delete mode 100644 examples/http/spin.toml create mode 100644 examples/http/wit/command-extended.wit delete mode 100644 examples/http/wit/deps.lock delete mode 100644 examples/http/wit/deps.toml create mode 100644 examples/http/wit/deps/cli/command.wit create mode 100644 examples/http/wit/deps/cli/environment.wit create mode 100644 examples/http/wit/deps/cli/exit.wit create mode 100644 examples/http/wit/deps/cli/reactor.wit create mode 100644 examples/http/wit/deps/cli/run.wit create mode 100644 examples/http/wit/deps/cli/stdio.wit create mode 100644 examples/http/wit/deps/cli/terminal.wit create mode 100644 examples/http/wit/deps/clocks/monotonic-clock.wit create mode 100644 examples/http/wit/deps/clocks/timezone.wit create mode 100644 examples/http/wit/deps/clocks/wall-clock.wit create mode 100644 examples/http/wit/deps/clocks/world.wit create mode 100644 examples/http/wit/deps/filesystem/preopens.wit create mode 100644 examples/http/wit/deps/filesystem/types.wit create mode 100644 examples/http/wit/deps/filesystem/world.wit create mode 100644 examples/http/wit/deps/http/incoming-handler.wit create mode 100644 examples/http/wit/deps/http/outgoing-handler.wit create mode 100644 examples/http/wit/deps/http/proxy.wit create mode 100644 examples/http/wit/deps/http/types.wit create mode 100644 examples/http/wit/deps/io/poll.wit create mode 100644 examples/http/wit/deps/io/streams.wit delete mode 100644 examples/http/wit/deps/io/streams2.wit create mode 100644 examples/http/wit/deps/logging/logging.wit create mode 100644 examples/http/wit/deps/logging/world.wit delete mode 100644 examples/http/wit/deps/poll/poll2.wit delete mode 100644 examples/http/wit/deps/poll/world.wit create mode 100644 examples/http/wit/deps/random/insecure-seed.wit create mode 100644 examples/http/wit/deps/random/insecure.wit create mode 100644 examples/http/wit/deps/random/random.wit create mode 100644 examples/http/wit/deps/random/world.wit create mode 100644 examples/http/wit/deps/sockets/instance-network.wit create mode 100644 examples/http/wit/deps/sockets/ip-name-lookup.wit create mode 100644 examples/http/wit/deps/sockets/network.wit create mode 100644 examples/http/wit/deps/sockets/tcp-create-socket.wit create mode 100644 examples/http/wit/deps/sockets/tcp.wit create mode 100644 examples/http/wit/deps/sockets/udp-create-socket.wit create mode 100644 examples/http/wit/deps/sockets/udp.wit create mode 100644 examples/http/wit/deps/sockets/world.wit delete mode 100644 examples/http/wit/incoming-handler.wit create mode 100644 examples/http/wit/main.wit delete mode 100644 examples/http/wit/outgoing-handler.wit delete mode 100644 examples/http/wit/proxy.wit create mode 100644 examples/http/wit/test.wit delete mode 100644 examples/http/wit/types2.wit delete mode 100644 examples/matrix-math/README.md delete mode 100644 examples/matrix-math/guest.py delete mode 100644 examples/matrix-math/host.py delete mode 100644 examples/matrix-math/wit/matrix-math.wit diff --git a/Cargo.lock b/Cargo.lock index 0bff344..8d6442a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -378,7 +378,7 @@ dependencies = [ [[package]] name = "componentize-py" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "async-trait", @@ -406,8 +406,8 @@ dependencies = [ "wasmparser 0.107.0", "wasmtime", "wasmtime-wasi", - "wit-component 0.14.4 (git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources)", - "wit-parser 0.11.3 (git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources)", + "wit-component 0.17.0", + "wit-parser 0.12.2", "zstd", ] @@ -447,16 +447,18 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2e8afb79855941beeb29dc89cfcfb5d38fbcc732589e2e82f0f0733a0c78a3" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8c84aba220b17d4cffa52331ddbbeb3d76bb8c9bb426dd479dbc68b4f1389c" dependencies = [ "bumpalo", "cranelift-bforest", @@ -475,29 +477,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06fda9b1e600d27e5e37232b76902ec9fb45d73a560042d7f96fe3a30398d460" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1e9a33d5c789be4f1b9cdaaefe1b603d8b69e2f9f53fb228b4b9921e2888eef" [[package]] name = "cranelift-control" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349c700003b22bcb6693bdd71aed70dd34375596034c8f2d89c4acc4b50f1aa9" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5b171044e1ba920fc2f6e46643ade12c7a2fd7e8f31fd358041d61ae3b71ed" dependencies = [ "serde", "serde_derive", @@ -505,8 +511,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80d52604ae27ce70c4b82fe9e0a70c42b257ea37b7598131002b1be1ef5b632" dependencies = [ "cranelift-codegen", "log", @@ -516,13 +523,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fd1b23913fec3bb27cf7b4006c02e89ba70404205b936608a37c93bf4b0e60" [[package]] name = "cranelift-native" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcb8b2137c10bfef93a5f3ef9686c92a3973924b35e53cfadc7a1eb7c6afa9a" dependencies = [ "cranelift-codegen", "libc", @@ -531,8 +540,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.101.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.101.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ded60b90cf944b39119c87b996537b6811b7cf732e679236039ed877c1f570" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -540,7 +550,7 @@ dependencies = [ "itertools", "log", "smallvec", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.115.0", "wasmtime-types", ] @@ -2151,8 +2161,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi-cap-std-sync" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eaaf4e1482081cc477c795f518454cabc9bc55173f5f9b5144189b79926ac89" dependencies = [ "anyhow", "async-trait", @@ -2163,7 +2174,6 @@ dependencies = [ "fs-set-times", "io-extras", "io-lifetimes 2.0.2", - "is-terminal", "once_cell", "rustix 0.38.8", "system-interface", @@ -2174,8 +2184,9 @@ dependencies = [ [[package]] name = "wasi-common" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5de78fb343bbe397148fd4dbbe32a4dd19febdc4c97ddf7e529479f50ebd42a" dependencies = [ "anyhow", "bitflags 2.3.3", @@ -2229,39 +2240,36 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.33.2" -source = "git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources#1b82fa93dd164825cbb487115f9ffdf35e486a4a" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca90ba1b5b0a70d3d49473c5579951f3bddc78d47b59256d2f9d4922b150aca" dependencies = [ "leb128", ] [[package]] -name = "wasm-metadata" -version = "0.10.6" +name = "wasm-encoder" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577508d8a45bc54ad97efe77c95ba57bb10e7e5c5bac9c31295ce88b8045cd7d" +checksum = "53ae0be20bf87918df4fa831bfbbd0b491d24aee407ed86360eae4c2c5608d38" dependencies = [ - "anyhow", - "indexmap 2.0.0", - "serde", - "serde_json", - "spdx", - "wasm-encoder 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", + "leb128", ] [[package]] name = "wasm-metadata" -version = "0.10.6" -source = "git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources#1b82fa93dd164825cbb487115f9ffdf35e486a4a" +version = "0.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5621910462c61a8efc3248fdfb1739bf649bb335b0df935c27b340418105f9d8" dependencies = [ "anyhow", "indexmap 2.0.0", "serde", + "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.33.2 (git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources)", - "wasmparser 0.113.2 (git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources)", + "wasm-encoder 0.36.1", + "wasmparser 0.116.0", ] [[package]] @@ -2296,8 +2304,19 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.113.2" -source = "git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources#1b82fa93dd164825cbb487115f9ffdf35e486a4a" +version = "0.115.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e06c0641a4add879ba71ccb3a1e4278fd546f76f1eafb21d8f7b07733b547cd5" +dependencies = [ + "indexmap 2.0.0", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.116.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53290b1276c5c2d47d694fb1a920538c01f51690e7e261acbe1d10c5fc306ea1" dependencies = [ "indexmap 2.0.0", "semver", @@ -2305,18 +2324,19 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.2.67" +version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6615a5587149e753bf4b93f90fa3c3f41c88597a7a2da72879afcabeda9648f" +checksum = "8f98260aa20f939518bcec1fac32c78898d5c68872e7363a4651f21f791b6c7e" dependencies = [ "anyhow", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.116.0", ] [[package]] name = "wasmtime" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75bcf6b34483f487a6d6052a52621c304c832a62b1cacab0c8a756b5c8c6a5f" dependencies = [ "anyhow", "async-trait", @@ -2337,8 +2357,8 @@ dependencies = [ "serde_derive", "serde_json", "target-lexicon", - "wasm-encoder 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.35.0", + "wasmparser 0.115.0", "wasmtime-cache", "wasmtime-component-macro", "wasmtime-component-util", @@ -2354,16 +2374,18 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b7aef8d206203b4ab6b81869ac4d8f5b12bd57cfc91e6589a6fa65b76e4828" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d07bd0cc0f1455a675e572a5f4037b49910bcf289bd3b46c2fa7a55f8419852" dependencies = [ "anyhow", "base64", @@ -2381,8 +2403,9 @@ dependencies = [ [[package]] name = "wasmtime-component-macro" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5babf8d70d50b416ada364265113e3c8ba54b89568c4459a1c0f32bca458914" dependencies = [ "anyhow", "proc-macro2", @@ -2390,18 +2413,20 @@ dependencies = [ "syn 2.0.29", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-parser 0.12.2", ] [[package]] name = "wasmtime-component-util" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb20a705f7984971ba6a18cebfdaca1558d129513ad68c8702bc74ec58c014" [[package]] name = "wasmtime-cranelift" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1f3a28b18fb6525b6435c629541f11eeb0331883466fb5903be94ff8fd00c04" dependencies = [ "anyhow", "cfg-if", @@ -2416,7 +2441,7 @@ dependencies = [ "object 0.32.0", "target-lexicon", "thiserror", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.115.0", "wasmtime-cranelift-shared", "wasmtime-environ", "wasmtime-versioned-export-macros", @@ -2424,8 +2449,9 @@ dependencies = [ [[package]] name = "wasmtime-cranelift-shared" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074e06acac80c4e42e868d550c8eac509f678cb6eb7df6711d5df4f3b1864116" dependencies = [ "anyhow", "cranelift-codegen", @@ -2439,8 +2465,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9957ba8872fa96706837254ea1e0e2fa89ded856caf911e5fa23c2f6ee4d66" dependencies = [ "anyhow", "cranelift-entity", @@ -2452,8 +2479,8 @@ dependencies = [ "serde_derive", "target-lexicon", "thiserror", - "wasm-encoder 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.35.0", + "wasmparser 0.115.0", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -2461,8 +2488,9 @@ dependencies = [ [[package]] name = "wasmtime-fiber" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4db404c73af51a40b711a4b3a01072828e4739cecab1e906555a1d64f7bcb29a" dependencies = [ "cc", "cfg-if", @@ -2474,8 +2502,9 @@ dependencies = [ [[package]] name = "wasmtime-jit" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab6a509139d243234dfcf278b18ab773d545ec71093a548dd8e85a758ff373b" dependencies = [ "addr2line 0.21.0", "anyhow", @@ -2500,8 +2529,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fa18c58b8eefd886e79cd9beb7f29a3eb40675a4fd80ceb6f57aa92a4ed444" dependencies = [ "object 0.32.0", "once_cell", @@ -2511,8 +2541,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-icache-coherence" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5abc692642397258099b9e108489e75ac3f1cf6fc9bbf7ac1711d59d8e7a56aa" dependencies = [ "cfg-if", "libc", @@ -2521,8 +2552,9 @@ dependencies = [ [[package]] name = "wasmtime-runtime" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a54fbc6bd5c737431928b58a25fa32a182dcb08f3e52ccf6976646458df9ac" dependencies = [ "anyhow", "cc", @@ -2538,7 +2570,7 @@ dependencies = [ "rand", "rustix 0.38.8", "sptr", - "wasm-encoder 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.35.0", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-fiber", @@ -2550,20 +2582,22 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd11700810a5a4700702276447f3353a6fa35bd536809f70fca5c939bfdc7023" dependencies = [ "cranelift-entity", "serde", "serde_derive", "thiserror", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.115.0", ] [[package]] name = "wasmtime-versioned-export-macros" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2becb0edbd9d37a51febe1d70d3437a734a4b944c749103a0e6915cf9caa16e6" dependencies = [ "proc-macro2", "quote", @@ -2572,8 +2606,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f02b36cf36f6d631b80823d93c5617fb5fe3887be6f1ab5b9585e9dd277aab" dependencies = [ "anyhow", "async-trait", @@ -2588,14 +2623,15 @@ dependencies = [ "futures", "io-extras", "io-lifetimes 2.0.2", - "is-terminal", "libc", + "log", "once_cell", "rustix 0.38.8", "system-interface", "thiserror", "tokio", "tracing", + "url", "wasi-cap-std-sync", "wasi-common", "wasmtime", @@ -2605,15 +2641,16 @@ dependencies = [ [[package]] name = "wasmtime-winch" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ac94ed1ba605a6016f3b2e9738955b4f7f32446256263fea5b54048e540ae0" dependencies = [ "anyhow", "cranelift-codegen", "gimli 0.28.0", "object 0.32.0", "target-lexicon", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.115.0", "wasmtime-cranelift-shared", "wasmtime-environ", "winch-codegen", @@ -2621,19 +2658,21 @@ dependencies = [ [[package]] name = "wasmtime-wit-bindgen" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db258144dd97b7f42b0bcf1b238887d8bf47cf065c53ea9829b5bc8a6d47025a" dependencies = [ "anyhow", "heck", "indexmap 2.0.0", - "wit-parser 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-parser 0.12.2", ] [[package]] name = "wasmtime-wmemcheck" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58d40efa95290d370bc3a78e953b77200f9497f82caee2f91008bae1babc4cbc" [[package]] name = "wast" @@ -2646,29 +2685,30 @@ dependencies = [ [[package]] name = "wast" -version = "65.0.2" +version = "67.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55a88724cf8c2c0ebbf32c8e8f4ac0d6aa7ba6d73a1cfd94b254aa8f894317e" +checksum = "36c2933efd77ff2398b83817a98984ffe4b67aefd9aa1d2c8e68e19b553f1c38" dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.36.1", ] [[package]] name = "wat" -version = "1.0.74" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83e1a8d86d008adc7bafa5cf4332d448699a08fcf2a715a71fbb75e2c5ca188" +checksum = "c02905d13751dcb18f4e19f489d37a1bf139f519feaeef28d072a41a78e69a74" dependencies = [ - "wast 65.0.2", + "wast 67.0.0", ] [[package]] name = "wiggle" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879c332d154253c2421a47f365617b5265af3ce3afe1a35f40b3d3fe00fcd462" dependencies = [ "anyhow", "async-trait", @@ -2681,8 +2721,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b518fdef726cbe27a354b2706c86b1587ce99d29690c9c744fd720b0d27c72f3" dependencies = [ "anyhow", "heck", @@ -2695,8 +2736,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "14.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "14.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df654374ecf51dfd2ac27104ef68113762223913dd58e4d97d8376b496c48d55" dependencies = [ "proc-macro2", "quote", @@ -2737,8 +2779,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "0.12.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0a797b1e777c93eb144533114ce8f2f277ec3a505d7b212508b462e71dde2f" dependencies = [ "anyhow", "cranelift-codegen", @@ -2746,7 +2789,7 @@ dependencies = [ "regalloc2", "smallvec", "target-lexicon", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.115.0", "wasmtime-environ", ] @@ -2843,8 +2886,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f0371c47784e7559efb422f74473e395b49f7101725584e2673657e0b4fc104" dependencies = [ "anyhow", - "wit-component 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)", - "wit-parser 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-component 0.14.4", + "wit-parser 0.11.3", ] [[package]] @@ -2855,10 +2898,10 @@ checksum = "eeab5a09a85b1641690922ce05d79d868a2f2e78e9415a5302f58b9846fab8f1" dependencies = [ "anyhow", "heck", - "wasm-metadata 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-metadata", "wit-bindgen-core", "wit-bindgen-rust-lib", - "wit-component 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-component 0.14.4", ] [[package]] @@ -2883,7 +2926,7 @@ dependencies = [ "wit-bindgen-core", "wit-bindgen-rust", "wit-bindgen-rust-lib", - "wit-component 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wit-component 0.14.4", ] [[package]] @@ -2898,27 +2941,29 @@ dependencies = [ "log", "serde", "serde_json", - "wasm-encoder 0.33.2 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-metadata 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.113.2 (registry+https://github.com/rust-lang/crates.io-index)", - "wit-parser 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.33.2", + "wasm-metadata", + "wasmparser 0.113.2", + "wit-parser 0.11.3", ] [[package]] name = "wit-component" -version = "0.14.4" -source = "git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources#1b82fa93dd164825cbb487115f9ffdf35e486a4a" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "480cc1a078b305c1b8510f7c455c76cbd008ee49935f3a6c5fd5e937d8d95b1e" dependencies = [ "anyhow", "bitflags 2.3.3", "indexmap 2.0.0", "log", "serde", + "serde_derive", "serde_json", - "wasm-encoder 0.33.2 (git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources)", - "wasm-metadata 0.10.6 (git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources)", - "wasmparser 0.113.2 (git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources)", - "wit-parser 0.11.3 (git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources)", + "wasm-encoder 0.36.1", + "wasm-metadata", + "wasmparser 0.116.0", + "wit-parser 0.12.2", ] [[package]] @@ -2941,25 +2986,26 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.11.3" -source = "git+https://github.com/dicej/wasm-tools?branch=adapter-export-resources#1b82fa93dd164825cbb487115f9ffdf35e486a4a" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43771ee863a16ec4ecf9da0fc65c3bbd4a1235c8e3da5f094b562894843dfa76" dependencies = [ "anyhow", "id-arena", "indexmap 2.0.0", "log", - "pulldown-cmark", "semver", "serde", + "serde_derive", "serde_json", "unicode-xid", - "url", ] [[package]] name = "witx" version = "0.9.1" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=40c1f9b8b4f962ed763e47943e6ce0a3be8d1966#40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" dependencies = [ "anyhow", "log", diff --git a/Cargo.toml b/Cargo.toml index cdbbfe3..1c76044 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "componentize-py" -version = "0.5.0" +version = "0.6.0" edition = "2021" exclude = ["cpython"] @@ -17,17 +17,15 @@ zstd = "0.11.1" componentize-py-shared = { path = "shared" } wasmparser = "0.107.0" wasm-encoder = "0.29.0" -# TODO: switch to release once https://github.com/bytecodealliance/wasm-tools/pull/1226 is merged and released: -wit-parser = { git = "https://github.com/dicej/wasm-tools", branch = "adapter-export-resources" } -wit-component = { git = "https://github.com/dicej/wasm-tools", branch = "adapter-export-resources" } +wit-parser = "0.12.2" +wit-component = "0.17.0" indexmap = "2.0.0" bincode = "1.3.3" heck = "0.4.1" pyo3 = { version = "0.18.3", features = ["abi3-py37", "extension-module"], optional = true } -# TODO: switch to Wasmtime 14 when released: -wasmtime-wasi = { git = "https://github.com/bytecodealliance/wasmtime", rev = "40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" } -wasi-common = { git = "https://github.com/bytecodealliance/wasmtime", rev = "40c1f9b8b4f962ed763e47943e6ce0a3be8d1966" } -wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "40c1f9b8b4f962ed763e47943e6ce0a3be8d1966", features = [ "component-model" ] } +wasmtime-wasi = "14.0.3" +wasi-common = "14.0.3" +wasmtime = { version = "14.0.3", features = [ "component-model" ] } once_cell = "1.17.1" component-init = { git = "https://github.com/dicej/component-init" } async-trait = "0.1.68" diff --git a/adapters/40c1f9b8/wasi_snapshot_preview1.reactor.wasm b/adapters/40c1f9b8/wasi_snapshot_preview1.reactor.wasm deleted file mode 100644 index 9aaeb5eaaff166e0478ec204293d65fb198b0faf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102611 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 aV70d9&YoIp*Q#sX>(brUsSSQx_WuLSyvB(D diff --git a/adapters/README.md b/adapters/README.md new file mode 100644 index 0000000..0678724 --- /dev/null +++ b/adapters/README.md @@ -0,0 +1,8 @@ +The subdirectory of this directory contains a build of the WASI Preview 1 +component adapter. It was built from commit `e8766e49` of +https://github.com/dicej/wasmtime using the +`ci/build-wasi-preview1-component-adapter.sh` script. + +TODO: Switch back to upstream once +https://github.com/bytecodealliance/wasmtime/pull/7444 has been merged and +released. diff --git a/adapters/e8766e49/wasi_snapshot_preview1.reactor.wasm b/adapters/e8766e49/wasi_snapshot_preview1.reactor.wasm new file mode 100644 index 0000000000000000000000000000000000000000..31547531611164d66464edb05bdd6388f0c819b5 GIT binary patch literal 109510 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 IxD9Uo9}R&i-v9sr literal 0 HcmV?d00001 diff --git a/build.rs b/build.rs index 5d9fb74..655c5d8 100644 --- a/build.rs +++ b/build.rs @@ -185,7 +185,7 @@ fn package_all_the_things(out_dir: &Path) -> Result<()> { bail!("no such directory: {}", path.display()) } compress( - &repo_dir.join("adapters/40c1f9b8"), + &repo_dir.join("adapters/e8766e49"), "wasi_snapshot_preview1.reactor.wasm", out_dir, false, diff --git a/examples/http/README.md b/examples/http/README.md index c5f3664..c134e16 100644 --- a/examples/http/README.md +++ b/examples/http/README.md @@ -1,24 +1,27 @@ # Example: `http` -This is an example of how to use [componentize-py] and [Spin] to build and run a -Python-based component targetting the [wasi-http] `proxy` world. +This is an example of how to use [componentize-py] and [Wasmtime] to build and +run a Python-based component targetting the [wasi-http] `proxy` world. Note that, as of this writing, neither `wasi-http` nor the portions of `wasi-cli` on which it is based have stabilized. Here we use a snapshot of both, which may differ from later revisions. [componentize-py]: https://github.com/bytecodealliance/componentize-py -[Spin]: https://github.com/fermyon/spin +[Wasmtime]: https://github.com/bytecodealliance/wasmtime [wasi-http]: https://github.com/WebAssembly/wasi-http ## Prerequisites -* `dicej/spin` branch `wasi-http-wasmtime-2ad057d7` -* `componentize-py` 0.5.0 -* `Rust`, for installing `Spin` +* `Wasmtime` 14.0.3 (later versions may use a different, incompatible `wasi-http` snapshot) +* `componentize-py` 0.6.0 + +Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If +you don't have `cargo`, you can download and install from +https://github.com/bytecodealliance/wasmtime/releases/tag/v14.0.3. ``` -cargo install --locked --git https://github.com/dicej/spin --branch wasi-http-wasmtime-2ad057d7 spin-cli +cargo install --version 14.0.3 wasmtime-cli pip install componentize-py ``` @@ -27,13 +30,14 @@ pip install componentize-py First, build the app and run it: ``` -spin build --up +componentize-py -d wit -w proxy componentize app -o http.wasm +wasmtime serve http.wasm ``` Then, in another terminal, use cURL to send a request to the app: ``` -curl -i -H 'content-type: text/plain' --data-binary @- http://127.0.0.1:3000/echo < Tuple[str, str]: """Download the contents of the specified URL, computing the SHA-256 @@ -109,22 +107,20 @@ async def sha256(url: str) -> Tuple[str, str]: case _: scheme = SchemeOther(url_parsed.scheme) - request = types.new_outgoing_request( + request = OutgoingRequest( MethodGet(), url_parsed.path, scheme, url_parsed.netloc, - types.new_fields([]) + Fields([]) ) - response = await outgoing_request_send(request) - - status = types.incoming_response_status(response) + response = await poll_loop.send(request) + status = response.status() if status < 200 or status > 299: return url, f"unexpected status: {status}" - stream = Stream(types.incoming_response_consume(response)) - + stream = Stream(response.consume()) hasher = hashlib.sha256() while True: chunk = await stream.next() @@ -132,20 +128,3 @@ async def sha256(url: str) -> Tuple[str, str]: return url, hasher.hexdigest() else: hasher.update(chunk) - -async def outgoing_request_send(request: int) -> int: - """Send the specified request and wait asynchronously for the response.""" - - future = outgoing_handler.handle(request, None) - pollable = types.listen_to_future_incoming_response(future) - - while True: - response = types.future_incoming_response_get(future) - if response is None: - await poll_loop.register(cast(PollLoop, asyncio.get_event_loop()), pollable) - else: - if isinstance(response, Ok): - return response.value - else: - raise response - diff --git a/examples/http/poll_loop.py b/examples/http/poll_loop.py index 6de255d..e49293f 100644 --- a/examples/http/poll_loop.py +++ b/examples/http/poll_loop.py @@ -9,68 +9,104 @@ import socket import subprocess -from proxy.imports import types2 as types, streams2 as streams, poll2 as poll -from proxy.imports.streams2 import StreamStatus +from proxy.types import Ok, Err +from proxy.imports import types, streams, poll, outgoing_handler +from proxy.imports.types import IncomingBody, OutgoingBody, OutgoingRequest, IncomingResponse +from proxy.imports.streams import StreamErrorClosed, InputStream +from proxy.imports.poll import Pollable from typing import Optional, cast # Maximum number of bytes to read at a time READ_SIZE: int = 16 * 1024 +async def send(request: OutgoingRequest) -> IncomingResponse: + """Send the specified request and wait asynchronously for the response.""" + + future = outgoing_handler.handle(request, None) + + while True: + response = future.get() + if response is None: + await register(cast(PollLoop, asyncio.get_event_loop()), future.subscribe()) + else: + if isinstance(response, Ok): + if isinstance(response.value, Ok): + return response.value.value + else: + raise response.value + else: + raise response + class Stream: - """Reader abstraction over `wasi-cli`'s low-level stream pseudo-resource.""" - def __init__(self, stream: int): - self.pollable = streams.subscribe_to_input_stream(stream) - self.stream = stream - self.saw_end = False + """Reader abstraction over `wasi:http/types#incoming-body`.""" + def __init__(self, body: IncomingBody): + self.body: Optional[IncomingBody] = body + self.stream: Optional[InputStream] = body.stream() async def next(self) -> Optional[bytes]: """Wait for the next chunk of data to arrive on the stream. This will return `None` when the end of the stream has been reached. """ - if self.saw_end: - return None - else: - while True: - buffer, status = streams.read(self.stream, READ_SIZE) - if status == StreamStatus.ENDED: - types.finish_incoming_stream(self.stream) - self.saw_end = True - - if buffer: - return buffer - elif status == StreamStatus.ENDED: + while True: + try: + if self.stream is None: return None else: - await register(cast(PollLoop, asyncio.get_event_loop()), self.pollable) + buffer = self.stream.read(READ_SIZE) + if len(buffer) == 0: + await register(cast(PollLoop, asyncio.get_event_loop()), self.stream.subscribe()) + else: + return buffer + except Err as e: + if isinstance(e.value, StreamErrorClosed): + if self.stream is not None: + self.stream.drop() + self.stream = None + if self.body is not None: + IncomingBody.finish(self.body) + self.body = None + else: + raise e class Sink: - """Writer abstraction over `wasi-cli`'s low-level stream pseudo-resource.""" - def __init__(self, stream: int): - self.pollable = streams.subscribe_to_output_stream(stream) - self.stream = stream + """Writer abstraction over `wasi-http/types#outgoing-body`.""" + def __init__(self, body: OutgoingBody): + self.body = body + self.stream = body.write() async def send(self, chunk: bytes): - """Write the specified bytes to the stream. + """Write the specified bytes to the sink. - This may need to yield according to the backpressure requirements of the stream. + This may need to yield according to the backpressure requirements of the sink. """ offset = 0 + flushing = False while True: - count = streams.write(self.stream, chunk[offset:]) - offset += count - if offset == len(chunk): - return + count = self.stream.check_write() + if count == 0: + await register(cast(PollLoop, asyncio.get_event_loop()), self.stream.subscribe()) + elif offset == len(chunk): + if flushing: + return + else: + self.stream.flush() + flushing = True else: - await register(cast(PollLoop, asyncio.get_event_loop()), self.pollable) + count = min(count, len(chunk) - offset) + self.stream.write(chunk[offset:offset+count]) + offset += count def close(self): """Close the stream, indicating no further data will be written.""" - - types.finish_outgoing_stream(self.stream) + + self.stream.drop() + self.stream = None + OutgoingBody.finish(self.body, None) + self.body = None class PollLoop(asyncio.AbstractEventLoop): - """Custom `asyncio` event loop backed by WASI's `poll_oneoff` function.""" + """Custom `asyncio` event loop backed by `wasi:io/poll#poll-list`.""" def __init__(self): self.wakers = [] @@ -96,8 +132,13 @@ def run_until_complete(self, future): [pollables, wakers] = list(map(list, zip(*self.wakers))) new_wakers = [] - for (ready, pollable), waker in zip(zip(poll.poll_oneoff(pollables), pollables), wakers): + ready = [False] * len(pollables) + for index in poll.poll_list(pollables): + ready[index] = True + + for (ready, pollable), waker in zip(zip(ready, pollables), wakers): if ready: + pollable.drop() waker.set_result(None) else: new_wakers.append((pollable, waker)) @@ -319,7 +360,7 @@ def default_exception_handler(self, context): def set_debug(self, enabled): raise NotImplementedError -async def register(loop: PollLoop, pollable: int): +async def register(loop: PollLoop, pollable: Pollable): waker = loop.create_future() loop.wakers.append((pollable, waker)) await waker diff --git a/examples/http/spin.toml b/examples/http/spin.toml deleted file mode 100644 index c57bc54..0000000 --- a/examples/http/spin.toml +++ /dev/null @@ -1,16 +0,0 @@ -spin_manifest_version = "1" -authors = ["Fermyon Engineering "] -description = "An example using `wasi-http`" -name = "http" -trigger = { type = "http", base = "/" } -version = "1.0.0" - -[[component]] -id = "http" -source = "http.wasm" -[component.trigger] -route = "/..." -executor = { type = "wasi" } -[component.build] -command = "componentize-py -d wit -w proxy componentize app -o http.wasm" -watch = ["app.py"] diff --git a/examples/http/wit/command-extended.wit b/examples/http/wit/command-extended.wit new file mode 100644 index 0000000..0661779 --- /dev/null +++ b/examples/http/wit/command-extended.wit @@ -0,0 +1,37 @@ +// All of the same imports and exports available in the wasi:cli/command world +// with addition of HTTP proxy related imports: +world command-extended { + import wasi:clocks/wall-clock@0.2.0-rc-2023-10-18; + import wasi:clocks/monotonic-clock@0.2.0-rc-2023-10-18; + import wasi:clocks/timezone@0.2.0-rc-2023-10-18; + import wasi:filesystem/types@0.2.0-rc-2023-10-18; + import wasi:filesystem/preopens@0.2.0-rc-2023-10-18; + import wasi:sockets/instance-network@0.2.0-rc-2023-10-18; + import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; + import wasi:sockets/network@0.2.0-rc-2023-10-18; + import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-10-18; + import wasi:sockets/tcp@0.2.0-rc-2023-10-18; + import wasi:sockets/udp-create-socket@0.2.0-rc-2023-10-18; + import wasi:sockets/udp@0.2.0-rc-2023-10-18; + import wasi:random/random@0.2.0-rc-2023-10-18; + import wasi:random/insecure@0.2.0-rc-2023-10-18; + import wasi:random/insecure-seed@0.2.0-rc-2023-10-18; + import wasi:io/poll@0.2.0-rc-2023-10-18; + import wasi:io/streams@0.2.0-rc-2023-10-18; + import wasi:cli/environment@0.2.0-rc-2023-10-18; + import wasi:cli/exit@0.2.0-rc-2023-10-18; + import wasi:cli/stdin@0.2.0-rc-2023-10-18; + import wasi:cli/stdout@0.2.0-rc-2023-10-18; + import wasi:cli/stderr@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-input@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-output@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-stdin@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-stdout@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-stderr@0.2.0-rc-2023-10-18; + + // We should replace all others with `include self.command` + // as soon as the unioning of worlds is available: + // https://github.com/WebAssembly/component-model/issues/169 + import wasi:logging/logging@0.2.0-rc-2023-10-18; + import wasi:http/outgoing-handler@0.2.0-rc-2023-10-18; +} diff --git a/examples/http/wit/deps.lock b/examples/http/wit/deps.lock deleted file mode 100644 index 5cf8421..0000000 --- a/examples/http/wit/deps.lock +++ /dev/null @@ -1,34 +0,0 @@ -[cli] -url = "https://github.com/WebAssembly/wasi-cli/archive/main.tar.gz" -sha256 = "f21b7722b9225b7ff4f040a67daacd4e412fe2c53c982568a2c25b97fc85d2a2" -sha512 = "ceec4b906f94ba53731dd9428e7e62b964de2284d6aa7cb3436cb856ca89405b0ca8b02e8a8d4575204b9e72dac0e3b29690221ca438324b89e0084117f1700f" - -[clocks] -url = "https://github.com/WebAssembly/wasi-clocks/archive/main.tar.gz" -sha256 = "1ed7e35b3f9738663854f0dd92a95bfadc410ea07170501f5c2fec0cc24e3d57" -sha512 = "ef1e23704a8a8436fd3718593d4c4d8b6d1c64dad3595b7496c0888ca14b725046f2900109800faca1bc8c14f237cdcaca791dba8284e1ad50105ab2d036825b" - -[filesystem] -url = "https://github.com/WebAssembly/wasi-filesystem/archive/main.tar.gz" -sha256 = "dc170645d8aa52f2f94ab8f71093fa0c101e509ed1a07318995dc0395e9d6cf2" -sha512 = "3195a3e0f9ec52c3a91c4b4fde0547694236c7b29bceecb7f38634894fafd809c69ed1c1c9acbf225b2d5d00f5036d70371c9fed121d85028162b202035cabef" - -[io] -url = "https://github.com/WebAssembly/wasi-io/archive/main.tar.gz" -sha256 = "6e18239b0e20d1a3e6343cb961ebfd2c663ba7feb4c1aa3744b756fbdd1fb5b8" -sha512 = "53169b6e4fba0b2cf5fcf808f76e7fbb7cabb6ed66ab53f77d0966e7448312ccbe8571880ef4fc2ee86fbd6ba19bc48d46e10d22dcac6c51d217e8d7127c32db" - -[poll] -url = "https://github.com/WebAssembly/wasi-poll/archive/main.tar.gz" -sha256 = "d4c27124f4c137eb538b5c92ba5858ed9042e11b24a2eef85d14becd0b7f55de" -sha512 = "422c01b273b4b1377ece6f2e4ba0dfc609ca8ef30a3e0be0e172e1303fcf7b3ca4c470f4dea6c51bdf114b0f5c871ebc4934dfe3bf217d66ea689748df2b1e55" - -[random] -url = "https://github.com/WebAssembly/wasi-random/archive/main.tar.gz" -sha256 = "9b622463e597b9ca94f41e4eaae589a77be38f71b4723142b60246ffed8eaae4" -sha512 = "21f03ca1e595b80d7ced522de1a47446526b49b900e2fb26fcbf410ce6aa267dbf247aebf3fbfa8123b46fc1a828e2fd64fb1e0198b40161a3257e8d86fd4546" - -[sockets] -url = "https://github.com/WebAssembly/wasi-sockets/archive/main.tar.gz" -sha256 = "871c211b12d87a5da87c42353338b652260840897efcd37e2afba3b9290058fc" -sha512 = "e436a5ff3145ca85d702a086499c03488523483dd3addc8d71e4946e9c186355291551bb6d38b157173836fcc318182403e6dba970de4512f6cfb3374ccad6b9" diff --git a/examples/http/wit/deps.toml b/examples/http/wit/deps.toml deleted file mode 100644 index 11f43c1..0000000 --- a/examples/http/wit/deps.toml +++ /dev/null @@ -1,8 +0,0 @@ -io = "https://github.com/WebAssembly/wasi-io/archive/main.tar.gz" -cli = "https://github.com/WebAssembly/wasi-cli/archive/main.tar.gz" -poll = "https://github.com/WebAssembly/wasi-poll/archive/main.tar.gz" -random = "https://github.com/WebAssembly/wasi-random/archive/main.tar.gz" -clocks = "https://github.com/WebAssembly/wasi-clocks/archive/main.tar.gz" -# not used by http/proxy, but included to allow full contents of wasi-cli to validate -filesystem = "https://github.com/WebAssembly/wasi-filesystem/archive/main.tar.gz" -sockets = "https://github.com/WebAssembly/wasi-sockets/archive/main.tar.gz" diff --git a/examples/http/wit/deps/cli/command.wit b/examples/http/wit/deps/cli/command.wit new file mode 100644 index 0000000..d7ea2d9 --- /dev/null +++ b/examples/http/wit/deps/cli/command.wit @@ -0,0 +1,7 @@ +package wasi:cli@0.2.0-rc-2023-10-18; + +world command { + include reactor; + + export run; +} diff --git a/examples/http/wit/deps/cli/environment.wit b/examples/http/wit/deps/cli/environment.wit new file mode 100644 index 0000000..7006523 --- /dev/null +++ b/examples/http/wit/deps/cli/environment.wit @@ -0,0 +1,18 @@ +interface environment { + /// Get the POSIX-style environment variables. + /// + /// Each environment variable is provided as a pair of string variable names + /// and string value. + /// + /// Morally, these are a value import, but until value imports are available + /// in the component model, this import function should return the same + /// values each time it is called. + get-environment: func() -> list>; + + /// Get the POSIX-style arguments to the program. + get-arguments: func() -> list; + + /// Return a path that programs should use as their initial current working + /// directory, interpreting `.` as shorthand for this. + initial-cwd: func() -> option; +} diff --git a/examples/http/wit/deps/cli/exit.wit b/examples/http/wit/deps/cli/exit.wit new file mode 100644 index 0000000..d0c2b82 --- /dev/null +++ b/examples/http/wit/deps/cli/exit.wit @@ -0,0 +1,4 @@ +interface exit { + /// Exit the current instance and any linked instances. + exit: func(status: result); +} diff --git a/examples/http/wit/deps/cli/reactor.wit b/examples/http/wit/deps/cli/reactor.wit new file mode 100644 index 0000000..904b994 --- /dev/null +++ b/examples/http/wit/deps/cli/reactor.wit @@ -0,0 +1,32 @@ +package wasi:cli@0.2.0-rc-2023-10-18; + +world reactor { + import wasi:clocks/wall-clock@0.2.0-rc-2023-10-18; + import wasi:clocks/monotonic-clock@0.2.0-rc-2023-10-18; + import wasi:clocks/timezone@0.2.0-rc-2023-10-18; + import wasi:filesystem/types@0.2.0-rc-2023-10-18; + import wasi:filesystem/preopens@0.2.0-rc-2023-10-18; + import wasi:sockets/instance-network@0.2.0-rc-2023-10-18; + import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; + import wasi:sockets/network@0.2.0-rc-2023-10-18; + import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-10-18; + import wasi:sockets/tcp@0.2.0-rc-2023-10-18; + import wasi:sockets/udp-create-socket@0.2.0-rc-2023-10-18; + import wasi:sockets/udp@0.2.0-rc-2023-10-18; + import wasi:random/random@0.2.0-rc-2023-10-18; + import wasi:random/insecure@0.2.0-rc-2023-10-18; + import wasi:random/insecure-seed@0.2.0-rc-2023-10-18; + import wasi:io/poll@0.2.0-rc-2023-10-18; + import wasi:io/streams@0.2.0-rc-2023-10-18; + + import environment; + import exit; + import stdin; + import stdout; + import stderr; + import terminal-input; + import terminal-output; + import terminal-stdin; + import terminal-stdout; + import terminal-stderr; +} diff --git a/examples/http/wit/deps/cli/run.wit b/examples/http/wit/deps/cli/run.wit new file mode 100644 index 0000000..a70ee8c --- /dev/null +++ b/examples/http/wit/deps/cli/run.wit @@ -0,0 +1,4 @@ +interface run { + /// Run the program. + run: func() -> result; +} diff --git a/examples/http/wit/deps/cli/stdio.wit b/examples/http/wit/deps/cli/stdio.wit new file mode 100644 index 0000000..513ca92 --- /dev/null +++ b/examples/http/wit/deps/cli/stdio.wit @@ -0,0 +1,17 @@ +interface stdin { + use wasi:io/streams@0.2.0-rc-2023-10-18.{input-stream}; + + get-stdin: func() -> input-stream; +} + +interface stdout { + use wasi:io/streams@0.2.0-rc-2023-10-18.{output-stream}; + + get-stdout: func() -> output-stream; +} + +interface stderr { + use wasi:io/streams@0.2.0-rc-2023-10-18.{output-stream}; + + get-stderr: func() -> output-stream; +} diff --git a/examples/http/wit/deps/cli/terminal.wit b/examples/http/wit/deps/cli/terminal.wit new file mode 100644 index 0000000..4749576 --- /dev/null +++ b/examples/http/wit/deps/cli/terminal.wit @@ -0,0 +1,47 @@ +interface terminal-input { + /// The input side of a terminal. + resource terminal-input; + + // In the future, this may include functions for disabling echoing, + // disabling input buffering so that keyboard events are sent through + // immediately, querying supported features, and so on. +} + +interface terminal-output { + /// The output side of a terminal. + resource terminal-output; + + // In the future, this may include functions for querying the terminal + // size, being notified of terminal size changes, querying supported + // features, and so on. +} + +/// An interface providing an optional `terminal-input` for stdin as a +/// link-time authority. +interface terminal-stdin { + use terminal-input.{terminal-input}; + + /// If stdin is connected to a terminal, return a `terminal-input` handle + /// allowing further interaction with it. + get-terminal-stdin: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stdout as a +/// link-time authority. +interface terminal-stdout { + use terminal-output.{terminal-output}; + + /// If stdout is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stdout: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stderr as a +/// link-time authority. +interface terminal-stderr { + use terminal-output.{terminal-output}; + + /// If stderr is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stderr: func() -> option; +} diff --git a/examples/http/wit/deps/clocks/monotonic-clock.wit b/examples/http/wit/deps/clocks/monotonic-clock.wit new file mode 100644 index 0000000..c0ecb52 --- /dev/null +++ b/examples/http/wit/deps/clocks/monotonic-clock.wit @@ -0,0 +1,32 @@ +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +/// +/// It is intended for measuring elapsed time. +interface monotonic-clock { + use wasi:io/poll@0.2.0-rc-2023-10-18.{pollable}; + + /// A timestamp in nanoseconds. + type instant = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + now: func() -> instant; + + /// Query the resolution of the clock. + resolution: func() -> instant; + + /// Create a `pollable` which will resolve once the specified time has been + /// reached. + subscribe: func( + when: instant, + absolute: bool + ) -> pollable; +} diff --git a/examples/http/wit/deps/clocks/timezone.wit b/examples/http/wit/deps/clocks/timezone.wit new file mode 100644 index 0000000..e717e7b --- /dev/null +++ b/examples/http/wit/deps/clocks/timezone.wit @@ -0,0 +1,48 @@ +interface timezone { + use wall-clock.{datetime}; + + /// Return information needed to display the given `datetime`. This includes + /// the UTC offset, the time zone name, and a flag indicating whether + /// daylight saving time is active. + /// + /// If the timezone cannot be determined for the given `datetime`, return a + /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + /// saving time. + display: func(when: datetime) -> timezone-display; + + /// The same as `display`, but only return the UTC offset. + utc-offset: func(when: datetime) -> s32; + + /// Information useful for displaying the timezone of a specific `datetime`. + /// + /// This information may vary within a single `timezone` to reflect daylight + /// saving time adjustments. + record timezone-display { + /// The number of seconds difference between UTC time and the local + /// time of the timezone. + /// + /// The returned value will always be less than 86400 which is the + /// number of seconds in a day (24*60*60). + /// + /// In implementations that do not expose an actual time zone, this + /// should return 0. + utc-offset: s32, + + /// The abbreviated name of the timezone to display to a user. The name + /// `UTC` indicates Coordinated Universal Time. Otherwise, this should + /// reference local standards for the name of the time zone. + /// + /// In implementations that do not expose an actual time zone, this + /// should be the string `UTC`. + /// + /// In time zones that do not have an applicable name, a formatted + /// representation of the UTC offset may be returned, such as `-04:00`. + name: string, + + /// Whether daylight saving time is active. + /// + /// In implementations that do not expose an actual time zone, this + /// should return false. + in-daylight-saving-time: bool, + } +} diff --git a/examples/http/wit/deps/clocks/wall-clock.wit b/examples/http/wit/deps/clocks/wall-clock.wit new file mode 100644 index 0000000..c395649 --- /dev/null +++ b/examples/http/wit/deps/clocks/wall-clock.wit @@ -0,0 +1,41 @@ +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + resolution: func() -> datetime; +} diff --git a/examples/http/wit/deps/clocks/world.wit b/examples/http/wit/deps/clocks/world.wit new file mode 100644 index 0000000..cdfb51d --- /dev/null +++ b/examples/http/wit/deps/clocks/world.wit @@ -0,0 +1,7 @@ +package wasi:clocks@0.2.0-rc-2023-10-18; + +world imports { + import monotonic-clock; + import wall-clock; + import timezone; +} diff --git a/examples/http/wit/deps/filesystem/preopens.wit b/examples/http/wit/deps/filesystem/preopens.wit new file mode 100644 index 0000000..3f787ac --- /dev/null +++ b/examples/http/wit/deps/filesystem/preopens.wit @@ -0,0 +1,6 @@ +interface preopens { + use types.{descriptor}; + + /// Return the set of preopened directories, and their path. + get-directories: func() -> list>; +} diff --git a/examples/http/wit/deps/filesystem/types.wit b/examples/http/wit/deps/filesystem/types.wit new file mode 100644 index 0000000..af36135 --- /dev/null +++ b/examples/http/wit/deps/filesystem/types.wit @@ -0,0 +1,810 @@ +/// WASI filesystem is a filesystem API primarily intended to let users run WASI +/// programs that access their files on their existing filesystems, without +/// significant overhead. +/// +/// It is intended to be roughly portable between Unix-family platforms and +/// Windows, though it does not hide many of the major differences. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` and symbolic link steps, reaches a directory outside of the base +/// directory, or reaches a symlink to an absolute or rooted path in the +/// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +interface types { + use wasi:io/streams@0.2.0-rc-2023-10-18.{input-stream, output-stream, error}; + use wasi:clocks/wall-clock@0.2.0-rc-2023-10-18.{datetime}; + + /// File size or length of a region within a file. + type filesize = u64; + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + enum descriptor-type { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrety + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + record descriptor-stat { + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, + } + + /// Flags determining the method of how paths are resolved. + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Permissions mode used by `open-at`, `change-file-permissions-at`, and + /// similar. + flags modes { + /// True if the resource is considered readable by the containing + /// filesystem. + readable, + /// True if the resource is considered writable by the containing + /// filesystem. + writable, + /// True if the resource is considered executable by the containing + /// filesystem. This does not apply to directories. + executable, + } + + /// Access type used by `access-at`. + variant access-type { + /// Test for readability, writeability, or executability. + access(modes), + + /// Test whether the path exists. + exists, + } + + /// Number of hard links to an inode. + type link-count = u64; + + /// When setting a timestamp, this gives the value to set it to. + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(datetime), + } + + /// A directory entry. + record directory-entry { + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + enum error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. + would-block, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + } + + /// File or memory access pattern advisory information. + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + record metadata-hash-value { + /// 64 bits of a 128-bit hash value. + lower: u64, + /// Another 64 bits of a 128-bit hash value. + upper: u64, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + resource descriptor { + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + read-via-stream: func( + /// The offset within the file at which to start reading. + offset: filesize, + ) -> result; + + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + write-via-stream: func( + /// The offset within the file at which to start writing. + offset: filesize, + ) -> result; + + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in in POSIX. + append-via-stream: func() -> result; + + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + advise: func( + /// The offset within the file to which the advisory applies. + offset: filesize, + /// The length of the region to which the advisory applies. + length: filesize, + /// The advice. + advice: advice + ) -> result<_, error-code>; + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + sync-data: func() -> result<_, error-code>; + + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-flags: func() -> result; + + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-type: func() -> result; + + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + set-size: func(size: filesize) -> result<_, error-code>; + + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + set-times: func( + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + read: func( + /// The maximum number of bytes to read. + length: filesize, + /// The offset within the file at which to read. + offset: filesize, + ) -> result, bool>, error-code>; + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + write: func( + /// Data to write + buffer: list, + /// The offset within the file at which to write. + offset: filesize, + ) -> result; + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + read-directory: func() -> result; + + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + sync: func() -> result<_, error-code>; + + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + create-directory-at: func( + /// The relative path at which to create the directory. + path: string, + ) -> result<_, error-code>; + + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + stat: func() -> result; + + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + stat-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + set-times-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + link-at: func( + /// Flags determining the method of how the path is resolved. + old-path-flags: path-flags, + /// The relative source path from which to link. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path at which to create the hard link. + new-path: string, + ) -> result<_, error-code>; + + /// Open a file or directory. + /// + /// The returned descriptor is not guaranteed to be the lowest-numbered + /// descriptor not currently open/ it is randomized to prevent applications + /// from depending on making assumptions about indexes, since this is + /// error-prone in multi-threaded contexts. The returned descriptor is + /// guaranteed to be less than 2**31. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + open-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + open-flags: open-flags, + /// Flags to use for the resulting descriptor. + %flags: descriptor-flags, + /// Permissions to use when creating a new file. + modes: modes + ) -> result; + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + readlink-at: func( + /// The relative path of the symbolic link from which to read. + path: string, + ) -> result; + + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + remove-directory-at: func( + /// The relative path to a directory to remove. + path: string, + ) -> result<_, error-code>; + + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + rename-at: func( + /// The relative source path of the file or directory to rename. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path to which to rename the file or directory. + new-path: string, + ) -> result<_, error-code>; + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + symlink-at: func( + /// The contents of the symbolic link. + old-path: string, + /// The relative destination path at which to create the symbolic link. + new-path: string, + ) -> result<_, error-code>; + + /// Check accessibility of a filesystem path. + /// + /// Check whether the given filesystem path names an object which is + /// readable, writable, or executable, or whether it exists. + /// + /// This does not a guarantee that subsequent accesses will succeed, as + /// filesystem permissions may be modified asynchronously by external + /// entities. + /// + /// Note: This is similar to `faccessat` with the `AT_EACCESS` flag in POSIX. + access-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to check. + path: string, + /// The type of check to perform. + %type: access-type + ) -> result<_, error-code>; + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + unlink-file-at: func( + /// The relative path to a file to unlink. + path: string, + ) -> result<_, error-code>; + + /// Change the permissions of a filesystem object that is not a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-file-permissions-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the filesystem object. + modes: modes, + ) -> result<_, error-code>; + + /// Change the permissions of a directory. + /// + /// Note that the ultimate meanings of these permissions is + /// filesystem-specific. + /// + /// Unlike in POSIX, the `executable` flag is not reinterpreted as a "search" + /// flag. `read` on a directory implies readability and searchability, and + /// `execute` is not valid for directories. + /// + /// Note: This is similar to `fchmodat` in POSIX. + change-directory-permissions-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path to operate on. + path: string, + /// The new permissions for the directory. + modes: modes, + ) -> result<_, error-code>; + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH)` in Unix. + lock-shared: func() -> result<_, error-code>; + + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function blocks until the lock can be acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX)` in Unix. + lock-exclusive: func() -> result<_, error-code>; + + /// Request a shared advisory lock for an open file. + /// + /// This requests a *shared* lock; more than one shared lock can be held for + /// a file at the same time. + /// + /// If the open file has an exclusive lock, this function downgrades the lock + /// to a shared lock. If it has a shared lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified how shared locks interact with locks acquired by + /// non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_SH | LOCK_NB)` in Unix. + try-lock-shared: func() -> result<_, error-code>; + + /// Request an exclusive advisory lock for an open file. + /// + /// This requests an *exclusive* lock; no other locks may be held for the + /// file while an exclusive lock is held. + /// + /// If the open file has a shared lock and there are no exclusive locks held + /// for the file, this function upgrades the lock to an exclusive lock. If the + /// open file already has an exclusive lock, this function has no effect. + /// + /// This requests an *advisory* lock, meaning that the file could be accessed + /// by other programs that don't hold the lock. + /// + /// It is unspecified whether this function succeeds if the file descriptor + /// is not opened for writing. It is unspecified how exclusive locks interact + /// with locks acquired by non-WASI programs. + /// + /// This function returns `error-code::would-block` if the lock cannot be + /// acquired. + /// + /// Not all filesystems support locking; on filesystems which don't support + /// locking, this function returns `error-code::unsupported`. + /// + /// Note: This is similar to `flock(fd, LOCK_EX | LOCK_NB)` in Unix. + try-lock-exclusive: func() -> result<_, error-code>; + + /// Release a shared or exclusive lock on an open file. + /// + /// Note: This is similar to `flock(fd, LOCK_UN)` in Unix. + unlock: func() -> result<_, error-code>; + + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + is-same-object: func(other: borrow) -> bool; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encourated to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + metadata-hash: func() -> result; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + metadata-hash-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + } + + /// A stream of directory entries. + resource directory-entry-stream { + /// Read a single directory entry from a `directory-entry-stream`. + read-directory-entry: func() -> result, error-code>; + } + + /// Attempts to extract a filesystem-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// filesystem-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are filesystem-related errors. + filesystem-error-code: func(err: borrow) -> option; +} diff --git a/examples/http/wit/deps/filesystem/world.wit b/examples/http/wit/deps/filesystem/world.wit new file mode 100644 index 0000000..3f953f8 --- /dev/null +++ b/examples/http/wit/deps/filesystem/world.wit @@ -0,0 +1,6 @@ +package wasi:filesystem@0.2.0-rc-2023-10-18; + +world imports { + import types; + import preopens; +} diff --git a/examples/http/wit/deps/http/incoming-handler.wit b/examples/http/wit/deps/http/incoming-handler.wit new file mode 100644 index 0000000..6968d63 --- /dev/null +++ b/examples/http/wit/deps/http/incoming-handler.wit @@ -0,0 +1,24 @@ +// The `wasi:http/incoming-handler` interface is meant to be exported by +// components and called by the host in response to a new incoming HTTP +// response. +// +// NOTE: in Preview3, this interface will be merged with +// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface +// that takes a `request` parameter and returns a `response` result. +// +interface incoming-handler { + use types.{incoming-request, response-outparam}; + + // The `handle` function takes an outparam instead of returning its response + // so that the component may stream its response while streaming any other + // request or response bodies. The callee MUST write a response to the + // `response-outparam` and then finish the response before returning. The `handle` + // function is allowed to continue execution after finishing the response's + // output stream. While this post-response execution is taken off the + // critical path, since there is no return value, there is no way to report + // its success or failure. + handle: func( + request: incoming-request, + response-out: response-outparam + ); +} diff --git a/examples/http/wit/deps/http/outgoing-handler.wit b/examples/http/wit/deps/http/outgoing-handler.wit new file mode 100644 index 0000000..286e283 --- /dev/null +++ b/examples/http/wit/deps/http/outgoing-handler.wit @@ -0,0 +1,20 @@ +// The `wasi:http/outgoing-handler` interface is meant to be imported by +// components and implemented by the host. +// +// NOTE: in Preview3, this interface will be merged with +// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface +// that takes a `request` parameter and returns a `response` result. +// +interface outgoing-handler { + use types.{outgoing-request, request-options, future-incoming-response, error}; + + // The parameter and result types of the `handle` function allow the caller + // to concurrently stream the bodies of the outgoing request and the incoming + // response. + // Consumes the outgoing-request. Gives an error if the outgoing-request + // is invalid or cannot be satisfied by this handler. + handle: func( + request: outgoing-request, + options: option + ) -> result; +} diff --git a/examples/http/wit/deps/http/proxy.wit b/examples/http/wit/deps/http/proxy.wit new file mode 100644 index 0000000..dde0659 --- /dev/null +++ b/examples/http/wit/deps/http/proxy.wit @@ -0,0 +1,34 @@ +package wasi:http@0.2.0-rc-2023-10-18; + +// The `wasi:http/proxy` world captures a widely-implementable intersection of +// hosts that includes HTTP forward and reverse proxies. Components targeting +// this world may concurrently stream in and out any number of incoming and +// outgoing HTTP requests. +world proxy { + // HTTP proxies have access to time and randomness. + import wasi:clocks/wall-clock@0.2.0-rc-2023-10-18; + import wasi:clocks/monotonic-clock@0.2.0-rc-2023-10-18; + import wasi:clocks/timezone@0.2.0-rc-2023-10-18; + import wasi:random/random@0.2.0-rc-2023-10-18; + + // Proxies have standard output and error streams which are expected to + // terminate in a developer-facing console provided by the host. + import wasi:cli/stdout@0.2.0-rc-2023-10-18; + import wasi:cli/stderr@0.2.0-rc-2023-10-18; + + // TODO: this is a temporary workaround until component tooling is able to + // gracefully handle the absence of stdin. Hosts must return an eof stream + // for this import, which is what wasi-libc + tooling will do automatically + // when this import is properly removed. + import wasi:cli/stdin@0.2.0-rc-2023-10-18; + + // This is the default handler to use when user code simply wants to make an + // HTTP request (e.g., via `fetch()`). + import outgoing-handler; + + // The host delivers incoming HTTP requests to a component by calling the + // `handle` function of this exported interface. A host may arbitrarily reuse + // or not reuse component instance when delivering incoming HTTP requests and + // thus a component must be able to handle 0..N calls to `handle`. + export incoming-handler; +} diff --git a/examples/http/wit/deps/http/types.wit b/examples/http/wit/deps/http/types.wit new file mode 100644 index 0000000..2cd2fe2 --- /dev/null +++ b/examples/http/wit/deps/http/types.wit @@ -0,0 +1,214 @@ +// The `wasi:http/types` interface is meant to be imported by components to +// define the HTTP resource types and operations used by the component's +// imported and exported interfaces. +interface types { + use wasi:io/streams@0.2.0-rc-2023-10-18.{input-stream, output-stream}; + use wasi:io/poll@0.2.0-rc-2023-10-18.{pollable}; + + // This type corresponds to HTTP standard Methods. + variant method { + get, + head, + post, + put, + delete, + connect, + options, + trace, + patch, + other(string) + } + + // This type corresponds to HTTP standard Related Schemes. + variant scheme { + HTTP, + HTTPS, + other(string) + } + + // TODO: perhaps better align with HTTP semantics? + // This type enumerates the different kinds of errors that may occur when + // initially returning a response. + variant error { + invalid-url(string), + timeout-error(string), + protocol-error(string), + unexpected-error(string) + } + + // This following block defines the `fields` resource which corresponds to + // HTTP standard Fields. Soon, when resource types are added, the `type + // fields = u32` type alias can be replaced by a proper `resource fields` + // definition containing all the functions using the method syntactic sugar. + resource fields { + // Multiple values for a header are multiple entries in the list with the + // same key. + constructor(entries: list>>); + + // Values off wire are not necessarily well formed, so they are given by + // list instead of string. + get: func(name: string) -> list>; + + // Values off wire are not necessarily well formed, so they are given by + // list instead of string. + set: func(name: string, value: list>); + delete: func(name: string); + append: func(name: string, value: list); + + // Values off wire are not necessarily well formed, so they are given by + // list instead of string. + entries: func() -> list>>; + + // Deep copy of all contents in a fields. + clone: func() -> fields; + } + + type headers = fields; + type trailers = fields; + + // The following block defines the `incoming-request` and `outgoing-request` + // resource types that correspond to HTTP standard Requests. Soon, when + // resource types are added, the `u32` type aliases can be replaced by + // proper `resource` type definitions containing all the functions as + // methods. Later, Preview2 will allow both types to be merged together into + // a single `request` type (that uses the single `stream` type mentioned + // above). The `consume` and `write` methods may only be called once (and + // return failure thereafter). + resource incoming-request { + method: func() -> method; + + path-with-query: func() -> option; + + scheme: func() -> option; + + authority: func() -> option; + + headers: func() -> /* child */ headers; + // Will return the input-stream child at most once. If called more than + // once, subsequent calls will return error. + + consume: func() -> result; + } + + resource outgoing-request { + constructor( + method: method, + path-with-query: option, + scheme: option, + authority: option, + headers: borrow + ); + + // Will return the outgoing-body child at most once. If called more than + // once, subsequent calls will return error. + write: func() -> result< /* child */ outgoing-body>; + } + + // Additional optional parameters that can be set when making a request. + record request-options { + // The following timeouts are specific to the HTTP protocol and work + // independently of the overall timeouts passed to `io.poll.poll-list`. + + // The timeout for the initial connect. + connect-timeout-ms: option, + + // The timeout for receiving the first byte of the response body. + first-byte-timeout-ms: option, + + // The timeout for receiving the next chunk of bytes in the response body + // stream. + between-bytes-timeout-ms: option + } + + // The following block defines a special resource type used by the + // `wasi:http/incoming-handler` interface. When resource types are added, this + // block can be replaced by a proper `resource response-outparam { ... }` + // definition. Later, with Preview3, the need for an outparam goes away entirely + // (the `wasi:http/handler` interface used for both incoming and outgoing can + // simply return a `stream`). + resource response-outparam { + set: static func(param: response-outparam, response: result); + } + + // This type corresponds to the HTTP standard Status Code. + type status-code = u16; + + // The following block defines the `incoming-response` and `outgoing-response` + // resource types that correspond to HTTP standard Responses. Soon, when + // resource types are added, the `u32` type aliases can be replaced by proper + // `resource` type definitions containing all the functions as methods. Later, + // Preview2 will allow both types to be merged together into a single `response` + // type (that uses the single `stream` type mentioned above). The `consume` and + // `write` methods may only be called once (and return failure thereafter). + resource incoming-response { + status: func() -> status-code; + + headers: func() -> /* child */ headers; + + // May be called at most once. returns error if called additional times. + // TODO: make incoming-request-consume work the same way, giving a child + // incoming-body. + consume: func() -> result; + } + + resource incoming-body { + // returned input-stream is a child - the implementation may trap if + // incoming-body is dropped (or consumed by call to + // incoming-body-finish) before the input-stream is dropped. + // May be called at most once. returns error if called additional times. + %stream: func() -> result; + + // takes ownership of incoming-body. this will trap if the + // incoming-body-stream child is still alive! + finish: static func(this: incoming-body) -> + /* transitive child of the incoming-response of incoming-body */ future-trailers; + } + + resource future-trailers { + /// Pollable that resolves when the body has been fully read, and the trailers + /// are ready to be consumed. + subscribe: func() -> /* child */ pollable; + + /// Retrieve reference to trailers, if they are ready. + get: func() -> option>; + } + + resource outgoing-response { + constructor(status-code: status-code, headers: borrow); + + /// Will give the child outgoing-response at most once. subsequent calls will + /// return an error. + write: func() -> result; + } + + resource outgoing-body { + /// Will give the child output-stream at most once. subsequent calls will + /// return an error. + write: func() -> result; + + /// Finalize an outgoing body, optionally providing trailers. This must be + /// called to signal that the response is complete. If the `outgoing-body` is + /// dropped without calling `outgoing-body-finalize`, the implementation + /// should treat the body as corrupted. + finish: static func(this: outgoing-body, trailers: option); + } + + /// The following block defines a special resource type used by the + /// `wasi:http/outgoing-handler` interface to emulate + /// `future>` in advance of Preview3. Given a + /// `future-incoming-response`, the client can call the non-blocking `get` + /// method to get the result if it is available. If the result is not available, + /// the client can call `listen` to get a `pollable` that can be passed to + /// `wasi:io/poll.poll-list`. + resource future-incoming-response { + /// option indicates readiness. + /// outer result indicates you are allowed to get the + /// incoming-response-or-error at most once. subsequent calls after ready + /// will return an error here. + /// inner result indicates whether the incoming-response was available, or an + /// error occured. + get: func() -> option>>; + + subscribe: func() -> /* child */ pollable; + } +} diff --git a/examples/http/wit/deps/io/poll.wit b/examples/http/wit/deps/io/poll.wit new file mode 100644 index 0000000..047389d --- /dev/null +++ b/examples/http/wit/deps/io/poll.wit @@ -0,0 +1,34 @@ +package wasi:io@0.2.0-rc-2023-10-18; + +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +interface poll { + /// A "pollable" handle. + resource pollable; + + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// If the list contains more elements than can be indexed with a `u32` + /// value, this function traps. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being reaedy for I/O. + poll-list: func(in: list>) -> list; + + /// Poll for completion on a single pollable. + /// + /// This function is similar to `poll-list`, but operates on only a single + /// pollable. When it returns, the handle is ready for I/O. + poll-one: func(in: borrow); +} diff --git a/examples/http/wit/deps/io/streams.wit b/examples/http/wit/deps/io/streams.wit new file mode 100644 index 0000000..d0e8f5c --- /dev/null +++ b/examples/http/wit/deps/io/streams.wit @@ -0,0 +1,289 @@ +package wasi:io@0.2.0-rc-2023-10-18; + +/// WASI I/O is an I/O abstraction API which is currently focused on providing +/// stream types. +/// +/// In the future, the component model is expected to add built-in stream types; +/// when it does, they are expected to subsume this API. +interface streams { + use poll.{pollable}; + + /// An error for input-stream and output-stream operations. + variant stream-error { + /// The last operation (a write or flush) failed before completion. + /// + /// More information is available in the `error` payload. + last-operation-failed(error), + /// The stream is closed: no more input will be accepted by the + /// stream. A closed output-stream will return this error on all + /// future operations. + closed + } + + /// Contextual error information about the last failure that happened on + /// a read, write, or flush from an `input-stream` or `output-stream`. + /// + /// This type is returned through the `stream-error` type whenever an + /// operation on a stream directly fails or an error is discovered + /// after-the-fact, for example when a write's failure shows up through a + /// later `flush` or `check-write`. + /// + /// Interfaces such as `wasi:filesystem/types` provide functionality to + /// further "downcast" this error into interface-specific error information. + resource error { + /// Returns a string that's suitable to assist humans in debugging this + /// error. + /// + /// The returned string will change across platforms and hosts which + /// means that parsing it, for example, would be a + /// platform-compatibility hazard. + to-debug-string: func() -> string; + } + + /// An input bytestream. + /// + /// `input-stream`s are *non-blocking* to the extent practical on underlying + /// platforms. I/O operations always return promptly; if fewer bytes are + /// promptly available than requested, they return the number of bytes promptly + /// available, which could even be zero. To wait for data to be available, + /// use the `subscribe` function to obtain a `pollable` which can be polled + /// for using `wasi:io/poll`. + resource input-stream { + /// Perform a non-blocking read from the stream. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a `stream-status` which, indicates whether further + /// reads are expected to produce data. The returned list will contain up to + /// `len` bytes; it may return fewer than requested, but not more. An + /// empty list and `stream-status:open` indicates no more data is + /// available at this time, and that the pollable given by `subscribe` + /// will be ready when more data is available. + /// + /// Once a stream has reached the end, subsequent calls to `read` or + /// `skip` will always report `stream-status:ended` rather than producing more + /// data. + /// + /// When the caller gives a `len` of 0, it represents a request to read 0 + /// bytes. This read should always succeed and return an empty list and + /// the current `stream-status`. + /// + /// The `len` parameter is a `u64`, which could represent a list of u8 which + /// is not possible to allocate in wasm32, or not desirable to allocate as + /// as a return value by the callee. The callee may return a list of bytes + /// less than `len` in size while more bytes are available for reading. + read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Read bytes from a stream, after blocking until at least one byte can + /// be read. Except for blocking, identical to `read`. + blocking-read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Skip bytes from a stream. + /// + /// This is similar to the `read` function, but avoids copying the + /// bytes into the instance. + /// + /// Once a stream has reached the end, subsequent calls to read or + /// `skip` will always report end-of-stream rather than producing more + /// data. + /// + /// This function returns the number of bytes skipped, along with a + /// `stream-status` indicating whether the end of the stream was + /// reached. The returned value will be at most `len`; it may be less. + skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Skip bytes from a stream, after blocking until at least one byte + /// can be skipped. Except for blocking behavior, identical to `skip`. + blocking-skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + /// The created `pollable` is a child resource of the `input-stream`. + /// Implementations may trap if the `input-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + } + + + /// An output bytestream. + /// + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe` function to obtain a `pollable` which can be + /// polled for using `wasi:io/poll`. + resource output-stream { + /// Check readiness for writing. This function never blocks. + /// + /// Returns the number of bytes permitted for the next call to `write`, + /// or an error. Calling `write` with more bytes than this function has + /// permitted will trap. + /// + /// When this function returns 0 bytes, the `subscribe` pollable will + /// become ready when this function will report at least 1 byte, or an + /// error. + check-write: func() -> result; + + /// Perform a write. This function never blocks. + /// + /// Precondition: check-write gave permit of Ok(n) and contents has a + /// length of less than or equal to n. Otherwise, this function will trap. + /// + /// returns Err(closed) without writing if the stream has closed since + /// the last call to check-write provided a permit. + write: func( + contents: list + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 bytes, and then flush the stream. Block + /// until all of these operations are complete, or an error occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write`, and `flush`, and is implemented with the + /// following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while !contents.is_empty() { + /// // Wait for the stream to become writable + /// poll-one(pollable); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, contents.len()); + /// let (chunk, rest) = contents.split_at(len); + /// this.write(chunk ); // eliding error handling + /// contents = rest; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// poll-one(pollable); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-and-flush: func( + contents: list + ) -> result<_, stream-error>; + + /// Request to flush buffered output. This function never blocks. + /// + /// This tells the output-stream that the caller intends any buffered + /// output to be flushed. the output which is expected to be flushed + /// is all that has been passed to `write` prior to this call. + /// + /// Upon calling this function, the `output-stream` will not accept any + /// writes (`check-write` will return `ok(0)`) until the flush has + /// completed. The `subscribe` pollable will become ready when the + /// flush has completed and the stream can accept more writes. + flush: func() -> result<_, stream-error>; + + /// Request to flush buffered output, and block until flush completes + /// and stream is ready for writing again. + blocking-flush: func() -> result<_, stream-error>; + + /// Create a `pollable` which will resolve once the output-stream + /// is ready for more writing, or an error has occured. When this + /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an + /// error. + /// + /// If the stream is closed, this pollable is always ready immediately. + /// + /// The created `pollable` is a child resource of the `output-stream`. + /// Implementations may trap if the `output-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + + /// Write zeroes to a stream. + /// + /// this should be used precisely like `write` with the exact same + /// preconditions (must use check-write first), but instead of + /// passing a list of bytes, you simply pass the number of zero-bytes + /// that should be written. + write-zeroes: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 zeroes, and then flush the stream. + /// Block until all of these operations are complete, or an error + /// occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with + /// the following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while num_zeroes != 0 { + /// // Wait for the stream to become writable + /// poll-one(pollable); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, num_zeroes); + /// this.write-zeroes(len); // eliding error handling + /// num_zeroes -= len; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// poll-one(pollable); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-zeroes-and-flush: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Read from one stream and write to another. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + /// + /// Unlike other I/O functions, this function blocks until all the data + /// read from the input stream has been written to the output stream. + splice: func( + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result; + + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until at least + /// one byte can be read. + blocking-splice: func( + /// The stream to read from + src: input-stream, + /// The number of bytes to splice + len: u64, + ) -> result; + + /// Forward the entire contents of an input stream to an output stream. + /// + /// This function repeatedly reads from the input stream and writes + /// the data to the output stream, until the end of the input stream + /// is reached, or an error is encountered. + /// + /// Unlike other I/O functions, this function blocks until the end + /// of the input stream is seen and all the data has been written to + /// the output stream. + /// + /// This function returns the number of bytes transferred, and the status of + /// the output stream. + forward: func( + /// The stream to read from + src: input-stream + ) -> result; + } +} diff --git a/examples/http/wit/deps/io/streams2.wit b/examples/http/wit/deps/io/streams2.wit deleted file mode 100644 index 9a7cf6a..0000000 --- a/examples/http/wit/deps/io/streams2.wit +++ /dev/null @@ -1,228 +0,0 @@ -/// WASI I/O is an I/O abstraction API which is currently focused on providing -/// stream types. -/// -/// In the future, the component model is expected to add built-in stream types; -/// when it does, they are expected to subsume this API. -interface streams2 { - use wasi:poll/poll2.{pollable} - - /// An error type returned from a stream operation. Currently this - /// doesn't provide any additional information. - enum stream-error { - error, - } - - /// Streams provide a sequence of data and then end; once they end, they - /// no longer provide any further data. - /// - /// For example, a stream reading from a file ends when the stream reaches - /// the end of the file. For another example, a stream reading from a - /// socket ends when the socket is closed. - enum stream-status { - /// The stream is open and may produce further data. - open, - /// The stream has ended and will not produce any further data. - ended, - } - - /// An input bytestream. In the future, this will be replaced by handle - /// types. - /// - /// This conceptually represents a `stream`. It's temporary - /// scaffolding until component-model's async features are ready. - /// - /// `input-stream`s are *non-blocking* to the extent practical on underlying - /// platforms. I/O operations always return promptly; if fewer bytes are - /// promptly available than requested, they return the number of bytes promptly - /// available, which could even be zero. To wait for data to be available, - /// use the `subscribe-to-input-stream` function to obtain a `pollable` which - /// can be polled for using `wasi_poll`. - /// - /// And at present, it is a `u32` instead of being an actual handle, until - /// the wit-bindgen implementation of handles and resources is ready. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type input-stream = u32 - - /// Read bytes from a stream. - /// - /// This function returns a list of bytes containing the data that was - /// read, along with a `stream-status` which indicates whether the end of - /// the stream was reached. The returned list will contain up to `len` - /// bytes; it may return fewer than requested, but not more. - /// - /// Once a stream has reached the end, subsequent calls to read or - /// `skip` will always report end-of-stream rather than producing more - /// data. - /// - /// If `len` is 0, it represents a request to read 0 bytes, which should - /// always succeed, assuming the stream hasn't reached its end yet, and - /// return an empty list. - /// - /// The len here is a `u64`, but some callees may not be able to allocate - /// a buffer as large as that would imply. - /// FIXME: describe what happens if allocation fails. - read: func( - this: input-stream, - /// The maximum number of bytes to read - len: u64 - ) -> result, stream-status>, stream-error> - - /// Read bytes from a stream, with blocking. - /// - /// This is similar to `read`, except that it blocks until at least one - /// byte can be read. - blocking-read: func( - this: input-stream, - /// The maximum number of bytes to read - len: u64 - ) -> result, stream-status>, stream-error> - - /// Skip bytes from a stream. - /// - /// This is similar to the `read` function, but avoids copying the - /// bytes into the instance. - /// - /// Once a stream has reached the end, subsequent calls to read or - /// `skip` will always report end-of-stream rather than producing more - /// data. - /// - /// This function returns the number of bytes skipped, along with a - /// `stream-status` indicating whether the end of the stream was - /// reached. The returned value will be at most `len`; it may be less. - skip: func( - this: input-stream, - /// The maximum number of bytes to skip. - len: u64, - ) -> result, stream-error> - - /// Skip bytes from a stream, with blocking. - /// - /// This is similar to `skip`, except that it blocks until at least one - /// byte can be consumed. - blocking-skip: func( - this: input-stream, - /// The maximum number of bytes to skip. - len: u64, - ) -> result, stream-error> - - /// Create a `pollable` which will resolve once either the specified stream - /// has bytes available to read or the other end of the stream has been - /// closed. - subscribe-to-input-stream: func(this: input-stream) -> pollable - - /// Dispose of the specified `input-stream`, after which it may no longer - /// be used. - drop-input-stream: func(this: input-stream) - - /// An output bytestream. In the future, this will be replaced by handle - /// types. - /// - /// This conceptually represents a `stream`. It's temporary - /// scaffolding until component-model's async features are ready. - /// - /// `output-stream`s are *non-blocking* to the extent practical on - /// underlying platforms. Except where specified otherwise, I/O operations also - /// always return promptly, after the number of bytes that can be written - /// promptly, which could even be zero. To wait for the stream to be ready to - /// accept data, the `subscribe-to-output-stream` function to obtain a - /// `pollable` which can be polled for using `wasi_poll`. - /// - /// And at present, it is a `u32` instead of being an actual handle, until - /// the wit-bindgen implementation of handles and resources is ready. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type output-stream = u32 - - /// Write bytes to a stream. - /// - /// This function returns a `u64` indicating the number of bytes from - /// `buf` that were written; it may be less than the full list. - write: func( - this: output-stream, - /// Data to write - buf: list - ) -> result - - /// Write bytes to a stream, with blocking. - /// - /// This is similar to `write`, except that it blocks until at least one - /// byte can be written. - blocking-write: func( - this: output-stream, - /// Data to write - buf: list - ) -> result - - /// Write multiple zero bytes to a stream. - /// - /// This function returns a `u64` indicating the number of zero bytes - /// that were written; it may be less than `len`. - write-zeroes: func( - this: output-stream, - /// The number of zero bytes to write - len: u64 - ) -> result - - /// Write multiple zero bytes to a stream, with blocking. - /// - /// This is similar to `write-zeroes`, except that it blocks until at least - /// one byte can be written. - blocking-write-zeroes: func( - this: output-stream, - /// The number of zero bytes to write - len: u64 - ) -> result - - /// Read from one stream and write to another. - /// - /// This function returns the number of bytes transferred; it may be less - /// than `len`. - /// - /// Unlike other I/O functions, this function blocks until all the data - /// read from the input stream has been written to the output stream. - splice: func( - this: output-stream, - /// The stream to read from - src: input-stream, - /// The number of bytes to splice - len: u64, - ) -> result, stream-error> - - /// Read from one stream and write to another, with blocking. - /// - /// This is similar to `splice`, except that it blocks until at least - /// one byte can be read. - blocking-splice: func( - this: output-stream, - /// The stream to read from - src: input-stream, - /// The number of bytes to splice - len: u64, - ) -> result, stream-error> - - /// Forward the entire contents of an input stream to an output stream. - /// - /// This function repeatedly reads from the input stream and writes - /// the data to the output stream, until the end of the input stream - /// is reached, or an error is encountered. - /// - /// Unlike other I/O functions, this function blocks until the end - /// of the input stream is seen and all the data has been written to - /// the output stream. - /// - /// This function returns the number of bytes transferred. - forward: func( - this: output-stream, - /// The stream to read from - src: input-stream - ) -> result - - /// Create a `pollable` which will resolve once either the specified stream - /// is ready to accept bytes or the other end of the stream has been closed. - subscribe-to-output-stream: func(this: output-stream) -> pollable - - /// Dispose of the specified `output-stream`, after which it may no longer - /// be used. - drop-output-stream: func(this: output-stream) -} diff --git a/examples/http/wit/deps/io/world.wit b/examples/http/wit/deps/io/world.wit index cb3d9dd..3627c9d 100644 --- a/examples/http/wit/deps/io/world.wit +++ b/examples/http/wit/deps/io/world.wit @@ -1,5 +1,6 @@ -package wasi:io +package wasi:io@0.2.0-rc-2023-10-18; -world example-world { - import streams2 +world imports { + import streams; + import poll; } diff --git a/examples/http/wit/deps/logging/logging.wit b/examples/http/wit/deps/logging/logging.wit new file mode 100644 index 0000000..b897a5a --- /dev/null +++ b/examples/http/wit/deps/logging/logging.wit @@ -0,0 +1,37 @@ +package wasi:logging@0.2.0-rc-2023-10-18; + +/// WASI Logging is a logging API intended to let users emit log messages with +/// simple priority levels and context values. +interface logging { + /// A log level, describing a kind of message. + enum level { + /// Describes messages about the values of variables and the flow of + /// control within a program. + trace, + + /// Describes messages likely to be of interest to someone debugging a + /// program. + debug, + + /// Describes messages likely to be of interest to someone monitoring a + /// program. + info, + + /// Describes messages indicating hazardous situations. + warn, + + /// Describes messages indicating serious errors. + error, + + /// Describes messages indicating fatal errors. + critical, + } + + /// Emit a log message. + /// + /// A log message has a `level` describing what kind of message is being + /// sent, a context, which is an uninterpreted string meant to help + /// consumers group similar messages, and a string containing the message + /// text. + log: func(level: level, context: string, message: string); +} diff --git a/examples/http/wit/deps/logging/world.wit b/examples/http/wit/deps/logging/world.wit new file mode 100644 index 0000000..a0fb255 --- /dev/null +++ b/examples/http/wit/deps/logging/world.wit @@ -0,0 +1,5 @@ +package wasi:logging@0.2.0-rc-2023-10-18; + +world imports { + import logging; +} diff --git a/examples/http/wit/deps/poll/poll2.wit b/examples/http/wit/deps/poll/poll2.wit deleted file mode 100644 index f56bf2d..0000000 --- a/examples/http/wit/deps/poll/poll2.wit +++ /dev/null @@ -1,49 +0,0 @@ -/// A poll API intended to let users wait for I/O events on multiple handles -/// at once. -interface poll2 { - /// A "pollable" handle. - /// - /// This is conceptually represents a `stream<_, _>`, or in other words, - /// a stream that one can wait on, repeatedly, but which does not itself - /// produce any data. It's temporary scaffolding until component-model's - /// async features are ready. - /// - /// And at present, it is a `u32` instead of being an actual handle, until - /// the wit-bindgen implementation of handles and resources is ready. - /// - /// `pollable` lifetimes are not automatically managed. Users must ensure - /// that they do not outlive the resource they reference. - /// - /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). - type pollable = u32 - - /// Dispose of the specified `pollable`, after which it may no longer - /// be used. - drop-pollable: func(this: pollable) - - /// Poll for completion on a set of pollables. - /// - /// This function takes a list of pollables, which identify I/O sources of - /// interest, and waits until one or more of the events is ready for I/O. - /// - /// The result `list` is the same length as the argument - /// `list`, and indicates the readiness of each corresponding - /// element in that list, with true indicating ready. A single call can - /// return multiple true elements. - /// - /// A timeout can be implemented by adding a pollable from the - /// wasi-clocks API to the list. - /// - /// This function does not return a `result`; polling in itself does not - /// do any I/O so it doesn't fail. If any of the I/O sources identified by - /// the pollables has an error, it is indicated by marking the source as - /// ready in the `list`. - /// - /// The "oneoff" in the name refers to the fact that this function must do a - /// linear scan through the entire list of subscriptions, which may be - /// inefficient if the number is large and the same subscriptions are used - /// many times. In the future, this is expected to be obsoleted by the - /// component model async proposal, which will include a scalable waiting - /// facility. - poll-oneoff: func(in: list) -> list -} diff --git a/examples/http/wit/deps/poll/world.wit b/examples/http/wit/deps/poll/world.wit deleted file mode 100644 index 5617c5d..0000000 --- a/examples/http/wit/deps/poll/world.wit +++ /dev/null @@ -1,5 +0,0 @@ -package wasi:poll - -world example-world { - import poll2 -} diff --git a/examples/http/wit/deps/random/insecure-seed.wit b/examples/http/wit/deps/random/insecure-seed.wit new file mode 100644 index 0000000..139aed1 --- /dev/null +++ b/examples/http/wit/deps/random/insecure-seed.wit @@ -0,0 +1,24 @@ +/// The insecure-seed interface for seeding hash-map DoS resistance. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure-seed { + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + insecure-seed: func() -> tuple; +} diff --git a/examples/http/wit/deps/random/insecure.wit b/examples/http/wit/deps/random/insecure.wit new file mode 100644 index 0000000..2ffd223 --- /dev/null +++ b/examples/http/wit/deps/random/insecure.wit @@ -0,0 +1,21 @@ +/// The insecure interface for insecure pseudo-random numbers. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure { + /// Return `len` insecure pseudo-random bytes. + /// + /// This function is not cryptographically secure. Do not use it for + /// anything related to security. + /// + /// There are no requirements on the values of the returned bytes, however + /// implementations are encouraged to return evenly distributed values with + /// a long period. + get-insecure-random-bytes: func(len: u64) -> list; + + /// Return an insecure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-insecure-random-bytes`, represented as a `u64`. + get-insecure-random-u64: func() -> u64; +} diff --git a/examples/http/wit/deps/random/random.wit b/examples/http/wit/deps/random/random.wit new file mode 100644 index 0000000..2c3c6a8 --- /dev/null +++ b/examples/http/wit/deps/random/random.wit @@ -0,0 +1,25 @@ +/// WASI Random is a random data API. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface random { + /// Return `len` cryptographically-secure random or pseudo-random bytes. + /// + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. + /// + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. + get-random-bytes: func(len: u64) -> list; + + /// Return a cryptographically-secure random or pseudo-random `u64` value. + /// + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. + get-random-u64: func() -> u64; +} diff --git a/examples/http/wit/deps/random/world.wit b/examples/http/wit/deps/random/world.wit new file mode 100644 index 0000000..dcbff93 --- /dev/null +++ b/examples/http/wit/deps/random/world.wit @@ -0,0 +1,7 @@ +package wasi:random@0.2.0-rc-2023-10-18; + +world imports { + import random; + import insecure; + import insecure-seed; +} diff --git a/examples/http/wit/deps/sockets/instance-network.wit b/examples/http/wit/deps/sockets/instance-network.wit new file mode 100644 index 0000000..14e4479 --- /dev/null +++ b/examples/http/wit/deps/sockets/instance-network.wit @@ -0,0 +1,9 @@ + +/// This interface provides a value-export of the default network handle.. +interface instance-network { + use network.{network}; + + /// Get a handle to the default network. + instance-network: func() -> network; + +} diff --git a/examples/http/wit/deps/sockets/ip-name-lookup.wit b/examples/http/wit/deps/sockets/ip-name-lookup.wit new file mode 100644 index 0000000..f2dab32 --- /dev/null +++ b/examples/http/wit/deps/sockets/ip-name-lookup.wit @@ -0,0 +1,61 @@ + +interface ip-name-lookup { + use wasi:io/poll@0.2.0-rc-2023-10-18.{pollable}; + use network.{network, error-code, ip-address, ip-address-family}; + + + /// Resolve an internet host name to a list of IP addresses. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// # Parameters + /// - `name`: The name to look up. IP addresses are not allowed. Unicode domain names are automatically converted + /// to ASCII using IDNA encoding. + /// - `address-family`: If provided, limit the results to addresses of this specific address family. + /// - `include-unavailable`: When set to true, this function will also return addresses of which the runtime + /// thinks (or knows) can't be connected to at the moment. For example, this will return IPv6 addresses on + /// systems without an active IPv6 interface. Notes: + /// - Even when no public IPv6 interfaces are present or active, names like "localhost" can still resolve to an IPv6 address. + /// - Whatever is "available" or "unavailable" is volatile and can change everytime a network cable is unplugged. + /// + /// This function never blocks. It either immediately fails or immediately returns successfully with a `resolve-address-stream` + /// that can be used to (asynchronously) fetch the results. + /// + /// At the moment, the stream never completes successfully with 0 items. Ie. the first call + /// to `resolve-next-address` never returns `ok(none)`. This may change in the future. + /// + /// # Typical errors + /// - `invalid-argument`: `name` is a syntactically invalid domain name. + /// - `invalid-argument`: `name` is an IP address. + /// - `not-supported`: The specified `address-family` is not supported. (EAI_FAMILY) + /// + /// # References: + /// - + /// - + /// - + /// - + resolve-addresses: func(network: borrow, name: string, address-family: option, include-unavailable: bool) -> result; + + resource resolve-address-stream { + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + resolve-next-address: func() -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } +} diff --git a/examples/http/wit/deps/sockets/network.wit b/examples/http/wit/deps/sockets/network.wit new file mode 100644 index 0000000..fc51604 --- /dev/null +++ b/examples/http/wit/deps/sockets/network.wit @@ -0,0 +1,146 @@ + +interface network { + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + resource network; + + /// Error codes. + /// + /// In theory, every API can return any error code. + /// In practice, API's typically only return the errors documented per API + /// combined with a couple of errors that are always possible: + /// - `unknown` + /// - `access-denied` + /// - `not-supported` + /// - `out-of-memory` + /// - `concurrency-conflict` + /// + /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. + enum error-code { + // ### GENERAL ERRORS ### + + /// Unknown error + unknown, + + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + + /// The operation is not supported. + /// + /// POSIX equivalent: EOPNOTSUPP + not-supported, + + /// One of the arguments is invalid. + /// + /// POSIX equivalent: EINVAL + invalid-argument, + + /// Not enough memory to complete the operation. + /// + /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + out-of-memory, + + /// The operation timed out before it could finish completely. + timeout, + + /// This operation is incompatible with another asynchronous operation that is already in progress. + /// + /// POSIX equivalent: EALREADY + concurrency-conflict, + + /// Trying to finish an asynchronous operation that: + /// - has not been started yet, or: + /// - was already finished by a previous `finish-*` call. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + not-in-progress, + + /// The operation has been aborted because it could not be completed immediately. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + would-block, + + + + // ### TCP & UDP SOCKET ERRORS ### + + /// The operation is not valid in the socket's current state. + invalid-state, + + /// A new socket resource could not be created because of a system limit. + new-socket-limit, + + /// A bind operation failed because the provided address is not an address that the `network` can bind to. + address-not-bindable, + + /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. + address-in-use, + + /// The remote address is not reachable + remote-unreachable, + + + // ### TCP SOCKET ERRORS ### + + /// The connection was forcefully rejected + connection-refused, + + /// The connection was reset. + connection-reset, + + /// A connection was aborted. + connection-aborted, + + // ### UDP SOCKET ERRORS ### + datagram-too-large, + + + // ### NAME LOOKUP ERRORS ### + + /// Name does not exist or has no suitable associated IP addresses. + name-unresolvable, + + /// A temporary failure in name resolution occurred. + temporary-resolver-failure, + + /// A permanent failure in name resolution occurred. + permanent-resolver-failure, + } + + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + type ipv4-address = tuple; + type ipv6-address = tuple; + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + record ipv4-socket-address { + port: u16, // sin_port + address: ipv4-address, // sin_addr + } + + record ipv6-socket-address { + port: u16, // sin6_port + flow-info: u32, // sin6_flowinfo + address: ipv6-address, // sin6_addr + scope-id: u32, // sin6_scope_id + } + + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + +} diff --git a/examples/http/wit/deps/sockets/tcp-create-socket.wit b/examples/http/wit/deps/sockets/tcp-create-socket.wit new file mode 100644 index 0000000..a9a3373 --- /dev/null +++ b/examples/http/wit/deps/sockets/tcp-create-socket.wit @@ -0,0 +1,26 @@ + +interface tcp-create-socket { + use network.{network, error-code, ip-address-family}; + use tcp.{tcp-socket}; + + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`listen`/`connect` + /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + create-tcp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/examples/http/wit/deps/sockets/tcp.wit b/examples/http/wit/deps/sockets/tcp.wit new file mode 100644 index 0000000..448f629 --- /dev/null +++ b/examples/http/wit/deps/sockets/tcp.wit @@ -0,0 +1,268 @@ + +interface tcp { + use wasi:io/streams@0.2.0-rc-2023-10-18.{input-stream, output-stream}; + use wasi:io/poll@0.2.0-rc-2023-10-18.{pollable}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + + /// Similar to `SHUT_WR` in POSIX. + send, + + /// Similar to `SHUT_RDWR` in POSIX. + both, + } + + + /// A TCP socket handle. + resource tcp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will + /// implicitly bind the socket. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) + /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address, but the socket has `ipv6-only` enabled. (EINVAL) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// + /// # Typical `finish` errors + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the Connection state + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// POSIX mentions: + /// > If connect() fails, the state of the socket is unspecified. Conforming applications should + /// > close the file descriptor and create a new socket before attempting to reconnect. + /// + /// WASI prescribes the following behavior: + /// - If `connect` fails because an input/state validation error, the socket should remain usable. + /// - If a connection was actually attempted but failed, the socket should become unusable for further network communication. + /// Besides `drop`, any method after such a failure may return an error. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) + /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address, but the socket has `ipv6-only` enabled. (EINVAL, EADDRNOTAVAIL on Illumos) + /// - `invalid-argument`: `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `invalid-state`: The socket is already in the Connection state. (EISCONN) + /// - `invalid-state`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) + /// + /// # Typical `finish` errors + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + finish-connect: func() -> result, error-code>; + + /// Start listening for new connections. + /// + /// Transitions the socket into the Listener state. + /// + /// Unlike POSIX: + /// - this function is async. This enables interactive WASI hosts to inject permission prompts. + /// - the socket must already be explicitly bound. + /// + /// # Typical `start` errors + /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) + /// - `invalid-state`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) + /// - `invalid-state`: The socket is already in the Listener state. + /// + /// # Typical `finish` errors + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A `listen` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-listen: func() -> result<_, error-code>; + finish-listen: func() -> result<_, error-code>; + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the Connection state. The following properties are inherited from the listener socket: + /// - `address-family` + /// - `ipv6-only` + /// - `keep-alive` + /// - `no-delay` + /// - `unicast-hop-limit` + /// - `receive-buffer-size` + /// - `send-buffer-size` + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `invalid-state`: Socket is not in the Listener state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + accept: func() -> result, error-code>; + + /// Get the bound local address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `invalid-state`: (set) The socket is already bound. + /// - `not-supported`: (get/set) `this` socket is an IPv4 socket. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + ipv6-only: func() -> result; + set-ipv6-only: func(value: bool) -> result<_, error-code>; + + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// # Typical errors + /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. + /// - `invalid-state`: (set) The socket is already in the Connection state. + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + + /// Equivalent to the SO_KEEPALIVE socket option. + keep-alive: func() -> result; + set-keep-alive: func(value: bool) -> result<_, error-code>; + + /// Equivalent to the TCP_NODELAY socket option. + /// + /// The default value is `false`. + no-delay: func() -> result; + set-no-delay: func(value: bool) -> result<_, error-code>; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + /// - `invalid-state`: (set) The socket is already in the Connection state. + /// - `invalid-state`: (set) The socket is already in the Listener state. + unicast-hop-limit: func() -> result; + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-state`: (set) The socket is already in the Connection state. + /// - `invalid-state`: (set) The socket is already in the Listener state. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + + /// Initiate a graceful shutdown. + /// + /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read + /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. + /// Any data still in the receive queue at time of calling `shutdown` will be discarded. + /// - send: the socket is not expecting to send any more data to the peer. All subsequent write + /// operations on the `output-stream` associated with this socket will return an error. + /// - both: same effect as receive & send combined. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the Connection state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + } +} diff --git a/examples/http/wit/deps/sockets/udp-create-socket.wit b/examples/http/wit/deps/sockets/udp-create-socket.wit new file mode 100644 index 0000000..e026359 --- /dev/null +++ b/examples/http/wit/deps/sockets/udp-create-socket.wit @@ -0,0 +1,26 @@ + +interface udp-create-socket { + use network.{network, error-code, ip-address-family}; + use udp.{udp-socket}; + + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` is called, + /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References: + /// - + /// - + /// - + /// - + create-udp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/examples/http/wit/deps/sockets/udp.wit b/examples/http/wit/deps/sockets/udp.wit new file mode 100644 index 0000000..91a8c6c --- /dev/null +++ b/examples/http/wit/deps/sockets/udp.wit @@ -0,0 +1,213 @@ + +interface udp { + use wasi:io/poll@0.2.0-rc-2023-10-18.{pollable}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + + record datagram { + data: list, // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + remote-address: ip-socket-address, + + /// Possible future additions: + /// local-address: ip-socket-address, // IP_PKTINFO / IP_RECVDSTADDR / IPV6_PKTINFO + /// local-interface: u32, // IP_PKTINFO / IP_RECVIF + /// ttl: u8, // IP_RECVTTL + /// dscp: u6, // IP_RECVTOS + /// ecn: u2, // IP_RECVTOS + } + + + + /// A UDP socket handle. + resource udp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to connect will implicitly bind the socket. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// + /// # Typical `finish` errors + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + + /// Set the destination address. + /// + /// The local-address is updated based on the best network path to `remote-address`. + /// + /// When a destination address is set: + /// - all receive operations will only return datagrams sent from the provided `remote-address`. + /// - the `send` function can only be used to send to this destination. + /// + /// Note that this function does not generate any network traffic and the peer is not aware of this "connection". + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// + /// # Typical `finish` errors + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + finish-connect: func() -> result<_, error-code>; + + /// Receive messages on the socket. + /// + /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. + /// The returned list may contain fewer elements than requested, but never more. + /// If `max-results` is 0, this function returns successfully with an empty list. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. (EINVAL) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `would-block`: There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + receive: func(max-results: u64) -> result, error-code>; + + /// Send messages on the socket. + /// + /// This function attempts to send all provided `datagrams` on the socket without blocking and + /// returns how many messages were actually sent (or queued for sending). + /// + /// This function semantically behaves the same as iterating the `datagrams` list and sequentially + /// sending each individual datagram until either the end of the list has been reached or the first error occurred. + /// If at least one datagram has been sent successfully, this function never returns an error. + /// + /// If the input list is empty, the function returns `ok(0)`. + /// + /// The remote address option is required. To send a message to the "connected" peer, + /// call `remote-address` to get their address. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN) + /// - `invalid-state`: The socket is not bound to any local address. Unlike POSIX, this function does not perform an implicit bind. + /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// - `would-block`: The send buffer is currently full. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + send: func(datagrams: list) -> result; + + /// Get the current bound address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// Get the address set with `connect`. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `not-supported`: (get/set) `this` socket is an IPv4 socket. + /// - `invalid-state`: (set) The socket is already bound. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + ipv6-only: func() -> result; + set-ipv6-only: func(value: bool) -> result<_, error-code>; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + unicast-hop-limit: func() -> result; + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. + /// In other words, after setting a value, reading the same setting back may return a different value. + /// + /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of + /// actual data to be sent/received by the application, because the kernel might also use the buffer space + /// for internal metadata structures. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } +} diff --git a/examples/http/wit/deps/sockets/world.wit b/examples/http/wit/deps/sockets/world.wit new file mode 100644 index 0000000..d16530c --- /dev/null +++ b/examples/http/wit/deps/sockets/world.wit @@ -0,0 +1,11 @@ +package wasi:sockets@0.2.0-rc-2023-10-18; + +world imports { + import instance-network; + import network; + import udp; + import udp-create-socket; + import tcp; + import tcp-create-socket; + import ip-name-lookup; +} diff --git a/examples/http/wit/incoming-handler.wit b/examples/http/wit/incoming-handler.wit deleted file mode 100644 index f5666e2..0000000 --- a/examples/http/wit/incoming-handler.wit +++ /dev/null @@ -1,26 +0,0 @@ -/// The `wasi:http/incoming-handler` interface is meant to be exported by -/// components and called by the host in response to a new incoming HTTP -/// response. -/// -/// NOTE: in Preview3, this interface will be merged with -/// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface -/// that takes a `request` parameter and returns a `response` result. -/// -interface incoming-handler2 { - use types2.{incoming-request, response-outparam} - - /// The `handle` function takes an outparam instead of returning its response - /// so that the component may stream its response while streaming any other - /// request or response bodies. The callee MUST write a response to the - /// `response-out` and then finish the response before returning. The caller - /// is expected to start streaming the response once `set-response-outparam` - /// is called and finish streaming the response when `drop-response-outparam` - /// is called. The `handle` function is then allowed to continue executing - /// any post-response logic before returning. While this post-response - /// execution is taken off the critical path, since there is no return value, - /// there is no way to report its success or failure. - handle: func( - request: incoming-request, - response-out: response-outparam - ) -} diff --git a/examples/http/wit/main.wit b/examples/http/wit/main.wit new file mode 100644 index 0000000..f3a4e60 --- /dev/null +++ b/examples/http/wit/main.wit @@ -0,0 +1,33 @@ +package wasmtime:wasi; + +// All of the same imports available in the wasi:cli/command world, but no +// export required: +world preview1-adapter-reactor { + import wasi:clocks/wall-clock@0.2.0-rc-2023-10-18; + import wasi:clocks/monotonic-clock@0.2.0-rc-2023-10-18; + import wasi:clocks/timezone@0.2.0-rc-2023-10-18; + import wasi:filesystem/types@0.2.0-rc-2023-10-18; + import wasi:filesystem/preopens@0.2.0-rc-2023-10-18; + import wasi:sockets/instance-network@0.2.0-rc-2023-10-18; + import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; + import wasi:sockets/network@0.2.0-rc-2023-10-18; + import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-10-18; + import wasi:sockets/tcp@0.2.0-rc-2023-10-18; + import wasi:sockets/udp-create-socket@0.2.0-rc-2023-10-18; + import wasi:sockets/udp@0.2.0-rc-2023-10-18; + import wasi:random/random@0.2.0-rc-2023-10-18; + import wasi:random/insecure@0.2.0-rc-2023-10-18; + import wasi:random/insecure-seed@0.2.0-rc-2023-10-18; + import wasi:io/poll@0.2.0-rc-2023-10-18; + import wasi:io/streams@0.2.0-rc-2023-10-18; + import wasi:cli/environment@0.2.0-rc-2023-10-18; + import wasi:cli/exit@0.2.0-rc-2023-10-18; + import wasi:cli/stdin@0.2.0-rc-2023-10-18; + import wasi:cli/stdout@0.2.0-rc-2023-10-18; + import wasi:cli/stderr@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-input@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-output@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-stdin@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-stdout@0.2.0-rc-2023-10-18; + import wasi:cli/terminal-stderr@0.2.0-rc-2023-10-18; +} diff --git a/examples/http/wit/outgoing-handler.wit b/examples/http/wit/outgoing-handler.wit deleted file mode 100644 index 737912f..0000000 --- a/examples/http/wit/outgoing-handler.wit +++ /dev/null @@ -1,18 +0,0 @@ -/// The `wasi:http/outgoing-handler` interface is meant to be imported by -/// components and implemented by the host. -/// -/// NOTE: in Preview3, this interface will be merged with -/// `wasi:http/outgoing-handler` into a single `wasi:http/handler` interface -/// that takes a `request` parameter and returns a `response` result. -/// -interface outgoing-handler2 { - use types2.{outgoing-request, request-options, future-incoming-response} - - /// The parameter and result types of the `handle` function allow the caller - /// to concurrently stream the bodies of the outgoing request and the incoming - /// response. - handle: func( - request: outgoing-request, - options: option - ) -> future-incoming-response -} diff --git a/examples/http/wit/proxy.wit b/examples/http/wit/proxy.wit deleted file mode 100644 index d5784ac..0000000 --- a/examples/http/wit/proxy.wit +++ /dev/null @@ -1,34 +0,0 @@ -package wasi:http - -/// The `wasi:http/proxy` world captures a widely-implementable intersection of -/// hosts that includes HTTP forward and reverse proxies. Components targeting -/// this world may concurrently stream in and out any number of incoming and -/// outgoing HTTP requests. -world proxy { - /// HTTP proxies have access to time and randomness. - /// import wasi:clocks/wall-clock - /// import wasi:clocks/monotonic-clock - /// import wasi:clocks/timezone - /// import wasi:random/random - - /// Proxies have standard output and error streams which are expected to - /// terminate in a developer-facing console provided by the host. - /// import wasi:cli/stdout - /// import wasi:cli/stderr - - /// TODO: this is a temporary workaround until component tooling is able to - /// gracefully handle the absence of stdin. Hosts must return an eof stream - /// for this import, which is what wasi-libc + tooling will do automatically - /// when this import is properly removed. - /// import wasi:cli/stdin - - /// This is the default handler to use when user code simply wants to make an - /// HTTP request (e.g., via `fetch()`). - import outgoing-handler2 - - /// The host delivers incoming HTTP requests to a component by calling the - /// `handle` function of this exported interface. A host may arbitrarily reuse - /// or not reuse component instance when delivering incoming HTTP requests and - /// thus a component must be able to handle 0..N calls to `handle`. - export incoming-handler2 -} diff --git a/examples/http/wit/test.wit b/examples/http/wit/test.wit new file mode 100644 index 0000000..3db5e08 --- /dev/null +++ b/examples/http/wit/test.wit @@ -0,0 +1,46 @@ +// only used as part of `test-programs` +world test-reactor { + + import wasi:cli/environment@0.2.0-rc-2023-10-18; + import wasi:io/poll@0.2.0-rc-2023-10-18; + import wasi:io/streams@0.2.0-rc-2023-10-18; + import wasi:filesystem/types@0.2.0-rc-2023-10-18; + import wasi:filesystem/preopens@0.2.0-rc-2023-10-18; + import wasi:cli/exit@0.2.0-rc-2023-10-18; + + export add-strings: func(s: list) -> u32; + export get-strings: func() -> list; + + use wasi:io/streams@0.2.0-rc-2023-10-18.{output-stream}; + + export write-strings-to: func(o: output-stream) -> result; + + use wasi:filesystem/types@0.2.0-rc-2023-10-18.{descriptor-stat}; + export pass-an-imported-record: func(d: descriptor-stat) -> string; +} + +world test-command { + import wasi:io/poll@0.2.0-rc-2023-10-18; + import wasi:io/streams@0.2.0-rc-2023-10-18; + import wasi:cli/environment@0.2.0-rc-2023-10-18; + import wasi:cli/stdin@0.2.0-rc-2023-10-18; + import wasi:cli/stdout@0.2.0-rc-2023-10-18; + import wasi:cli/stderr@0.2.0-rc-2023-10-18; +} + +world test-command-with-sockets { + import wasi:io/poll@0.2.0-rc-2023-10-18; + import wasi:io/streams@0.2.0-rc-2023-10-18; + import wasi:cli/environment@0.2.0-rc-2023-10-18; + import wasi:cli/stdin@0.2.0-rc-2023-10-18; + import wasi:cli/stdout@0.2.0-rc-2023-10-18; + import wasi:cli/stderr@0.2.0-rc-2023-10-18; + import wasi:sockets/tcp@0.2.0-rc-2023-10-18; + import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-10-18; + import wasi:sockets/udp@0.2.0-rc-2023-10-18; + import wasi:sockets/udp-create-socket@0.2.0-rc-2023-10-18; + import wasi:sockets/network@0.2.0-rc-2023-10-18; + import wasi:sockets/instance-network@0.2.0-rc-2023-10-18; + import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; + import wasi:clocks/monotonic-clock@0.2.0-rc-2023-10-18; +} diff --git a/examples/http/wit/types2.wit b/examples/http/wit/types2.wit deleted file mode 100644 index 260f3b0..0000000 --- a/examples/http/wit/types2.wit +++ /dev/null @@ -1,187 +0,0 @@ -/// The `wasi:http/types` interface is meant to be imported by components to -/// define the HTTP resource types and operations used by the component's -/// imported and exported interfaces. -interface types2 { - use wasi:io/streams2.{input-stream, output-stream} - use wasi:poll/poll2.{pollable} - - /// This type corresponds to HTTP standard Methods. - variant method { - get, - head, - post, - put, - delete, - connect, - options, - trace, - patch, - other(string) - } - - /// This type corresponds to HTTP standard Related Schemes. - variant scheme { - HTTP, - HTTPS, - other(string) - } - - /// TODO: perhaps better align with HTTP semantics? - /// This type enumerates the different kinds of errors that may occur when - /// initially returning a response. - variant error { - invalid-url(string), - timeout-error(string), - protocol-error(string), - unexpected-error(string) - } - - /// This following block defines the `fields` resource which corresponds to - /// HTTP standard Fields. Soon, when resource types are added, the `type - /// fields = u32` type alias can be replaced by a proper `resource fields` - /// definition containing all the functions using the method syntactic sugar. - type fields = u32 - drop-fields: func(fields: fields) - new-fields: func(entries: list>>) -> fields - fields-get: func(fields: fields, name: string) -> list> - fields-set: func(fields: fields, name: string, value: list>) - fields-delete: func(fields: fields, name: string) - fields-append: func(fields: fields, name: string, value: list) - fields-entries: func(fields: fields) -> list>> - fields-clone: func(fields: fields) -> fields - - type headers = fields - type trailers = fields - - /// The following block defines stream types which corresponds to the HTTP - /// standard Contents and Trailers. With Preview3, all of these fields can be - /// replaced by a stream>. In the interim, we need to - /// build on separate resource types defined by `wasi:io/streams`. The - /// `finish-` functions emulate the stream's result value and MUST be called - /// exactly once after the final read/write from/to the stream before dropping - /// the stream. The optional `future-` types describe the asynchronous result of - /// reading/writing the optional HTTP trailers and MUST be waited on and dropped - /// to complete streaming the request/response. - type incoming-stream = input-stream - type outgoing-stream = output-stream - finish-incoming-stream: func(s: incoming-stream) -> option - finish-outgoing-stream: func(s: outgoing-stream) - finish-outgoing-stream-with-trailers: func(s: outgoing-stream, trailers: trailers) -> future-write-trailers-result - - /// The following block defines the `future-trailers` resource, which is - /// returned when finishing an `incoming-stream` to asychronously produce the - /// final trailers. - type future-trailers = u32 - drop-future-trailers: func(f: future-trailers) - future-trailers-get: func(f: future-trailers) -> option> - listen-to-future-trailers: func(f: future-trailers) -> pollable - - /// The following block defines the `future-write-trailers-result` resource, - /// which is returned when finishing an `outgoing-stream` and asychronously - /// indicates the success or failure of writing the trailers. - type future-write-trailers-result = u32 - drop-future-write-trailers-result: func(f: future-write-trailers-result) - future-write-trailers-result-get: func(f: future-write-trailers-result) -> option> - listen-to-future-write-trailers-result: func(f: future-write-trailers-result) -> pollable - - /// The following block defines the `incoming-request` and `outgoing-request` - /// resource types that correspond to HTTP standard Requests. Soon, when - /// resource types are added, the `u32` type aliases can be replaced by proper - /// `resource` type definitions containing all the functions as methods. - /// Later, Preview2 will allow both types to be merged together into a single - /// `request` type (that uses the single `stream` type mentioned above). The - /// `consume` and `write` methods may only be called once (and return failure - /// thereafter). The `headers` and `trailers` passed into and out of requests - /// are shared with the request, with all mutations visible to all uses. - /// Components MUST avoid updating `headers` and `trailers` after passing a - /// request that points to them to the outside world. - /// The streams returned by `consume` and `write` are owned by the request and - /// response objects. The streams are destroyed when the request/response is - /// dropped, thus a client MUST drop any handle referring to a request/response stream - /// before dropping the request/response or passing ownership of the request/response - /// to the outside world. The caller can also call drop on the stream before the - /// request/response is dropped if they want to release resources earlier. - type incoming-request = u32 - type outgoing-request = u32 - drop-incoming-request: func(request: incoming-request) - drop-outgoing-request: func(request: outgoing-request) - incoming-request-method: func(request: incoming-request) -> method - incoming-request-path-with-query: func(request: incoming-request) -> option - incoming-request-scheme: func(request: incoming-request) -> option - incoming-request-authority: func(request: incoming-request) -> option - incoming-request-headers: func(request: incoming-request) -> headers - incoming-request-consume: func(request: incoming-request) -> result - new-outgoing-request: func( - method: method, - path-with-query: option, - scheme: option, - authority: option, - headers: headers - ) -> result - outgoing-request-write: func(request: outgoing-request) -> result - - /// Additional optional parameters that can be set when making a request. - record request-options { - /// The following timeouts are specific to the HTTP protocol and work - /// independently of the overall timeouts passed to `io.poll.poll-oneoff`. - - /// The timeout for the initial connect. - connect-timeout-ms: option, - - /// The timeout for receiving the first byte of the response body. - first-byte-timeout-ms: option, - - /// The timeout for receiving the next chunk of bytes in the response body - /// stream. - between-bytes-timeout-ms: option - } - - /// The following block defines a special resource type used by the - /// `wasi:http/incoming-handler` interface. When resource types are added, this - /// block can be replaced by a proper `resource response-outparam { ... }` - /// definition. Later, with Preview3, the need for an outparam goes away entirely - /// (the `wasi:http/handler` interface used for both incoming and outgoing can - /// simply return a `stream`). - type response-outparam = u32 - drop-response-outparam: func(response: response-outparam) - set-response-outparam: func(param: response-outparam, response: result) -> result - - /// This type corresponds to the HTTP standard Status Code. - type status-code = u16 - - /// The following block defines the `incoming-response` and `outgoing-response` - /// resource types that correspond to HTTP standard Responses. Soon, when - /// resource types are added, the `u32` type aliases can be replaced by proper - /// `resource` type definitions containing all the functions as methods. Later, - /// Preview2 will allow both types to be merged together into a single `response` - /// type (that uses the single `stream` type mentioned above). The `consume` and - /// `write` methods may only be called once (and return failure thereafter). - /// The `headers` and `trailers` passed into and out of responses are shared - /// with the response, with all mutations visible to all uses. Components MUST - /// avoid updating `headers` and `trailers` after passing a response that - /// points to them to the outside world. - type incoming-response = u32 - type outgoing-response = u32 - drop-incoming-response: func(response: incoming-response) - drop-outgoing-response: func(response: outgoing-response) - incoming-response-status: func(response: incoming-response) -> status-code - incoming-response-headers: func(response: incoming-response) -> headers - incoming-response-consume: func(response: incoming-response) -> result - new-outgoing-response: func( - status-code: status-code, - headers: headers - ) -> result - outgoing-response-write: func(response: outgoing-response) -> result - - /// The following block defines a special resource type used by the - /// `wasi:http/outgoing-handler` interface to emulate - /// `future>` in advance of Preview3. Given a - /// `future-incoming-response`, the client can call the non-blocking `get` - /// method to get the result if it is available. If the result is not available, - /// the client can call `listen` to get a `pollable` that can be passed to - /// `io.poll.poll-oneoff`. - type future-incoming-response = u32 - drop-future-incoming-response: func(f: future-incoming-response) - future-incoming-response-get: func(f: future-incoming-response) -> option> - listen-to-future-incoming-response: func(f: future-incoming-response) -> pollable -} diff --git a/examples/matrix-math/README.md b/examples/matrix-math/README.md deleted file mode 100644 index 0d840c7..0000000 --- a/examples/matrix-math/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Example: `matrix-math` - -This is an example of how to use [wasmtime-py] and [componentize-py] to do -matrix multiplication using [NumPy] inside a sandboxed WASI component. This -demonstrates using a non-trivial Python package containing native extensions -within a guest component. - -[wasmtime-py]: https://github.com/bytecodealliance/wasmtime-py -[componentize-py]: https://github.com/bytecodealliance/componentize-py -[NumPy]: https://numpy.org - -## Prerequisites - -* `wasmtime-py` 13 or later -* `componentize-py` 0.5.0 -* `NumPy`, built for WASI - -Note that we must build `wasmtime-py` from source until version 13 has been -released. - -Also note that we use an unofficial build of NumPy since the upstream project -does not yet publish WASI builds. - -``` -git clone https://github.com/bytecodealliance/wasmtime-py -(cd wasmtime-py && python ci/download-wasmtime.py && python ci/build-rust.py && pip install .) -pip install componentize-py -curl -OL https://github.com/dicej/wasi-wheels/releases/download/canary/numpy-wasi.tar.gz -tar xf numpy-wasi.tar.gz -``` - -## Running the demo - -``` -componentize-py -d wit -w matrix-math componentize guest -o matrix-math.wasm -python3 -m wasmtime.bindgen matrix-math.wasm --out-dir matrix_math -python3 host.py '[[1, 2], [4, 5], [6, 7]]' '[[1, 2, 3], [4, 5, 6]]' -``` - -The last command above should print the following: - -``` -guest log: matrix_multiply received arguments [[1.0, 2.0], [4.0, 5.0], [6.0, 7.0]] and [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]] -result: [[9.0, 12.0, 15.0], [24.0, 33.0, 42.0], [34.0, 47.0, 60.0]] -``` - -If you run into any problems, please file an issue! diff --git a/examples/matrix-math/guest.py b/examples/matrix-math/guest.py deleted file mode 100644 index a11d054..0000000 --- a/examples/matrix-math/guest.py +++ /dev/null @@ -1,20 +0,0 @@ -import numpy -import matrix_math -from matrix_math.types import Err -from matrix_math import log -from typing import NoReturn - -def handle(e: Exception) -> NoReturn: - message = str(e) - if message == '': - raise Err(f"{type(e).__name__}") - else: - raise Err(f"{type(e).__name__}: {message}") - -class MatrixMath(matrix_math.MatrixMath): - def multiply(self, a: list[list[float]], b: list[list[float]]) -> list[list[float]]: - try: - log(f"matrix_multiply received arguments {a} and {b}") - return numpy.matmul(a, b).tolist() - except Exception as e: - handle(e) diff --git a/examples/matrix-math/host.py b/examples/matrix-math/host.py deleted file mode 100644 index ff951e9..0000000 --- a/examples/matrix-math/host.py +++ /dev/null @@ -1,61 +0,0 @@ -import matrix_math -import sys - -from matrix_math import Root, RootImports, imports -from matrix_math.types import Ok, Err, Result -from matrix_math.imports import environment -from wasmtime import Store -from typing import List, Tuple - -class Host(imports.Host): - def log(self, message: str) -> Result[None, str]: - print(f"guest log: {message}") - return Ok(None) - -class HostEnvironment(environment.HostEnvironment): - def get_environment(self) -> List[Tuple[str, str]]: - return [] - - def get_arguments(self) -> List[str]: - return [] - -args = sys.argv[1:] -if len(args) != 2: - print("usage: python3 host.py ", file=sys.stderr) - exit(-1) - -store = Store() - -matrix_math = Root( - store, - RootImports( - host=Host(), - environment=HostEnvironment(), - # As of this writing, `wasmtime-py` does not yet support WASI Preview 2, - # and our example won't use it at runtime anyway, so we provide `None` - # for most `wasi-cli` interfaces: - poll=None, - monotonic_clock=None, - wall_clock=None, - streams=None, - types=None, - preopens=None, - random=None, - exit=None, - stdin=None, - stdout=None, - stderr=None, - terminal_input=None, - terminal_output=None, - terminal_stdin=None, - terminal_stdout=None, - terminal_stderr=None - ) -) - -result = matrix_math.multiply(store, eval(args[0]), eval(args[1])) - -if isinstance(result, Ok): - print(f"result: {result.value}") -else: - print(f"error: {result.value}") diff --git a/examples/matrix-math/wit/matrix-math.wit b/examples/matrix-math/wit/matrix-math.wit deleted file mode 100644 index a5cc44c..0000000 --- a/examples/matrix-math/wit/matrix-math.wit +++ /dev/null @@ -1,7 +0,0 @@ -package componentize-py:examples - -world matrix-math { - import log: func(message: string) -> result<_, string> - - export multiply: func(a: list>, b: list>) -> result>, string> -} diff --git a/pyproject.toml b/pyproject.toml index 420dbd7..5fac432 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ features = ["pyo3/extension-module"] [project] name = "componentize-py" -version = "0.5.0" +version = "0.6.0" description = "Tool to package Python applications as WebAssembly components" readme = "README.md" license = { file = "LICENSE" } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index cb8a1e7..12d97dc 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -173,7 +173,13 @@ fn do_init(app_name: String, symbols: Symbols) -> Result<()> { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { - let app = py.import(app_name.as_str())?; + let app = match py.import(app_name.as_str()) { + Ok(app) => app, + Err(e) => { + e.print(py); + return Err(e.into()); + } + }; EXPORTS .set( @@ -337,7 +343,22 @@ struct MyExports; impl Guest for MyExports { fn init(app_name: String, symbols: Symbols) -> Result<(), String> { - do_init(app_name, symbols).map_err(|e| format!("{e:?}")) + let result = do_init(app_name, symbols).map_err(|e| format!("{e:?}")); + + // This tells the WASI Preview 1 component adapter to reset its state. In particular, we want it to forget + // about any open handles and re-request the stdio handles at runtime since we'll be running under a brand + // new host. + #[link(wasm_import_module = "wasi_snapshot_preview1")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "reset_adapter_state")] + fn reset_adapter_state(); + } + + unsafe { + reset_adapter_state(); + } + + result } } diff --git a/src/bindings.rs b/src/bindings.rs index 2f45e9f..c7ea2d3 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -60,7 +60,7 @@ pub fn make_bindings(resolve: &Resolve, world: WorldId, summary: &Summary) -> Re .as_ref() .map(|interface| { format!( - "{}{}{}", + "{}{}", if matches!( function.kind, FunctionKind::Import | FunctionKind::ResourceDropRemote @@ -69,12 +69,11 @@ pub fn make_bindings(resolve: &Resolve, world: WorldId, summary: &Summary) -> Re } else { "[export]" }, - if let Some(package) = interface.package { - format!("{}:{}/", package.namespace, package.name) + if let Some(name) = resolve.id_of(interface.id) { + name } else { - String::new() - }, - interface.name + interface.name.to_owned() + } ) }) .unwrap_or_else(|| "$root".to_owned()); @@ -94,7 +93,10 @@ pub fn make_bindings(resolve: &Resolve, world: WorldId, summary: &Summary) -> Re types.function(params, results); imports.import(module, name, EntityType::Function(offset)); - function_names.push((offset, format!("{}-imported", function.internal_name()))); + function_names.push(( + offset, + format!("{}-imported", function.internal_name(resolve)), + )); } let import_function_count = imports.len(); @@ -170,7 +172,7 @@ pub fn make_bindings(resolve: &Resolve, world: WorldId, summary: &Summary) -> Re let (params, results) = function.core_export_type(resolve); types.function(params, results); functions.function(offset); - function_names.push((offset, function.internal_name())); + function_names.push((offset, function.internal_name(resolve))); let mut gen = FunctionBindgen::new(summary, function, stack_pointer); match function.kind { @@ -228,13 +230,12 @@ pub fn make_bindings(resolve: &Resolve, world: WorldId, summary: &Summary) -> Re }, if let Some(interface) = &function.interface { format!( - "{}{}#{}", - if let Some(package) = interface.package { - format!("{}:{}/", package.namespace, package.name) + "{}#{}", + if let Some(name) = resolve.id_of(interface.id) { + name } else { - String::new() + interface.name.to_owned() }, - interface.name, function.name ) } else { @@ -373,6 +374,7 @@ pub fn make_bindings(resolve: &Resolve, world: WorldId, summary: &Summary) -> Re world, wit_component::StringEncoding::UTF8, None, + None, )?), }); diff --git a/src/command.rs b/src/command.rs index 30a0daf..96b4489 100644 --- a/src/command.rs +++ b/src/command.rs @@ -68,6 +68,10 @@ pub struct Bindings { /// /// This will be created if it does not already exist. pub output_dir: PathBuf, + + /// Disable type annotations + #[arg(short = 'n', long)] + pub no_typings: bool, } pub fn run + Clone, I: IntoIterator>(args: I) -> Result<()> { @@ -83,6 +87,7 @@ fn generate_bindings(common: Common, bindings: Bindings) -> Result<()> { &common.wit_path, common.world.as_deref(), &bindings.output_dir, + !bindings.no_typings, ) } diff --git a/src/lib.rs b/src/lib.rs index a7e0289..272fd86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ use { preview2::{ command as wasi_command, pipe::{MemoryInputPipe, MemoryOutputPipe}, - DirPerms, FilePerms, IsATTY, Table, WasiCtx, WasiCtxBuilder, WasiView, + DirPerms, FilePerms, Table, WasiCtx, WasiCtxBuilder, WasiView, }, Dir, }, @@ -136,12 +136,17 @@ impl Invoker for MyInvoker { } } -pub fn generate_bindings(wit_path: &Path, world: Option<&str>, output_dir: &Path) -> Result<()> { +pub fn generate_bindings( + wit_path: &Path, + world: Option<&str>, + output_dir: &Path, + with_typings: bool, +) -> Result<()> { let (resolve, world) = parse_wit(wit_path, world)?; let summary = Summary::try_new(&resolve, world)?; let world_dir = output_dir.join(resolve.worlds[world].name.to_snake_case().escape()); fs::create_dir_all(&world_dir)?; - summary.generate_code(&world_dir)?; + summary.generate_code(&world_dir, with_typings)?; // Also generate `componentize_py_runtime` stub for type checking purposes: let internal_dir = output_dir.join("componentize_py_runtime"); @@ -289,7 +294,7 @@ pub async fn componentize( .path() .join(resolve.worlds[world].name.to_snake_case()); fs::create_dir_all(&world_dir)?; - summary.generate_code(&world_dir)?; + summary.generate_code(&world_dir, false)?; let python_path = iter::once( generated_code @@ -304,9 +309,9 @@ pub async fn componentize( let stderr = MemoryOutputPipe::new(10000); let mut wasi = WasiCtxBuilder::new(); - wasi.stdin(MemoryInputPipe::new(Bytes::new()), IsATTY::No) - .stdout(stdout.clone(), IsATTY::No) - .stderr(stderr.clone(), IsATTY::No) + wasi.stdin(MemoryInputPipe::new(Bytes::new())) + .stdout(stdout.clone()) + .stderr(stderr.clone()) .env("PYTHONUNBUFFERED", "1") .env("COMPONENTIZE_PY_APP_NAME", app_name) .env("PYTHONHOME", "/python") @@ -333,10 +338,10 @@ pub async fn componentize( .collect::>() .join(":"); - let mut table = Table::new(); + let table = Table::new(); let wasi = wasi .env("PYTHONPATH", format!("/python:{python_path}")) - .build(&mut table)?; + .build(); let mut config = Config::new(); config.wasm_component_model(true); @@ -418,19 +423,7 @@ fn add_wasi_and_stubs( WorldItem::Interface(interface) => { let interface_name = match key { WorldKey::Name(name) => name.clone(), - WorldKey::Interface(interface) => { - let interface = &resolve.interfaces[*interface]; - format!( - "{}{}", - if let Some(package) = interface.package { - let package = &resolve.packages[package]; - format!("{}:{}/", package.name.namespace, package.name.name) - } else { - String::new() - }, - interface.name.as_deref().unwrap() - ) - } + WorldKey::Interface(interface) => resolve.id_of(*interface).unwrap(), }; let interface = &resolve.interfaces[*interface]; @@ -470,19 +463,24 @@ fn add_wasi_and_stubs( for (interface_name, stubs) in stubs { if let Some(interface_name) = interface_name { - let mut instance = linker.instance(&interface_name)?; - for stub in stubs { - let interface_name = interface_name.clone(); - match stub { - Stub::Function(name) => instance.func_new(component, name, { - let name = name.clone(); - move |_, _, _| Err(anyhow!("called trapping stub: {interface_name}#{name}")) - }), - Stub::Resource(name) => instance.resource::<()>(name, { - let name = name.clone(); - move |_, _| Err(anyhow!("called trapping stub: {interface_name}#{name}")) - }), - }?; + if let Ok(mut instance) = linker.instance(&interface_name) { + for stub in stubs { + let interface_name = interface_name.clone(); + match stub { + Stub::Function(name) => instance.func_new(component, name, { + let name = name.clone(); + move |_, _, _| { + Err(anyhow!("called trapping stub: {interface_name}#{name}")) + } + }), + Stub::Resource(name) => instance.resource::<()>(name, { + let name = name.clone(); + move |_, _| { + Err(anyhow!("called trapping stub: {interface_name}#{name}")) + } + }), + }?; + } } } else { let mut instance = linker.root(); diff --git a/src/summary.rs b/src/summary.rs index 333c2a4..a013ec9 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -1,7 +1,7 @@ use { crate::{ abi::{self, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS}, - bindgen::{self, DISPATCHABLE_CORE_PARAM_COUNT}, + bindgen::DISPATCHABLE_CORE_PARAM_COUNT, exports::exports::{ self, Case, Constructor, Function, FunctionExport, LocalResource, OwnedKind, OwnedType, RemoteResource, Resource, Static, Symbols, @@ -17,6 +17,7 @@ use { fmt::Write as _, fs::{self, File}, io::Write as _, + iter, ops::Deref, path::Path, str, @@ -84,16 +85,15 @@ pub struct MyFunction<'a> { } impl<'a> MyFunction<'a> { - pub fn internal_name(&self) -> String { + pub fn internal_name(&self, resolve: &Resolve) -> String { if let Some(interface) = &self.interface { format!( - "{}{}#{}{}", - if let Some(package) = interface.package { - format!("{}:{}/", package.namespace, package.name) + "{}#{}{}", + if let Some(name) = resolve.id_of(interface.id) { + name } else { - String::new() + interface.name.to_owned() }, - interface.name, self.name, match self.kind { FunctionKind::Import => "-import", @@ -169,7 +169,7 @@ struct FunctionCode { snake: String, params: String, args: String, - return_statement: &'static str, + return_statement: String, static_method: &'static str, return_type: String, result_count: usize, @@ -701,21 +701,12 @@ impl<'a> Summary<'a> { } } - fn is_self_handle(&self, resource: Option, ty: Type) -> bool { - if let (Some(resource), Type::Id(id)) = (resource, ty) { - if let TypeDefKind::Handle(Handle::Own(id) | Handle::Borrow(id)) = - &self.resolve.types[id].kind - { - bindgen::dealias(self.resolve, *id) == resource - } else { - false - } - } else { - false - } - } - - fn function_code(&self, function: &MyFunction, names: &mut TypeNames) -> FunctionCode { + fn function_code( + &self, + function: &MyFunction, + names: &mut TypeNames, + with_typings: bool, + ) -> FunctionCode { enum SpecialReturn<'a> { Result(&'a Result_), None, @@ -735,32 +726,28 @@ impl<'a> Summary<'a> { let snake = self.function_name(function); - let (skip_count, self_, resource) = match function.wit_kind { - wit_parser::FunctionKind::Freestanding => (0, false, None), - wit_parser::FunctionKind::Constructor(resource) => (0, true, Some(resource)), - wit_parser::FunctionKind::Method(resource) => (1, true, Some(resource)), - wit_parser::FunctionKind::Static(resource) => (0, false, Some(resource)), + let (skip_count, self_) = match function.wit_kind { + wit_parser::FunctionKind::Freestanding => (0, false), + wit_parser::FunctionKind::Constructor(_) => (0, true), + wit_parser::FunctionKind::Method(_) => (1, true), + wit_parser::FunctionKind::Static(_) => (0, false), }; - let mut type_name = |ty| { - if self.is_self_handle(resource, ty) { - // TODO: we should probably use `typing.Self` here in most cases, but note that it won't work for - // static methods as of this writing. Maybe we can do something fancy with `typing.TypeVar`. - "Any".to_string() - } else { - names.type_name(ty) - } - }; + let mut type_name = |ty| names.type_name(ty); - let params = - self_ - .then(|| "self".to_string()) - .into_iter() - .chain(function.params.iter().skip(skip_count).map(|(name, ty)| { - format!("{}: {}", name.to_snake_case().escape(), type_name(*ty)) - })) - .collect::>() - .join(", "); + let params = self_ + .then(|| "self".to_string()) + .into_iter() + .chain(function.params.iter().skip(skip_count).map(|(name, ty)| { + let snake = name.to_snake_case().escape(); + if with_typings { + format!("{snake}: {}", type_name(*ty)) + } else { + snake + } + })) + .collect::>() + .join(", "); let args = function .params @@ -773,22 +760,30 @@ impl<'a> Summary<'a> { let (return_statement, return_type) = if let wit_parser::FunctionKind::Constructor(_) = function.wit_kind { - ("return", "None".to_owned()) + ("return".to_owned(), "None".to_owned()) } else { + let indent = if let wit_parser::FunctionKind::Freestanding = function.wit_kind { + "" + } else { + " " + }; + match result_types.as_slice() { - [] => ("return", "None".to_owned()), + [] => ("return".to_owned(), "None".to_owned()), [ty] => match special_return(*ty) { SpecialReturn::Result(result) => ( - "if isinstance(result[0], Err): - raise result[0] - else: - return result[0].value", + format!( + "if isinstance(result[0], Err): +{indent} raise result[0] +{indent} else: +{indent} return result[0].value" + ), result.ok.map(type_name).unwrap_or_else(|| "None".into()), ), - SpecialReturn::None => ("return result[0]", type_name(*ty)), + SpecialReturn::None => ("return result[0]".to_owned(), type_name(*ty)), }, _ => ( - "return result", + "return result".to_owned(), format!( "({})", result_types @@ -815,12 +810,16 @@ impl<'a> Summary<'a> { args, return_statement, static_method, - return_type, + return_type: if with_typings { + format!(" -> {return_type}") + } else { + String::new() + }, result_count, } } - pub fn generate_code(&self, path: &Path) -> Result<()> { + pub fn generate_code(&self, path: &Path, with_typings: bool) -> Result<()> { #[derive(Default)] struct Definitions<'a> { types: Vec, @@ -1020,7 +1019,7 @@ class {camel}(Flag): return_statement, static_method, result_count, - } = self.function_code(function, &mut names); + } = self.function_code(function, &mut names, with_typings); let docs = docstring(function.docs, 2); @@ -1037,7 +1036,7 @@ class {camel}(Flag): } else { format!( "{static_method} - def {snake}({params}) -> {return_type}: + def {snake}({params}){return_type}: {docs}result = componentize_py_runtime.call_import({index}, [{args}], {result_count}) {return_statement} " @@ -1063,6 +1062,15 @@ class {camel}(Flag): } }) .map(method) + // TODO: avoid potential name conflict: + .chain(iter::once(format!( + " + def drop(self): + (_, func, args, _) = self.finalizer.detach() + self.handle = None + func(args[0], args[1]) +" + ))) .collect::>() .concat(); @@ -1090,14 +1098,14 @@ class {camel}: return_type, static_method, .. - } = self.function_code(function, &mut names); + } = self.function_code(function, &mut names, with_typings); let docs = docstring(function.docs, 2); format!( "{static_method} @abstractmethod - def {snake}({params}) -> {return_type}: + def {snake}({params}){return_type}: {docs}raise NotImplementedError " ) @@ -1235,7 +1243,7 @@ class {camel}(Protocol): return_statement, result_count, .. - } = self.function_code(function, &mut names); + } = self.function_code(function, &mut names, with_typings); match function.kind { FunctionKind::Import => { @@ -1243,7 +1251,7 @@ class {camel}(Protocol): let code = format!( " -def {snake}({params}) -> {return_type}: +def {snake}({params}){return_type}: {docs}result = componentize_py_runtime.call_import({index}, [{args}], {result_count}) {return_statement} " @@ -1277,7 +1285,7 @@ def {snake}({params}) -> {return_type}: let code = format!( " @abstractmethod - def {snake}({params}) -> {return_type}: + def {snake}({params}){return_type}: {docs}raise NotImplementedError " ); diff --git a/src/test.rs b/src/test.rs index 84304b4..1d4122a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -133,12 +133,11 @@ impl Tester { let runtime = Runtime::new()?; let mut store = runtime.block_on(async { - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .inherit_stdout() .inherit_stderr() - .build(&mut table) - .unwrap(); + .build(); Store::new(&ENGINE, Ctx { wasi, table }) }); @@ -166,12 +165,11 @@ impl Tester { Ok(runner.run(strategy, move |v| { let mut store = runtime.block_on(async { - let mut table = Table::new(); + let table = Table::new(); let wasi = WasiCtxBuilder::new() .inherit_stdout() .inherit_stderr() - .build(&mut table) - .unwrap(); + .build(); Store::new(&ENGINE, Ctx { wasi, table }) }); diff --git a/src/test/tests.rs b/src/test/tests.rs index 441f641..b139943 100644 --- a/src/test/tests.rs +++ b/src/test/tests.rs @@ -29,6 +29,7 @@ import resource_borrow_in_record from tests import exports, imports from tests.imports import resource_borrow_import from tests.imports import simple_import_and_export +from tests.exports import resource_alias2 from tests.types import Result, Ok from typing import Tuple, List, Optional diff --git a/wit/deps/cli/environment.wit b/wit/deps/cli/environment.wit index bebfc4a..931b1ff 100644 --- a/wit/deps/cli/environment.wit +++ b/wit/deps/cli/environment.wit @@ -1,4 +1,4 @@ -package wasi:cli +package wasi:cli@0.2.0-rc-2023-10-18; interface environment { /// Get the POSIX-style environment variables. @@ -9,12 +9,12 @@ interface environment { /// Morally, these are a value import, but until value imports are available /// in the component model, this import function should return the same /// values each time it is called. - get-environment: func() -> list> + get-environment: func() -> list>; /// Get the POSIX-style arguments to the program. - get-arguments: func() -> list + get-arguments: func() -> list; /// Return a path that programs should use as their initial current working /// directory, interpreting `.` as shorthand for this. - initial-cwd: func() -> option + initial-cwd: func() -> option; } diff --git a/wit/init.wit b/wit/init.wit index 4bfcf9d..1cbf88c 100644 --- a/wit/init.wit +++ b/wit/init.wit @@ -1,7 +1,7 @@ package componentize-py:init world init { - import wasi:cli/environment + import wasi:cli/environment@0.2.0-rc-2023-10-18 export exports: interface { record function {