Repository: bytecodealliance/componentize-py (language: python)

Issue:
Add interface version support
Per https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#package-declaration, if the WIT interface has an interface, it needs to be encoded in the component type identifier for that interface.


Discussion / hints:

Fix patch:
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 9aaeb5e..0000000
Binary files a/adapters/40c1f9b8/wasi_snapshot_preview1.reactor.wasm and /dev/null differ
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 0000000..3154753
Binary files /dev/null and b/adapters/e8766e49/wasi_snapshot_preview1.reactor.wasm differ
diff --git a/build.rs b/build.rs
index 5d9fb74..93e35c0 100644
--- a/build.rs
+++ b/build.rs
@@ -80,6 +80,14 @@ fn stubs_for_clippy(out_dir: &Path) -> Result<()> {
             .do_finish()?;
     }
 
+    let path = out_dir.join("bundled.tar.zst");
+
+    if !path.exists() {
+        Builder::new(Encoder::new(File::create(path)?, ZSTD_COMPRESSION_LEVEL)?)
+            .into_inner()?
+            .do_finish()?;
+    }
+
     Ok(())
 }
 
@@ -184,8 +192,24 @@ fn package_all_the_things(out_dir: &Path) -> Result<()> {
     } else {
         bail!("no such directory: {}", path.display())
     }
