-
Notifications
You must be signed in to change notification settings - Fork 41
add tcp-p3 example and test
#204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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. | ||
| 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) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By some reason, this does not block on empty buffers - I assumed that this would only ever return a non-zero buffer or an error. For example, if The component receives There is no way to send data to the component via I am assuming this is a bug in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the spec (https://github.com/WebAssembly/component-model/blob/c7176a512c0bbe4654849f4ba221c1a71c7cf514/design/mvp/Concurrency.md#stream-readiness):
|
||
| print(f"received: {str(data)}") | ||
| await asyncio.gather(recv_fut.read(), read()) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not actually happen, but I'm assuming that's a temporary bug