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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ examples/http/.spin
examples/http/http.wasm
examples/http/proxy
examples/http/poll_loop.py
examples/tcp-p3/tcp.wasm
examples/tcp/tcp.wasm
examples/tcp/command
examples/tls-p3/tls.wasm
examples/cli-p3/cli.wasm
examples/cli/cli.wasm
examples/cli/command
examples/sandbox/sandbox
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/cli-p3/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from wit_world import exports

class Run(exports.Run):
async def run(self):
async def run(self) -> None:
print("Hello, world!")
45 changes: 45 additions & 0 deletions examples/tcp-p3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Example: `tcp-p3`

This is an example of how to use [componentize-py] and [Wasmtime] to build and
run a Python-based component targetting version `0.3.0-rc-2026-01-06` of the
[wasi-cli] `command` world and making an outbound TCP request using [wasi-sockets].

[componentize-py]: https://github.com/bytecodealliance/componentize-py
[Wasmtime]: https://github.com/bytecodealliance/wasmtime
[wasi-cli]: https://github.com/WebAssembly/WASI/tree/v0.3.0-rc-2026-01-06/proposals/cli/wit-0.3.0-draft
[wasi-sockets]: https://github.com/WebAssembly/WASI/tree/v0.3.0-rc-2026-01-06/proposals/sockets/wit-0.3.0-draft

## Prerequisites

* `Wasmtime` 41.0.3
* `componentize-py` 0.21.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/v41.0.3.

```
cargo install --version 41.0.3 wasmtime-cli
pip install componentize-py==0.21.0
```

## Running the demo

First, in a separate terminal, run `netcat`, telling it to listen for incoming
TCP connections. You can choose any port you like.

```
nc -l 127.0.0.1 3456
```

Now, build and run the example, using the same port you gave to `netcat`.

```
componentize-py -d ../../wit -w wasi:cli/command@0.3.0-rc-2026-01-06 componentize app -o tcp.wasm
wasmtime run -Sp3 -Sinherit-network -Wcomponent-model-async tcp.wasm 127.0.0.1:3456
```

The program will open a TCP connection, send a message, and wait to receive a
response before exiting. You can give it a response by typing anything you like
into the terminal where `netcat` is running and then pressing the `Enter` key on
your keyboard.
81 changes: 81 additions & 0 deletions examples/tcp-p3/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import sys
import asyncio
import ipaddress
from ipaddress import IPv4Address, IPv6Address
import wit_world
from wit_world import exports
from wit_world.imports.wasi_sockets_types import (
TcpSocket,
IpSocketAddress_Ipv4,
IpSocketAddress_Ipv6,
Ipv4SocketAddress,
Ipv6SocketAddress,
IpAddressFamily,
)
from typing import Tuple


IPAddress = IPv4Address | IPv6Address

class Run(exports.Run):
async def run(self) -> None:
args = sys.argv[1:]
if len(args) != 1:
print("usage: tcp-p3 <address>:<port>", file=sys.stderr)
exit(-1)

address, port = parse_address_and_port(args[0])
await send_and_receive(address, port)


def parse_address_and_port(address_and_port: str) -> Tuple[IPAddress, int]:
ip, separator, port = address_and_port.rpartition(":")
assert separator
return (ipaddress.ip_address(ip.strip("[]")), int(port))


def make_socket_address(address: IPAddress, port: int) -> IpSocketAddress_Ipv4 | IpSocketAddress_Ipv6:
if isinstance(address, IPv4Address):
octets = address.packed
return IpSocketAddress_Ipv4(Ipv4SocketAddress(
port=port,
address=(octets[0], octets[1], octets[2], octets[3]),
))
else:
b = address.packed
return IpSocketAddress_Ipv6(Ipv6SocketAddress(
port=port,
flow_info=0,
address=(
(b[0] << 8) | b[1],
(b[2] << 8) | b[3],
(b[4] << 8) | b[5],
(b[6] << 8) | b[7],
(b[8] << 8) | b[9],
(b[10] << 8) | b[11],
(b[12] << 8) | b[13],
(b[14] << 8) | b[15],
),
scope_id=0,
))


async def send_and_receive(address: IPAddress, port: int) -> None:
family = IpAddressFamily.IPV4 if isinstance(address, IPv4Address) else IpAddressFamily.IPV6

sock = TcpSocket.create(family)

await sock.connect(make_socket_address(address, port))

send_tx, send_rx = wit_world.byte_stream()
async def write() -> None:
with send_tx:
await send_tx.write_all(b"hello, world!")
await asyncio.gather(sock.send(send_rx), write())

recv_rx, recv_fut = sock.receive()
async def read() -> None:
with recv_rx:
data = await recv_rx.read(1024)
print(f"received: {str(data)}")
await asyncio.gather(recv_fut.read(), read())
38 changes: 38 additions & 0 deletions examples/tls-p3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Example: `tls-p3`

This is an example of how to use [componentize-py] and [Wasmtime] to build and
run a Python-based TLS client component targetting version `0.3.0-rc-2026-02-09`
of the [wasi-cli] `command` world with [wasi-tls] and [wasi-sockets] support.

[componentize-py]: https://github.com/bytecodealliance/componentize-py
[Wasmtime]: https://github.com/bytecodealliance/wasmtime
[wasi-cli]: https://github.com/WebAssembly/WASI/tree/v0.3.0-rc-2026-02-09/proposals/cli/wit-0.3.0-draft
[wasi-sockets]: https://github.com/WebAssembly/WASI/tree/v0.3.0-rc-2026-02-09/proposals/sockets/wit-0.3.0-draft
[wasi-tls]: https://github.com/WebAssembly/wasi-tls