+
+    let path = repo_dir.join("bundled");
+
+    if path.exists() {
+        let mut builder = Builder::new(Encoder::new(
+            File::create(out_dir.join("bundled.tar.zst"))?,
+            ZSTD_COMPRESSION_LEVEL,
+        )?);
+
+        add(&mut builder, &path, &path)?;
+
+        builder.into_inner()?.do_finish()?;
+    } else {
+        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/poll_loop.py b/bundled/poll_loop.py
similarity index 72%
rename from examples/http/poll_loop.py
rename to bundled/poll_loop.py
index 6de255d..c7cb852 100644
--- a/examples/http/poll_loop.py
+++ b/bundled/poll_loop.py
@@ -1,4 +1,6 @@
-"""Defines a custom `asyncio` event loop backed by WASI's `poll_oneoff`.
+"""Defines a custom `asyncio` event loop backed by `wasi:io/poll#poll-list`.
+
+This also includes helper classes and functions for working with `wasi:http`.
 
 As of WASI Preview 2, there is not yet a standard for first-class, composable
 asynchronous functions and streams.  We expect that little or none of this
@@ -9,68 +11,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 +134,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 +362,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/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 <<EOF
+curl -i -H 'content-type: text/plain' --data-binary @- http://127.0.0.1:8080/echo <<EOF
 ’Twas brillig, and the slithy toves
       Did gyre and gimble in the wabe:
 All mimsy were the borogoves,
@@ -52,7 +56,7 @@ curl -i \
     -H 'url: https://webassembly.github.io/spec/core/' \
     -H 'url: https://www.w3.org/groups/wg/wasm/' \
     -H 'url: https://bytecodealliance.org/' \
-    http://127.0.0.1:3000/hash-all
+    http://127.0.0.1:8080/hash-all
 ```
 
 If you run into any problems, please file an issue!
diff --git a/examples/http/app.py b/examples/http/app.py
index 9ad4e83..d39dc5d 100644
--- a/examples/http/app.py
+++ b/examples/http/app.py
@@ -5,33 +5,27 @@
 to thread I/O through coroutines.
 """
 
-# Note that we've temporarily renamed various `wasi-http` and `wasi-cli`
-# interfaces (appending a 2 to each name) to avoid conflicting with the
-# implementations in `wasmtime-wasi` which are still under development.
-#
-# Also note that `wasi-http` currently uses pseudo-resources (represented as
-# integers) to model requests, responses, etc.  As of this writing, proper WIT
-# resource support is still under development; we'll update this example to use
-# them once they're ready.
-
 import asyncio
 import hashlib
 import poll_loop
 
 from proxy import exports
 from proxy.types import Ok
-from proxy.imports import types2 as types, outgoing_handler2 as outgoing_handler
-from proxy.imports.types2 import MethodGet, MethodPost, Scheme, SchemeHttp, SchemeHttps, SchemeOther
+from proxy.imports import types
+from proxy.imports.types import (
+    MethodGet, MethodPost, Scheme, SchemeHttp, SchemeHttps, SchemeOther, IncomingRequest, ResponseOutparam,
+    OutgoingResponse, Fields, OutgoingBody, OutgoingRequest
+)
 from poll_loop import Stream, Sink, PollLoop
-from typing import Tuple, cast
+from typing import Tuple
 from urllib import parse
 
-class IncomingHandler2(exports.IncomingHandler2):
+class IncomingHandler(exports.IncomingHandler):
     """Implements the `export`ed portion of the `wasi-http` `proxy` world."""
     
-    def handle(self, request: int, response_out: int):
-        """Handle the specified `request` (represented as a pseudo-resource), sending
-        the response to `response_out`.
+    def handle(self, request: IncomingRequest, response_out: ResponseOutparam):
+        """Handle the specified `request`, sending the response to `response_out`.
+
         """
         # Dispatch the request using `asyncio`, backed by a custom event loop
         # based on WASI's `poll_oneoff` function.
@@ -39,13 +33,12 @@ def handle(self, request: int, response_out: int):
         asyncio.set_event_loop(loop)
         loop.run_until_complete(handle_async(request, response_out))
 
-async def handle_async(request: int, response_out: int):
-    """Handle the specified `request` (represented as a pseudo-resource), sending
-    the response to `response_out`."""
+async def handle_async(request: IncomingRequest, response_out: ResponseOutparam):
+    """Handle the specified `request`, sending the response to `response_out`."""
     
-    method = types.incoming_request_method(request)
-    path = types.incoming_request_path_with_query(request)
-    headers = types.fields_entries(types.incoming_request_headers(request))
+    method = request.method()
+    path = request.path_with_query()
+    headers = request.headers().entries()
 
     if isinstance(method, MethodGet) and path == "/hash-all":
         # Collect one or more "url" headers, download their contents
@@ -55,29 +48,33 @@ async def handle_async(request: int, response_out: int):
         
         urls = map(lambda pair: str(pair[1], "utf-8"), filter(lambda pair: pair[0] == "url", headers))
 
-        response = types.new_outgoing_response(200, types.new_fields([("content-type", b"text/plain")]))
+        response = OutgoingResponse(200, Fields([("content-type", b"text/plain")]))
 
-        types.set_response_outparam(response_out, Ok(response))
+        response_body = response.write()
         
-        sink = Sink(types.outgoing_response_write(response))
-
+        ResponseOutparam.set(response_out, Ok(response))
+        
+        sink = Sink(response_body)
         for result in asyncio.as_completed(map(sha256, urls)):
             url, sha = await result
             await sink.send(bytes(f"{url}: {sha}\n", "utf-8"))
 
         sink.close()
+            
     elif isinstance(method, MethodPost) and path == "/echo":
         # Echo the request body back to the client without buffering.
-        
-        response = types.new_outgoing_response(
+
+        response = OutgoingResponse(
             200,
-            types.new_fields(list(filter(lambda pair: pair[0] == "content-type", headers)))
+            Fields(list(filter(lambda pair: pair[0] == "content-type", headers)))
         )
-        types.set_response_outparam(response_out, Ok(response))
 
-        stream = Stream(types.incoming_request_consume(request))
-        sink = Sink(types.outgoing_response_write(response))
+        response_body = response.write()
+        
+        ResponseOutparam.set(response_out, Ok(response))
 
+        stream = Stream(request.consume())
+        sink = Sink(response_body)
         while True:
             chunk = await stream.next()
             if chunk is None:
@@ -87,9 +84,9 @@ async def handle_async(request: int, response_out: int):
 
         sink.close()
     else:
-        response = types.new_outgoing_response(400, types.new_fields([]))
-        types.set_response_outparam(response_out, Ok(response))
-        types.finish_outgoing_stream(types.outgoing_response_write(response))
+        response = OutgoingResponse(400, Fields([]))
+        ResponseOutparam.set(response_out, Ok(response))
+        OutgoingBody.finish(response.write(), None)
 
 async def sha256(url: str) -> Tuple[str, str]:
     """Download the contents of the specified URL, computing the SHA-256
@@ -109,22 +106,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 +127,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/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 <engineering@fermyon.com>"]
-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<tuple<string, string>>;
+
+  /// Get the POSIX-style arguments to the program.
+  get-arguments: func() -> list<string>;
+
+  /// Return a path that programs should use as their initial current working
+  /// directory, interpreting `.` as shorthand for this.
+  initial-cwd: func() -> option<string>;
+}
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<terminal-input>;
+}
+
+/// An interface providing an optional `terminal-output` for stdout as a
+/// link-time authority.
+interface terminal-stdout {
+    use terminal-output.{terminal-output};
+
+    /// If stdout is connected to a terminal, return a `terminal-output` handle
+    /// allowing further interaction with it.
+    get-terminal-stdout: func() -> option<terminal-output>;
+}
+
+/// An interface providing an optional `terminal-output` for stderr as a
+/// link-time authority.
+interface terminal-stderr {
+    use terminal-output.{terminal-output};
+
+    /// If stderr is connected to a terminal, return a `terminal-output` handle
+    /// allowing further interaction with it.
+    get-terminal-stderr: func() -> option<terminal-output>;
+}
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<tuple<descriptor, string>>;
+}
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<datetime>,
+        /// Last data modification timestamp.
+        ///
+        /// If the `option` is none, the platform doesn't maintain a
+        /// modification timestamp for this file.
+        data-modification-timestamp: option<datetime>,
+        /// 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<datetime>,
+    }
+
+    /// Flags determining the method of how paths are resolved.
+    flags path-flags {
+        /// As long as the resolved path corresponds to a symbolic link, it is
+        /// expanded.
+        symlink-follow,
+    }
+
+    /// Open flags used by `open-at`.
+    flags open-flags {
+        /// Create file if it does not exist, similar to `O_CREAT` in POSIX.
+        create,
+        /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX.
+        directory,
+        /// Fail if file already exists, similar to `O_EXCL` in POSIX.
+        exclusive,
+        /// Truncate file to size 0, similar to `O_TRUNC` in POSIX.
+        truncate,
+    }
+
+    /// Permissions mode used by `open-at`, `change-file-permissions-at`, and
+    /// similar.
+    flags modes {
+        /// True if the resource is considered readable by the containing
+        /// filesystem.
+        readable,
+        /// True if the resource is considered writable by the containing
+        /// filesystem.
+        writable,
+        /// True if the resource is considered executable by the containing
+        /// filesystem. This does not apply to directories.
+        executable,
+    }
+
+    /// Access type used by `access-at`.
+    variant access-type {
+        /// Test for readability, writeability, or executability.
+        access(modes),
+
+        /// Test whether the path exists.
+        exists,
+    }
+
+    /// Number of hard links to an inode.
+    type link-count = u64;
+
+    /// When setting a timestamp, this gives the value to set it to.
+    variant new-timestamp {
+        /// Leave the timestamp set to its previous value.
+        no-change,
+        /// Set the timestamp to the current time of the system clock associated
+        /// with the filesystem.
+        now,
+        /// Set the timestamp to the given value.
+        timestamp(datetime),
+    }
+
+    /// A directory entry.
+    record directory-entry {
+        /// The type of the file referred to by this directory entry.
+        %type: descriptor-type,
+
+        /// The name of the object.
+        name: string,
+    }
+
+    /// Error codes returned by functions, similar to `errno` in POSIX.
+    /// Not all of these error codes are returned by the functions provided by this
+    /// API; some are used in higher-level library layers, and others are provided
+    /// merely for alignment with POSIX.
+    enum error-code {
+        /// Permission denied, similar to `EACCES` in POSIX.
+        access,
+        /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX.
+        would-block,
+        /// Connection already in progress, similar to `EALREADY` in POSIX.
+        already,
+        /// Bad descriptor, similar to `EBADF` in POSIX.
+        bad-descriptor,
+        /// Device or resource busy, similar to `EBUSY` in POSIX.
+        busy,
+        /// Resource deadlock would occur, similar to `EDEADLK` in POSIX.
+        deadlock,
+        /// Storage quota exceeded, similar to `EDQUOT` in POSIX.
+        quota,
+        /// File exists, similar to `EEXIST` in POSIX.
+        exist,
+        /// File too large, similar to `EFBIG` in POSIX.
+        file-too-large,
+        /// Illegal byte sequence, similar to `EILSEQ` in POSIX.
+        illegal-byte-sequence,
+        /// Operation in progress, similar to `EINPROGRESS` in POSIX.
+        in-progress,
+        /// Interrupted function, similar to `EINTR` in POSIX.
+        interrupted,
+        /// Invalid argument, similar to `EINVAL` in POSIX.
+        invalid,
+        /// I/O error, similar to `EIO` in POSIX.
+        io,
+        /// Is a directory, similar to `EISDIR` in POSIX.
+        is-directory,
+        /// Too many levels of symbolic links, similar to `ELOOP` in POSIX.
+        loop,
+        /// Too many links, similar to `EMLINK` in POSIX.
+        too-many-links,
+        /// Message too large, similar to `EMSGSIZE` in POSIX.
+        message-size,
+        /// Filename too long, similar to `ENAMETOOLONG` in POSIX.
+        name-too-long,
+        /// No such device, similar to `ENODEV` in POSIX.
+        no-device,
+        /// No such file or directory, similar to `ENOENT` in POSIX.
+        no-entry,
+        /// No locks available, similar to `ENOLCK` in POSIX.
+        no-lock,
+        /// Not enough space, similar to `ENOMEM` in POSIX.
+        insufficient-memory,
+        /// No space left on device, similar to `ENOSPC` in POSIX.
+        insufficient-space,
+        /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX.
+        not-directory,
+        /// Directory not empty, similar to `ENOTEMPTY` in POSIX.
+        not-empty,
+        /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX.
+        not-recoverable,
+        /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX.
+        unsupported,
+        /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX.
+        no-tty,
+        /// No such device or address, similar to `ENXIO` in POSIX.
+        no-such-device,
+        /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX.
+        overflow,
+        /// Operation not permitted, similar to `EPERM` in POSIX.
+        not-permitted,
+        /// Broken pipe, similar to `EPIPE` in POSIX.
+        pipe,
+        /// Read-only file system, similar to `EROFS` in POSIX.
+        read-only,
+        /// Invalid seek, similar to `ESPIPE` in POSIX.
+        invalid-seek,
+        /// Text file busy, similar to `ETXTBSY` in POSIX.
+        text-file-busy,
+        /// Cross-device link, similar to `EXDEV` in POSIX.
+        cross-device,
+    }
+
+    /// File or memory access pattern advisory information.
+    enum advice {
+        /// The application has no advice to give on its behavior with respect
+        /// to the specified data.
+        normal,
+        /// The application expects to access the specified data sequentially
+        /// from lower offsets to higher offsets.
+        sequential,
+        /// The application expects to access the specified data in a random
+        /// order.
+        random,
+        /// The application expects to access the specified data in the near
+        /// future.
+        will-need,
+        /// The application expects that it will not access the specified data
+        /// in the near future.
+        dont-need,
+        /// The application expects to access the specified data once and then
+        /// not reuse it thereafter.
+        no-reuse,
+    }
+
+    /// A 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<input-stream, error-code>;
+
+        /// Return a stream for writing to a file, if available.
+        ///
+        /// May fail with an error-code describing why the file cannot be written.
+        ///
+        /// Note: This allows using `write-stream`, which is similar to `write` in
+        /// POSIX.
+        write-via-stream: func(
+            /// The offset within the file at which to start writing.
+            offset: filesize,
+        ) -> result<output-stream, error-code>;
+
+        /// Return a stream for appending to a file, if available.
+        ///
+        /// May fail with an error-code describing why the file cannot be appended.
+        ///
+        /// Note: This allows using `write-stream`, which is similar to `write` with
+        /// `O_APPEND` in in POSIX.
+        append-via-stream: func() -> result<output-stream, error-code>;
+
+        /// 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<descriptor-flags, error-code>;
+
+        /// Get the dynamic type of a descriptor.
+        ///
+        /// Note: This returns the same value as the `type` field of the `fd-stat`
+        /// returned by `stat`, `stat-at` and similar.
+        ///
+        /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided
+        /// by `fstat` in POSIX.
+        ///
+        /// Note: This returns the value that was the `fs_filetype` value returned
+        /// from `fdstat_get` in earlier versions of WASI.
+        get-type: func() -> result<descriptor-type, error-code>;
+
+        /// Adjust the size of an open file. If this increases the file's size, the
+        /// extra bytes are filled with zeros.
+        ///
+        /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI.
+        set-size: func(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<u8, error-code>`.
+        ///
+        /// 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<tuple<list<u8>, bool>, error-code>;
+
+        /// Write to a descriptor, without using and updating the descriptor's offset.
+        ///
+        /// It is valid to write past the end of a file; the file is extended to the
+        /// extent of the write, with bytes between the previous end and the start of
+        /// the write set to zero.
+        ///
+        /// In the future, this may change to take a `stream<u8, error-code>`.
+        ///
+        /// Note: This is similar to `pwrite` in POSIX.
+        write: func(
+            /// Data to write
+            buffer: list<u8>,
+            /// The offset within the file at which to write.
+            offset: filesize,
+        ) -> result<filesize, error-code>;
+
+        /// Read directory entries from a directory.
+        ///
+        /// On filesystems where directories contain entries referring to themselves
+        /// and their parents, often named `.` and `..` respectively, these entries
+        /// are omitted.
+        ///
+        /// This always returns a new stream which starts at the beginning of the
+        /// directory. Multiple streams may be active on the same directory, and they
+        /// do not interfere with each other.
+        read-directory: func() -> result<directory-entry-stream, error-code>;
+
+        /// Synchronize the data and metadata of a file to disk.
+        ///
+        /// This function succeeds with no effect if the file descriptor is not
+        /// opened for writing.
+        ///
+        /// Note: This is similar to `fsync` in POSIX.
+        sync: func() -> 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<descriptor-stat, error-code>;
+
+        /// Return the attributes of a file or directory.
+        ///
+        /// Note: This is similar to `fstatat` in POSIX, except that it does not
+        /// return device and inode information. See the `stat` description for a
+        /// discussion of alternatives.
+        ///
+        /// Note: This was called `path_filestat_get` in earlier versions of WASI.
+        stat-at: func(
+            /// Flags determining the method of how the path is resolved.
+            path-flags: path-flags,
+            /// The relative path of the file or directory to inspect.
+            path: string,
+        ) -> result<descriptor-stat, error-code>;
+
+        /// Adjust the timestamps of a file or directory.
+        ///
+        /// Note: This is similar to `utimensat` in POSIX.
+        ///
+        /// Note: This was called `path_filestat_set_times` in earlier versions of
+        /// WASI.
+        set-times-at: func(
+            /// 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<descriptor>,
+            /// The relative destination path at which to create the hard link.
+            new-path: string,
+        ) -> result<_, error-code>;
+
+        /// Open a file or directory.
+        ///
+        /// The returned descriptor is not guaranteed to be the lowest-numbered
+        /// descriptor not currently open/ it is randomized to prevent applications
+        /// from depending on making assumptions about indexes, since this is
+        /// error-prone in multi-threaded contexts. The returned descriptor is
+        /// guaranteed to be less than 2**31.
+        ///
+        /// If `flags` contains `descriptor-flags::mutate-directory`, and the base
+        /// descriptor doesn't have `descriptor-flags::mutate-directory` set,
+        /// `open-at` fails with `error-code::read-only`.
+        ///
+        /// If `flags` contains `write` or `mutate-directory`, or `open-flags`
+        /// contains `truncate` or `create`, and the base descriptor doesn't have
+        /// `descriptor-flags::mutate-directory` set, `open-at` fails with
+        /// `error-code::read-only`.
+        ///
+        /// Note: This is similar to `openat` in POSIX.
+        open-at: func(
+            /// Flags determining the method of how the path is resolved.
+            path-flags: path-flags,
+            /// The relative path of the object to open.
+            path: string,
+            /// The method by which to open the file.
+            open-flags: open-flags,
+            /// Flags to use for the resulting descriptor.
+            %flags: descriptor-flags,
+            /// Permissions to use when creating a new file.
+            modes: modes
+        ) -> result<descriptor, error-code>;
+
+        /// Read the contents of a symbolic link.
+        ///
+        /// If the contents contain an absolute or rooted path in the underlying
+        /// filesystem, this function fails with `error-code::not-permitted`.
+        ///
+        /// Note: This is similar to `readlinkat` in POSIX.
+        readlink-at: func(
+            /// The relative path of the symbolic link from which to read.
+            path: string,
+        ) -> result<string, error-code>;
+
+        /// Remove a directory.
+        ///
+        /// Return `error-code::not-empty` if the directory is not empty.
+        ///
+        /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX.
+        remove-directory-at: func(
+            /// 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<descriptor>,
+            /// The relative destination path to which to rename the file or directory.
+            new-path: string,
+        ) -> result<_, error-code>;
+
+        /// Create a symbolic link (also known as a "symlink").
+        ///
+        /// If `old-path` starts with `/`, the function fails with
+        /// `error-code::not-permitted`.
+        ///
+        /// Note: This is similar to `symlinkat` in POSIX.
+        symlink-at: func(
+            /// 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<descriptor>) -> bool;
+
+        /// Return a hash of the metadata associated with a filesystem object referred
+        /// to by a descriptor.
+        ///
+        /// This returns a hash of the last-modification timestamp and file size, and
+        /// may also include the inode number, device number, birth timestamp, and
+        /// other metadata fields that may change when the file is modified or
+        /// replaced. It may also include a secret value chosen by the
+        /// implementation and not otherwise exposed.
+        ///
+        /// Implementations are encourated to provide the following properties:
+        ///
+        ///  - If the file is not modified or replaced, the computed hash value should
+        ///    usually not change.
+        ///  - If the object is modified or replaced, the computed hash value should
+        ///    usually change.
+        ///  - The inputs to the hash should not be easily computable from the
+        ///    computed hash.
+        ///
+        /// However, none of these is required.
+        metadata-hash: func() -> result<metadata-hash-value, error-code>;
+
+        /// Return a hash of the metadata associated with a filesystem object referred
+        /// to by a directory descriptor and a relative path.
+        ///
+        /// This performs the same hash computation as `metadata-hash`.
+        metadata-hash-at: func(
+            /// Flags determining the method of how the path is resolved.
+            path-flags: path-flags,
+            /// The relative path of the file or directory to inspect.
+            path: string,
+        ) -> result<metadata-hash-value, error-code>;
+    }
+
+    /// A stream of directory entries.
+    resource directory-entry-stream {
+        /// Read a single directory entry from a `directory-entry-stream`.
+        read-directory-entry: func() -> result<option<directory-entry>, 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<error>) -> option<error-code>;
+}
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<request-options>
+  ) -> result<future-incoming-response, error>;
+}
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<tuple<string,list<u8>>>);
+
+    // Values off wire are not necessarily well formed, so they are given by
+    // list<u8> instead of string.
+    get: func(name: string) -> list<list<u8>>;
+
+    // Values off wire are not necessarily well formed, so they are given by
+    // list<u8> instead of string.
+    set: func(name: string, value: list<list<u8>>);
+    delete: func(name: string);
+    append: func(name: string, value: list<u8>);
+
+    // Values off wire are not necessarily well formed, so they are given by
+    // list<u8> instead of string.
+    entries: func() -> list<tuple<string,list<u8>>>;
+
+    // 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<string>;
+
+    scheme: func() -> option<scheme>;
+
+    authority: func() -> option<string>;
+
+    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<incoming-body>;
+  }
+
+  resource outgoing-request {
+    constructor(
+      method: method,
+      path-with-query: option<string>,
+      scheme: option<scheme>,
+      authority: option<string>,
+      headers: borrow<headers>
+    );
+
+    // 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<u32>,
+
+    // The timeout for receiving the first byte of the response body.
+    first-byte-timeout-ms: option<u32>,
+
+    // The timeout for receiving the next chunk of bytes in the response body
+    // stream.
+    between-bytes-timeout-ms: option<u32>
+  }
+
+  // The following block defines a special resource type used by the
+  // `wasi:http/incoming-handler` interface. When resource types are added, this
+  // block can be replaced by a proper `resource response-outparam { ... }`
+  // definition. Later, with Preview3, the need for an outparam goes away entirely
+  // (the `wasi:http/handler` interface used for both incoming and outgoing can
+  // simply return a `stream`).
+  resource response-outparam {
+    set: static func(param: response-outparam, response: result<outgoing-response, error>);
+  }
+
+  // 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<incoming-body>;
+  }
+
+  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</* child */ input-stream>;
+
+    // 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<result</* child */ trailers, error>>;
+  }
+
+  resource outgoing-response {
+    constructor(status-code: status-code, headers: borrow<headers>);
+
+    /// Will give the child outgoing-response at most once. subsequent calls will
+    /// return an error.
+    write: func() -> result<outgoing-body>;
+  }
+
+  resource outgoing-body {
+    /// Will give the child output-stream at most once. subsequent calls will
+    /// return an error.
+    write: func() -> result</* child */ output-stream>;
+
+    /// 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<trailers>);
+  }
+
+  /// The following block defines a special resource type used by the
+  /// `wasi:http/outgoing-handler` interface to emulate
+  /// `future<result<response, error>>` in advance of Preview3. Given a
+  /// `future-incoming-response`, the client can call the non-blocking `get`
+  /// method to get the result if it is available. If the result is not available,
+  /// the client can call `listen` to get a `pollable` that can be passed to
+  /// `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<result<result</* NOT a child*/ incoming-response, error>>>;
+
+    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<u32>` 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<borrow<pollable>>) -> list<u32>;
+
+    /// 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<pollable>);
+}
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<list<u8>, 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<list<u8>, 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<u64, stream-error>;
+
+        /// 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<u64, 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.
+        /// 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<u64, stream-error>;
+
+        /// 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<u8>
+        ) -> 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<u8>
+        ) -> 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<u64, 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(
+            /// The stream to read from
+            src: input-stream,
+            /// The number of bytes to splice
+            len: u64,
+        ) -> result<u64, 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, and the status of
+        /// the output stream.
+        forward: func(
+            /// The stream to read from
+            src: input-stream
+        ) -> result<u64, stream-error>;
+    }
+}
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<u8, _>`. 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<tuple<list<u8>, 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<tuple<list<u8>, 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<tuple<u64, stream-status>, 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<tuple<u64, stream-status>, 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<u8, _>`. 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<u8>
-    ) -> result<u64, stream-error>
-
-    /// 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<u8>
-    ) -> result<u64, stream-error>
-
-    /// 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<u64, stream-error>
-
-    /// 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<u64, 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(
-        this: output-stream,
-        /// The stream to read from
-        src: input-stream,
-        /// The number of bytes to splice
-        len: u64,
-    ) -> result<tuple<u64, stream-status>, 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<tuple<u64, stream-status>, 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<u64, stream-error>
-
-    /// 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<bool>` is the same length as the argument
-    /// `list<pollable>`, and indicates the readiness of each corresponding
-    /// element in that list, with true indicating ready. 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<bool>`.
-    ///
-    /// 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<pollable>) -> list<bool>
-}
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<u64, u64>;
+}
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<u8>;
+
+    /// Return an insecure pseudo-random `u64` value.
+    ///
+    /// This function returns the same type of pseudo-random data as
+    /// `get-insecure-random-bytes`, represented as a `u64`.
+    get-insecure-random-u64: func() -> u64;
+}
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<u8>;
+
+    /// 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:
+	/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html>
+	/// - <https://man7.org/linux/man-pages/man3/getaddrinfo.3.html>
+	/// - <https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfo>
+	/// - <https://man.freebsd.org/cgi/man.cgi?query=getaddrinfo&sektion=3>
+	resolve-addresses: func(network: borrow<network>, name: string, address-family: option<ip-address-family>, include-unavailable: bool) -> result<resolve-address-stream, error-code>;
+
+	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<option<ip-address>, 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<u8, u8, u8, u8>;
+	type ipv6-address = tuple<u16, u16, u16, u16, u16, u16, u16, u16>;
+
+	variant ip-address {
+		ipv4(ipv4-address),
+		ipv6(ipv6-address),
+	}
+
+	record ipv4-socket-address {
+		port: u16, // sin_port
+		address: ipv4-address, // sin_addr
+	}
+
+	record ipv6-socket-address {
+		port: u16, // sin6_port
+		flow-info: u32, // sin6_flowinfo
+		address: ipv6-address, // sin6_addr
+		scope-id: u32, // sin6_scope_id
+	}
+
+	variant ip-socket-address {
+		ipv4(ipv4-socket-address),
+		ipv6(ipv6-socket-address),
+	}
+
+}
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
+	/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html>
+	/// - <https://man7.org/linux/man-pages/man2/socket.2.html>
+	/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw>
+	/// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2>
+	create-tcp-socket: func(address-family: ip-address-family) -> result<tcp-socket, error-code>;
+}
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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>
+		/// - <https://man7.org/linux/man-pages/man2/bind.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind>
+		/// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html>
+		start-bind: func(network: borrow<network>, 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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>
+		/// - <https://man7.org/linux/man-pages/man2/connect.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>
+		/// - <https://man.freebsd.org/cgi/man.cgi?connect>
+		start-connect: func(network: borrow<network>, remote-address: ip-socket-address) -> result<_, error-code>;
+		finish-connect: func() -> result<tuple<input-stream, output-stream>, 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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html>
+		/// - <https://man7.org/linux/man-pages/man2/listen.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen>
+		/// - <https://man.freebsd.org/cgi/man.cgi?query=listen&sektion=2>
+		start-listen: func() -> 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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html>
+		/// - <https://man7.org/linux/man-pages/man2/accept.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept>
+		/// - <https://man.freebsd.org/cgi/man.cgi?query=accept&sektion=2>
+		accept: func() -> result<tuple<tcp-socket, input-stream, output-stream>, 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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html>
+		/// - <https://man7.org/linux/man-pages/man2/getsockname.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname>
+		/// - <https://man.freebsd.org/cgi/man.cgi?getsockname>
+		local-address: func() -> result<ip-socket-address, error-code>;
+
+		/// Get the remote address.
+		///
+		/// # Typical errors
+		/// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN)
+		///
+		/// # References
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html>
+		/// - <https://man7.org/linux/man-pages/man2/getpeername.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername>
+		/// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>
+		remote-address: func() -> result<ip-socket-address, error-code>;
+
+		/// 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<bool, error-code>;
+		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<bool, error-code>;
+		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<bool, error-code>;
+		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<u8, error-code>;
+		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<u64, error-code>;
+		set-receive-buffer-size: func(value: u64) -> result<_, error-code>;
+		send-buffer-size: func() -> result<u64, error-code>;
+		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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html>
+		/// - <https://man7.org/linux/man-pages/man2/shutdown.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown>
+		/// - <https://man.freebsd.org/cgi/man.cgi?query=shutdown&sektion=2>
+		shutdown: func(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:
+	/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html>
+	/// - <https://man7.org/linux/man-pages/man2/socket.2.html>
+	/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketw>
+	/// - <https://man.freebsd.org/cgi/man.cgi?query=socket&sektion=2>
+	create-udp-socket: func(address-family: ip-address-family) -> result<udp-socket, error-code>;
+}
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<u8>, // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes.
+		remote-address: ip-socket-address,
+
+		/// Possible future additions:
+		/// local-address: ip-socket-address, // IP_PKTINFO / IP_RECVDSTADDR / IPV6_PKTINFO
+		/// local-interface: u32, // IP_PKTINFO / IP_RECVIF
+		/// ttl: u8, // IP_RECVTTL
+		/// dscp: u6, // IP_RECVTOS
+		/// ecn: u2, // IP_RECVTOS
+	}
+
+
+
+	/// 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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>
+		/// - <https://man7.org/linux/man-pages/man2/bind.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind>
+		/// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html>
+		start-bind: func(network: borrow<network>, 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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>
+		/// - <https://man7.org/linux/man-pages/man2/connect.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>
+		/// - <https://man.freebsd.org/cgi/man.cgi?connect>
+		start-connect: func(network: borrow<network>, 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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html>
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html>
+		/// - <https://man7.org/linux/man-pages/man2/recv.2.html>
+		/// - <https://man7.org/linux/man-pages/man2/recvmmsg.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recv>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-recvfrom>
+		/// - <https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms741687(v=vs.85)>
+		/// - <https://man.freebsd.org/cgi/man.cgi?query=recv&sektion=2>
+		receive: func(max-results: u64) -> result<list<datagram>, 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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html>
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html>
+		/// - <https://man7.org/linux/man-pages/man2/send.2.html>
+		/// - <https://man7.org/linux/man-pages/man2/sendmmsg.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-sendto>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasendmsg>
+		/// - <https://man.freebsd.org/cgi/man.cgi?query=send&sektion=2>
+		send: func(datagrams: list<datagram>) -> result<u64, error-code>;
+
+		/// 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
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html>
+		/// - <https://man7.org/linux/man-pages/man2/getsockname.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname>
+		/// - <https://man.freebsd.org/cgi/man.cgi?getsockname>
+		local-address: func() -> result<ip-socket-address, error-code>;
+
+		/// Get the address set with `connect`.
+		///
+		/// # Typical errors
+		/// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN)
+		///
+		/// # References
+		/// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html>
+		/// - <https://man7.org/linux/man-pages/man2/getpeername.2.html>
+		/// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername>
+		/// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>
+		remote-address: func() -> result<ip-socket-address, error-code>;
+
+		/// 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<bool, error-code>;
+		set-ipv6-only: func(value: bool) -> result<_, error-code>;
+
+		/// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options.
+		unicast-hop-limit: func() -> result<u8, error-code>;
+		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<u64, error-code>;
+		set-receive-buffer-size: func(value: u64) -> result<_, error-code>;
+		send-buffer-size: func() -> result<u64, error-code>;
+		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<request-options>
-  ) -> 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/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<tuple<string,list<u8>>>) -> fields
-  fields-get: func(fields: fields, name: string) -> list<list<u8>>
-  fields-set: func(fields: fields, name: string, value: list<list<u8>>)
-  fields-delete: func(fields: fields, name: string)
-  fields-append: func(fields: fields, name: string, value: list<u8>)
-  fields-entries: func(fields: fields) -> list<tuple<string,list<u8>>>
-  fields-clone: func(fields: fields) -> fields
-
-  type headers = fields
-  type trailers = fields
-
-  /// The following block defines stream types which corresponds to the HTTP
-  /// standard Contents and Trailers. With Preview3, all of these fields can be
-  /// replaced by a stream<u8, option<trailers>>. In the interim, we need to
-  /// build on separate resource types defined by `wasi:io/streams`. The
-  /// `finish-` functions emulate the stream's result value and MUST be called
-  /// exactly once after the final read/write from/to the stream before dropping
-  /// the stream. 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<future-trailers>
-  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<result<trailers, error>>
-  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<result<_, error>>
-  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<string>
-  incoming-request-scheme: func(request: incoming-request) -> option<scheme>
-  incoming-request-authority: func(request: incoming-request) -> option<string>
-  incoming-request-headers: func(request: incoming-request) -> headers
-  incoming-request-consume: func(request: incoming-request) -> result<incoming-stream>
-  new-outgoing-request: func(
-    method: method,
-    path-with-query: option<string>,
-    scheme: option<scheme>,
-    authority: option<string>,
-    headers: headers
-  ) -> result<outgoing-request, error>
-  outgoing-request-write: func(request: outgoing-request) -> result<outgoing-stream>
-
-  /// Additional optional parameters that can be set when making a request.
-  record request-options {
-    /// The following timeouts are specific to the HTTP protocol and work
-    /// independently of the overall timeouts passed to `io.poll.poll-oneoff`.
-
-    /// The timeout for the initial connect.
-    connect-timeout-ms: option<u32>,
-
-    /// The timeout for receiving the first byte of the response body.
-    first-byte-timeout-ms: option<u32>,
-
-    /// The timeout for receiving the next chunk of bytes in the response body
-    /// stream.
-    between-bytes-timeout-ms: option<u32>
-  }
-
-  /// The following block defines a special resource type used by the
-  /// `wasi:http/incoming-handler` interface. When resource types are added, this
-  /// block can be replaced by a proper `resource response-outparam { ... }`
-  /// definition. Later, with Preview3, the need for an outparam goes away entirely
-  /// (the `wasi:http/handler` interface used for both incoming and outgoing can
-  /// simply return a `stream`).
-  type response-outparam = u32
-  drop-response-outparam: func(response: response-outparam)
-  set-response-outparam: func(param: response-outparam, response: result<outgoing-response, error>) -> result
-
-  /// This type corresponds to the HTTP standard Status Code.
-  type status-code = u16
-
-  /// The following block defines the `incoming-response` and `outgoing-response`
-  /// resource types that correspond to HTTP standard Responses. Soon, when
-  /// resource types are added, the `u32` type aliases can be replaced by proper
-  /// `resource` type definitions containing all the functions as methods. Later,
-  /// Preview2 will allow both types to be merged together into a single `response`
-  /// type (that uses the single `stream` type mentioned above). The `consume` and
-  /// `write` methods may only be called once (and return failure thereafter).
-  /// 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<incoming-stream>
-  new-outgoing-response: func(
-    status-code: status-code,
-    headers: headers
-  ) -> result<outgoing-response, error>
-  outgoing-response-write: func(response: outgoing-response) -> result<outgoing-stream>
-
-  /// The following block defines a special resource type used by the
-  /// `wasi:http/outgoing-handler` interface to emulate
-  /// `future<result<response, error>>` in advance of Preview3. Given a
-  /// `future-incoming-response`, the client can call the non-blocking `get`
-  /// method to get the result if it is available. If the result is not available,
-  /// the client can call `listen` to get a `pollable` that can be passed to
-  /// `io.poll.poll-oneoff`.
-  type future-incoming-response = u32
-  drop-future-incoming-response: func(f: future-incoming-response)
-  future-incoming-response-get: func(f: future-incoming-response) -> option<result<incoming-response, error>>
-  listen-to-future-incoming-response: func(f: future-incoming-response) -> pollable
-}
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 <matrix> <matrix>", 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<list<float64>>, b: list<list<float64>>) -> result<list<list<float64>>, 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<T: Into<OsString> + Clone, I: IntoIterator<Item = T>>(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..b83bc02 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");
@@ -176,6 +181,14 @@ pub async fn componentize(
     ))))?)
     .unpack(stdlib.path())?;
 
+    let bundled = tempfile::tempdir()?;
+
+    Archive::new(Decoder::new(Cursor::new(include_bytes!(concat!(
+        env!("OUT_DIR"),
+        "/bundled.tar.zst"
+    ))))?)
+    .unpack(bundled.path())?;
+
     let (resolve, world) = parse_wit(wit_path, world)?;
     let summary = Summary::try_new(&resolve, world)?;
     let symbols = summary.collect_symbols();
@@ -289,7 +302,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 +317,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")
@@ -316,6 +329,13 @@ pub async fn componentize(
             DirPerms::all(),
             FilePerms::all(),
             "python",
+        )
+        .preopened_dir(
+            Dir::open_ambient_dir(bundled.path(), cap_std::ambient_authority())
+                .with_context(|| format!("unable to open {}", bundled.path().display()))?,
+            DirPerms::all(),
+            FilePerms::all(),
+            "bundled",
         );
 
     for (index, path) in python_path.iter().enumerate() {
@@ -333,10 +353,10 @@ pub async fn componentize(
         .collect::<Vec<_>>()
         .join(":");
 
-    let mut table = Table::new();
+    let table = Table::new();
     let wasi = wasi
-        .env("PYTHONPATH", format!("/python:{python_path}"))
-        .build(&mut table)?;
+        .env("PYTHONPATH", format!("/python:/bundled:{python_path}"))
+        .build();
 
     let mut config = Config::new();
     config.wasm_component_model(true);
@@ -418,19 +438,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 +478,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/python.rs b/src/python.rs
index 1ed68ae..35b3bc9 100644
--- a/src/python.rs
+++ b/src/python.rs
@@ -29,13 +29,14 @@ fn python_componentize(
 
 #[pyo3::pyfunction]
 #[pyo3(name = "generate_bindings")]
-#[pyo3(signature = (wit_path, world, output_dir))]
+#[pyo3(signature = (wit_path, world, output_dir, with_typings))]
 fn python_generate_bindings(
     wit_path: PathBuf,
     world: Option<&str>,
     output_dir: PathBuf,
+    with_typings: bool,
 ) -> PyResult<()> {
-    crate::generate_bindings(&wit_path, world, &output_dir)
+    crate::generate_bindings(&wit_path, world, &output_dir, with_typings)
         .map_err(|e| PyAssertionError::new_err(format!("{e:?}")))
 }
 
diff --git a/src/summary.rs b/src/summary.rs
index 333c2a4..a6a392b 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<TypeId>, 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::<Vec<_>>()
-                .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::<Vec<_>>()
+            .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<String>,
@@ -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,16 @@ class {camel}(Flag):
                                 }
                             })
                             .map(method)
+                            // TODO: avoid potential name conflict:
+                            .chain(iter::once(
+                                "
+    def drop(self):
+        (_, func, args, _) = self.finalizer.detach()
+        self.handle = None
+        func(args[0], args[1])
+"
+                                .to_owned(),
+                            ))
                             .collect::<Vec<_>>()
                             .concat();
 
@@ -1090,14 +1099,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 +1244,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 +1252,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 +1286,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/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<tuple<string, string>>
+  get-environment: func() -> list<tuple<string, string>>;
 
   /// Get the POSIX-style arguments to the program.
-  get-arguments: func() -> list<string>
+  get-arguments: func() -> list<string>;
 
   /// Return a path that programs should use as their initial current working
   /// directory, interpreting `.` as shorthand for this.
-  initial-cwd: func() -> option<string>
+  initial-cwd: func() -> option<string>;
 }
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 {


Test patch:
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<string>) -> u32;
+  export get-strings: func() -> list<string>;
+
+  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/src/test.rs b/src/test.rs
index 84304b4..1d4122a 100644
--- a/src/test.rs
+++ b/src/test.rs
@@ -133,12 +133,11 @@ impl<H: Host> Tester<H> {
         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<H: Host> Tester<H> {
 
         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