## Prerequisites

* `Wasmtime` 43.0.0
* `componentize-py` 0.21.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/v43.0.0.

```
cargo install --version 43.0.0 wasmtime-cli
pip install componentize-py==0.21.0
```

## Running the demo

```
componentize-py -d ../../wit -w tls-p3 componentize app -o tls.wasm
wasmtime run -Sp3,inherit-network,tls,allow-ip-name-lookup -Wcomponent-model-async tls.wasm <server_name>
```

For example, to connect to `api.github.com` over TLS:

```
wasmtime run -Sp3,inherit-network,tls,allow-ip-name-lookup -Wcomponent-model-async tls.wasm api.github.com
```
76 changes: 76 additions & 0 deletions examples/tls-p3/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import sys
import asyncio
import wit_world
from wit_world import exports
from wit_world.imports.wasi_sockets_types import (
TcpSocket,
IpSocketAddress_Ipv4,
IpSocketAddress_Ipv6,
Ipv4SocketAddress,
Ipv6SocketAddress,
IpAddressFamily,
IpAddress_Ipv4,
IpAddress_Ipv6,
)
from wit_world.imports.ip_name_lookup import resolve_addresses
from wit_world.imports.client import Connector


class Run(exports.Run):
async def run(self) -> None:
args = sys.argv[1:]
if len(args) != 1:
print("usage: tls-p3 <server_name>", file=sys.stderr)
exit(-1)

server_name = args[0]
await send_and_receive(server_name)


async def send_and_receive(server_name: str) -> None:
port = 443
addresses = await resolve_addresses(server_name)
address = addresses[0]

if isinstance(address, IpAddress_Ipv4):
family = IpAddressFamily.IPV4
sock_addr: IpSocketAddress_Ipv4 | IpSocketAddress_Ipv6 = IpSocketAddress_Ipv4(
Ipv4SocketAddress(port=port, address=address.value)
)
else:
family = IpAddressFamily.IPV6
sock_addr = IpSocketAddress_Ipv6(
Ipv6SocketAddress(port=port, flow_info=0, address=address.value, scope_id=0)
)

sock = TcpSocket.create(family)
await sock.connect(sock_addr)

tls = Connector()

data_send_tx, data_send_rx = wit_world.byte_stream()
tls_send_rx, tls_send_fut = tls.send(data_send_rx)
sock_send_fut = sock.send(tls_send_rx)

tls_recv_rx, sock_recv_fut = sock.receive()
data_recv_rx, tls_recv_fut = tls.receive(tls_recv_rx)

async def write() -> None:
with data_send_tx:
await data_send_tx.write_all(f"GET / HTTP/1.1\r\nHost: {server_name}\r\nUser-Agent: wasmtime-wasi-rust\r\nConnection: close\r\n\r\n".encode())

async def read() -> None:
with data_recv_rx:
while not data_recv_rx.writer_dropped:
buf = await data_recv_rx.read(1024)
sys.stdout.buffer.write(buf)

await asyncio.gather(
Connector.connect(tls, server_name),
write(),
read(),
sock_send_fut.read(),
sock_recv_fut.read(),
tls_send_fut.read(),
tls_recv_fut.read(),
)
59 changes: 59 additions & 0 deletions tests/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,27 @@ fn lint_cli_bindings() -> anyhow::Result<()> {
Ok(())
}

#[test]
fn lint_cli_p3_bindings() -> anyhow::Result<()> {
let dir = tempfile::tempdir()?;
fs_extra::copy_items(
&["./examples/cli-p3", "./wit"],
dir.path(),
&CopyOptions::new(),
)?;
let path = dir.path().join("cli-p3");

generate_bindings(&path, "wasi:cli/command@0.3.0-rc-2026-01-06")?;

assert!(predicate::path::is_dir().eval(&path.join("wit_world")));

_ = dir.keep();

mypy_check(&path, ["--strict", "-m", "app"]);

Ok(())
}

#[test]
fn lint_http_bindings() -> anyhow::Result<()> {
let dir = tempfile::tempdir()?;
Expand Down Expand Up @@ -150,6 +171,44 @@ fn lint_tcp_bindings() -> anyhow::Result<()> {
Ok(())
}

#[test]
fn lint_tcp_p3_bindings() -> anyhow::Result<()> {
let dir = tempfile::tempdir()?;
fs_extra::copy_items(
&["./examples/tcp-p3", "./wit"],
dir.path(),
&CopyOptions::new(),
)?;
let path = dir.path().join("tcp-p3");

generate_bindings(&path, "wasi:cli/command@0.3.0-rc-2026-01-06")?;

assert!(predicate::path::is_dir().eval(&path.join("wit_world")));

mypy_check(&path, ["--strict", "-m", "app"]);

Ok(())
}

#[test]
fn lint_tls_p3_bindings() -> anyhow::Result<()> {
let dir = tempfile::tempdir()?;
fs_extra::copy_items(
&["./examples/tls-p3", "./wit"],
dir.path(),
&CopyOptions::new(),
)?;
let path = dir.path().join("tls-p3");

generate_bindings(&path, "tls-p3")?;

assert!(predicate::path::is_dir().eval(&path.join("wit_world")));

mypy_check(&path, ["--strict", "-m", "app"]);

Ok(())
}

fn generate_bindings(path: &Path, world: &str) -> Result<Assert, anyhow::Error> {
Ok(cargo::cargo_bin_cmd!("componentize-py")
.current_dir(path)
Expand Down
Loading
Loading