WasmtimeからHTTP通信をする手解き

- 限界開発鯖 Advent Calender 2022 ~~6 日目~~ **32 日目** です (年末に投稿したかったのに… **あ, 明けましておめでとうございます**)
- 今話題沸騰中の WASI を Rust からコンパイルして Wasmtime 上から HTTP で echo をするまでの記録です
- Rust のことについて, 特に断りは入れません

## 前置き

[WASI, The **W**eb**A**ssembly **S**ystem **I**nterface](https://wasi.dev)という ~~変態的~~ 革新的技術をご存知でしょうか. めちゃめちゃざっくり言うと, WASM の親戚みたいな感じで, 尚且つ syscall とかが絡むようなこともできちゃうっていうか **syscall ができる (多分)** すごーいやつです.

https://wasi.dev

で, 今回お世話になる WASI の実行環境, ランタイムは [Wasmtime](https://wasmtime.dev) というやつです. なんか [Lucet](https://github.com/bytecodealliance/lucet) ってやつが有ったり無かったりしたとか, あと [WasmEdge](https://wasmedge.org) とかいうくそ速そうなやつや, [Wasmer](https://wasmer.io) っていうエコシステム呼ばわりされているものがあったりしますが, 今回は割愛, ソースは [ここ](https://zenn.dev/zaki_yama/scraps/cd40f7535b3224). でもそもそも Wasmtime 以外今回の要件を満たしていないのかな？なんて気もしている. ちなみに Wasmtime の実装は Rust です, 嬉しい！ (ｷｬｯｷｬ)

https://wasmtime.dev

まあそんな革新的技術の ~~闇~~ 実態に触れつつ, クロスプラットフォームな HTTP サーバーの WASI バイナリを吐かせよう！という試みについての **記録** です. 何か明確な方向性が有るわけでは御座いませんので悪しからず.

## 環境

```console:zsh
> uname -snrmo
Linux archlinux 5.15.85-1-lts x86_64 GNU/Linux
```

```console:zsh
> rustup -V
rustup 1.25.1 (2022-11-01)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.68.0-nightly (d0dc9efff 2022-12-18)`
```

```toml:~/.cargo/config.toml より抜粋
[target.'cfg(all())']
rustflags = ["-C", "target-cpu=native", "-Z", "share-generics=y"]
```

もしお手元で試す時に `nightly` が必要そうだったら適位導入してください. 多分 `stable` でも大丈夫です.

## 導入

まず主役となる Wasmtime を導入します. Archlinux を使っているので, これは Package Manager (pacman) で一発です (うれしい).

```console:zsh
> sudo pacman -S wasmtime
resolving dependencies...
looking for conflicting packages...

Package (1)         New Version  Net Change

community/wasmtime  4.0.0-1       21.66 MiB

Total Installed Size:  21.66 MiB

...
```

ダウンロード時のことですが, 圧縮時のパッケージサイズ もとい `*.pkg.tar.zst` のファイルサイズは **4.2MiB** とのことで, けっこう小さいじゃーんという気持ち

次に, 今回の作業場所を作成します.

```console:zsh
> cargo new http-wasi-rs
     Created binary (application) `http-wasi-rs` package

> cd http-wasi-rs
```

あとは ~~思想を反映~~ 下準備をして…

```toml:Cargo.toml
[package]
name = "http-wasi-rs"
version = "0.0.0"
edition = "2021"
```

```rs:src/main.rs
fn main() {
    println!("hi, from rust on wasmtime!");
}
```

そしたら, バイナリを作成！

```console:zsh
...
'-prefetchwt1' is not a recognized feature for this target (ignoring feature)
'+cmov' is not a recognized feature for this target (ignoring feature)
'-avx512vbmi' is not a recognized feature for this target (ignoring feature)
'-shstk' is not a recognized feature for this target (ignoring feature)
'+movbe' is not a recognized feature for this target (ignoring feature)
'-avx512vp2intersect' is not a recognized feature for this target (ignoring feature)
'+xsaveopt' is not a recognized feature for this target (ignoring feature)
'-avx512dq' is not a recognized feature for this target (ignoring feature)
'+sse2' is not a recognized feature for this target (ignoring feature)
'+adx' is not a recognized feature for this target (ignoring feature)
'+sse3' is not a recognized feature for this target (ignoring feature)
'skylake' is not a recognized processor for this target (ignoring processor)
'skylake' is not a recognized processor for this target (ignoring processor)
'skylake' is not a recognized processor for this target (ignoring processor)
'skylake' is not a recognized processor for this target (ignoring processor)
'skylake' is not a recognized processor for this target (ignoring processor)
    Finished dev [unoptimized + debuginfo] target(s) in 0.48s
```

**え？うるさ. なんなんこれ.** と思いきや, 前述の `-C target-cpu=native` が影響している模様. `.cargo/config.toml` で修正しようにもなんか上手く行かなかったので, 以下のように修正.

```toml:~/.cargo/config.toml より抜粋
[target.'cfg(all())']
rustflags = ["-Z", "share-generics=y"]
```

<details>
  <summary>
    【余談】<code>rustflags</code> の適用順について
  </summary>

補足情報として,

```console:zsh
> rustc --target wasm32-wasi --print target-cpus
Available CPUs for this target:
    bleeding-edge
    generic
    mvp
```

```console:zsh
> rustc --target x86_64-unknown-linux-gnu --print target-cpus
Available CPUs for this target:
    native         - Select the CPU of the current host (currently skylake).
    alderlake
    amdfam10
    athlon
...
```

ですから `wasm32-wasi` に `target-cpu=native` なんて指定は意味が無いはずなのですが, x86_64 と同様に色々解釈されてしまっているようなのですよね.

先に提示したくっそうるさい出力の全容なのですが,

```
'<FEATURE/PROCESSOR>' is not a recognized {feature,processor} for this target (ignoring {feature,processor})
```

なんか出力がバグってそう (引用符の位置や個数がおかしかったり, 括弧書きだけ改行されたり複数回出力されたり…) なのでこの限りではありませんが, おおよそこんな感じ.

で, `~/.cargo/config.toml` で `[target.*]` を全部コメントアウト, 消してみて `.cargo/config.toml` をいじくりまわしてみた. (多分適応順には変わりがない)

```toml:.cargo/config.toml
[target.'cfg(all())']
rustflags = ["-C", "target-cpu=before"]

[target.wasm32-wasi]
rustflags = ["-C", "target-cpu=after"]
```

すると,

```console:zsh
> cargo build -vv --target wasm32-wasi
   Compiling http-wasi-rs v0.0.0 ($HOME/http-wasi-rs)
     Running `... -C target-cpu=after -C target-cpu=before -Z share-generics=y`
'before' is not a recognized processor for this target (ignoring processor)
'before' is not a recognized processor for this target (ignoring processor)
'before' is not a recognized processor for this target (ignoring processor)
'before' is not a recognized processor for this target (ignoring processor)
'before' is not a recognized processor for this target (ignoring processor)
...
    Finished dev [unoptimized + debuginfo] target(s) in 0.19s
```

でじゃあこれ toml 上での指定順に関わりあるんか？ってことで `[target.wasm32-wasi]` を上にもってきたけど, 変わらず.

じゃあ公式の docs はどうなってんだっていうと, 該当するのがここ.

[Configuration - The Cargo Book #build.rustflags](https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags)

> There are four mutually exclusive sources of extra flags. They are checked in order, with the first one being used:
>
> 1. `CARGO_ENCODED_RUSTFLAGS` environment variable.
> 2. `RUSTFLAGS` environment variable.
> 3. **All matching `target.<triple>.rustflags` and `target.<cfg>.rustflags` config entries joined together.**
> 4. `build.rustflags` config value.
>
> Additional flags may also be passed with the cargo rustc command.

多分だけど, 厳密には結合順が `target.<triple>.rustflags` + `target.<cfg>.rustflags` ってことなんだろうな, と憶測. なのでもうどうしようもないので, `-C target-cpu=native` はいつもお使いの `x86_64-unknown-linux-gnu` へ指定しときました.

てなわけで, 余談でした.

---

</details>

```console:zsh
> cargo build --target wasm32-wasi
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
```

ふう, すっきりだぜ. そしたら, さっさと実行してみる.

```console:zsh
> wasmtime target/wasm32-wasi/debug/http-wasi-rs.wasm
hi, from rust on wasmtime!
```

phew, 良い眺めだ.

## using `TcpListener` of `std`

本題の HTTP 通信をする部分ですが, ちょっとこれが良くわからない.
詳しくは書きませんが, いつものように `tokio` + `warp` で構築しようと思ったら, `tokio` は WASM 向けでは [一部の features しか対応していない](https://docs.rs/tokio/latest/tokio/#wasm-support) ことが原因で build が通らない. じゃあ `tokio` + `hyper` は？というと, サーバーを立てるのに必須な `hyper/tcp` の build が同様の理由で通らない.

というわけで, 取り敢えず接続の待ち受けまでは自分で書いてみることにした.
そして実装がこちら, 参考は [これ](https://www.arnevogel.com/wasm-hosted/).

```rs:src/main.rs
use std::net::*;
use std::os::fd::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = unsafe { TcpListener::from_raw_fd(4) };
    listener.set_nonblocking(true)?;

    for result in listener.incoming() {
        match result {
            Ok(_) => {},
            Err(error) => {
                eprintln!("error: {error}");
            },
        }
    }

    Ok(())
}
```

とりあえず実行しようってことで具体的な処理については書いていない. で実行.

```console:zsh
> cargo build --target wasm32-wasi && wasmtime run --tcplisten 127.0.0.1:4000 target/wasm32-wasi/debug/http-wasi-rs.wasm
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Error: Os { code: 8, kind: Uncategorized, message: "Bad file descriptor" }
```

ああ, そうですか… って**ちょいちょいちょい, 困ります.**

幸いなことに commit へのリンクが [貼ってあった](https://github.com/bytecodealliance/wasmtime/commit/853a025613e012c6c29002ddcccfced67073a8d0) りしたが, 正直参考にならなかったのでさっさと Issue を探すことに. で見つけた (PR だけど).

[Add CLI options for preopened listen sockets by haraldh · Pull Request #3729 · bytecodealliance/wasmtime](https://github.com/bytecodealliance/wasmtime/pull/3729)

どうやら `--env 'LISTEN_FDS=1'` を指定してやれば良いのかな？

```console:zsh
> wasmtime run --tcplisten 127.0.0.1:4000 --env 'LISTEN_FDS=1' target/wasm32-wasi/debug/http-wasi-rs.wasm
Error: Os { code: 8, kind: Uncategorized, message: "Bad file descriptor" }
```

だめらしい.
ので PR 主が提示している [example](https://github.com/haraldh/mio/blob/wasi/examples/tcp_server.rs) を見てみることに.

```rs:github.com/haraldh/mio tree/wasi examples/tcp_server.rs#L32 fixed
let stdlistener = unsafe { std::net::TcpListener::from_raw_fd(3) };
```

えぇ (fd 違うじゃん)

```console:zsh
> wasmtime run --tcplisten 127.0.0.1:4000 --env 'LISTEN_FDS=1' target/wasm32-wasi/debug/http-wasi-rs.wasm
...
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
...
```

えぇ… (処理を書き間違えた)

```console:zsh
> wasmtime run --tcplisten 127.0.0.1:4000 target/wasm32-wasi/debug/http-wasi-rs.wasm
...
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
error: Resource temporarily unavailable (os error 6)
...
```

えぇ…… (しかも環境変数要らないじゃん)

なんか動きそうだし, さっさと本題の処理も書いてしまおう.

```rs:src/main.rs
use std::net::*;
use std::os::fd::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = unsafe { TcpListener::from_raw_fd(3) };
    listener.set_nonblocking(true)?;

    for mut stream in listener.incoming().flatten() {
        use std::io::*;

        let mut buffer = Vec::new();
        let mut before = '\0';
        let mut len = None;
        for c in Stream(&mut stream).filter(|c| *c != '\r') {
            print!("{c}");

            if c == '\n' {
                let str = core::str::from_utf8(buffer.as_slice())?;
                if let Some(str) = str.strip_prefix("Content-Length: ") {
                    len = Some(str.parse::<usize>()?);
                }

                if before == '\n' {
                    break;
                }

                before = c;
                buffer.clear();
            } else {
                before = c;
                buffer.push(c as u8);
            }
        }

        let Some(len) = len else {
            stream.shutdown(Shutdown::Both)?;
            continue;
        };

        let mut content = Vec::new();
        content.resize(len, 0);
        stream.read_exact(content.as_mut_slice())?;
        let content = core::str::from_utf8(content.as_slice())?;

        let send = String::new()
            + "HTTP/1.1 200 OK"
            + "\n"
            + "Content-Type: text/plain"
            + "\n"
            + &format!("Content-Length: {}", content.len())
            + "\n"
            + "\n"
            + content;

        stream.write_all(send.as_bytes())?;

        stream.shutdown(Shutdown::Both)?;
    }

    Ok(())
}

struct Stream<'a>(&'a mut TcpStream);
impl Iterator for Stream<'_> {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        use std::io::Read;

        let mut bytes = [0; 1];
        let result = self.0.read_exact(&mut bytes);

        if let Ok(()) = result {
            Some(bytes[0] as char)
        } else {
            None
        }
    }
}
```

**_唐 突 な く そ で か 実 装_**
はい. (だってこうするしかなかったんだもん！)

解説すると:

- ヘッダから 1 文字ずつ読んでいる
  - `Read::read_to_end` なんてしようもんなら `EOF` が来ないが為にブロックしてしまう
  - `Read::set_read_timeout` は使えないので, 無いバイトを読んだ時点でブロックする
  - `Stream` という構造体, イテレータを実装し, バイト単位で読み出し易いように
  - `\r` は処理するの面倒なので無視している (`\n` だけでいいじゃん)
  - `Content-Length` だけパースしている
    - 他のヘッダは不必要なので無視している
- `Content-Length` 分だけ末尾を読み出す
- 最小限のヘッダと共に返答

Rust の Iterator の恩恵はこういうところに出るのだ…素晴らしい… (惚れ惚れ)

実行してみる.

```console:zsh (server)
> cargo build --target wasm32-wasi && wasmtime run --tcplisten 127.0.0.1:4000 target/wasm32-wasi/debug/http-wasi-rs.wasm
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
POST / HTTP/1.1
Host: 127.0.0.1:4000
User-Agent: curl/7.87.0
Accept: */*
Content-Type: text/plain
Content-Length: 4

^C
```

```console:zsh (client)
> curl -X POST -H 'Content-Type: text/plain' -d 'echo' 127.0.0.1:4000
echo%
```

ちゃんと返ってきてる.

fd の謎については, 多分 `std{in,out,err}` の次に開いてるから…だと思うけど, どうなんだろう. もしそうだったらかなり不安定な実装だと思うのだが…
あと, [`--dir` オプションが兼ね合いでこわれる](https://github.com/bytecodealliance/wasmtime/issues/3936) とかなんとかってのもあるので, 多分この辺は不安定なんでしょう. 取り敢えずそういうことにしときます.

## using `TcpListener` of `tokio`

生の TCP 接続とか扱ってたら埒が明かないので, 取り敢えず `tokio` が使えることを確かめたい. さっきの PR で提示されてた [example](https://github.com/haraldh/mio/blob/wasi/examples/tcp_server.rs) は `mio` の fork だったし, 多分動くってことなんだろう.
というわけで依存を追加. [不安定と云われている](https://docs.rs/tokio/latest/tokio/#unstable-wasm-support) flag も追加. バグを恐れずに行け？

```toml:.cargo/config.toml
[target.wasm32-wasi]
rustflags = ["--cfg", "tokio_unstable"]
```

~~この指定の仕方, "解ってね" ってメッセージを感じるけど, ~~英弱なので分からなかった~~ どこにも書いてなかったので, [`tokio-rs/tokio` で .toml 内の "unstable" というキーワードを探した所](https://github.com/search?q=repo%3Atokio-rs%2Ftokio+unstable+language%3ATOML&type=code&l=TOML), `netlify.toml` に記述があるのを発見しましたが, 正攻法じゃないかもしれない.~~

↑ これは少し前まで書き置いていた文章.
実際には [ここ](https://docs.rs/tokio/1.23.0/tokio/index.html#unstable-features) に堂々と書いてありました. はい.

```console:zsh
> cargo add tokio -F tokio/full
    Updating crates.io index
      Adding tokio v1.23.0 to dependencies.
             Features:
             + bytes
             + fs
             + full
             + io-std
             + io-util
             + libc
             + macros
             + memchr
             + net
             + num_cpus
             + parking_lot
             + process
             + rt
             + rt-multi-thread
             + signal
             + signal-hook-registry
             + socket2
             + sync
             + time
             + tokio-macros
             - mio
             - stats
             - test-util
             - tracing
             - windows-sys
```

良い眺めだ.

```rs:src/main.rs#L1~L5
use std::net::*;
use std::os::fd::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
```

早速非同期の悦びを…

```
error[E0599]: no function or associated item named `new_multi_thread` found for struct `Builder` in the current scope
  --> src/main.rs:61:5
   |
61 |     Ok(())
   |     ^^
   |     |
   |     function or associated item not found in `Builder`
   |     help: there is an associated function with a similar name: `new_current_thread`
```

…？？？？？ ああ, `new_multi_thread` にはどう足掻いても対応してないのね. じゃあ…

```rs:src/main.rs#L4
#[tokio::main(flavor = "current_thread")]
```

あ, 通った通った. これなら使えるのね.
じゃあお待ちかね, TCP 接続待ち受けの非同期化と洒落込みましょうか.
但し `tokio::net::TcpListener` は `FromRawFd` じゃないので, `std::net::TcpListener` を構築した上で `tokio::net::TcpListener::from_std` を介して構築します.
で, 最初は ~~イキって~~ 折角ならと `core::async_iter::AsyncIterator` を `Stream` に実装してやろうかと思ったのですが, 極めて残念なことにこいつがまだ [実用化されてない](https://rust-lang.github.io/rfcs/2996-async-iterator.html#next-method) 様子だったので, Iterator の恩恵は受けられなくなりましたとさ (元からそんなに受けてないけど).

```rs:src/main.rs
use std::os::fd::*;

use tokio::net::*;

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = unsafe { std::net::TcpListener::from_raw_fd(3) };
    listener.set_nonblocking(true)?;

    let listener = TcpListener::from_std(listener)?;

    while let Ok((mut stream, _)) = listener.accept().await {
        use tokio::io::*;

        let mut buffer = Vec::new();
        let mut before = '\0';
        let mut len = None;
        while let Ok(c) = stream.read_u8().await.map(|b| b as char) {
            if c == '\r' {
                continue;
            }

            print!("{c}");

            if c == '\n' {
                let str = core::str::from_utf8(buffer.as_slice())?;
                if let Some(str) = str.strip_prefix("Content-Length: ") {
                    len = Some(str.parse::<usize>()?);
                }

                if before == '\n' {
                    break;
                }

                before = c;
                buffer.clear();
            } else {
                before = c;
                buffer.push(c as u8);
            }
        }

        let Some(len) = len else {
            stream.shutdown().await?;
            continue;
        };

        let mut content = Vec::new();
        content.resize(len, 0);
        stream.read_exact(content.as_mut_slice()).await?;
        let content = core::str::from_utf8(content.as_slice())?;

        let send = String::new()
            + "HTTP/1.1 200 OK"
            + "\n"
            + "Content-Type: text/plain"
            + "\n"
            + &format!("Content-Length: {}", content.len())
            + "\n"
            + "\n"
            + content;

        stream.write_all(send.as_bytes()).await?;

        stream.shutdown().await?;
    }

    Ok(())
}
```

一部の関数呼び出しが `tokio` 流に変更されているのと, 随所に `.await` が付いている以外は何も処理を変えてません. あとは前述の `Stream` 構造体が消えたので `for _ in ...` の代替として,`AsyncReadExt::read_u8` を `while let Ok(_) = ...` で受けて使うようにしています.

で動作確認.

```console:zsh (server)
> cargo build --target wasm32-wasi && wasmtime run --tcplisten 127.0.0.1:4000 target/wasm32-wasi/debug/http-wasi-rs.wasm
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
POST / HTTP/1.1
Host: 127.0.0.1:4000
User-Agent: curl/7.87.0
Accept: */*
Content-Type: text/plain
Content-Length: 4

^C
```

```console:zsh (client)
> curl -X POST -H 'Content-Type: text/plain' -d 'echo' 127.0.0.1:4000
echo%
```

代わり映えしないけど, ちゃんと動いていることは確認.

## using `Connection` of `hyper`

やっとこさ本題. 非同期になろうとも TCP 接続を直に触ってたら埒が明かないので, さっさと既成ライブラリをぶち込む！使うのは先述した `hyper` です. 前述の `warp` を使おうと思いましたが, 多分 `socket2` に依存してると WASM 向けに build できないっぽい. Issue も [立ってる](https://github.com/rust-lang/socket2/issues/268). ので `hyper/tcp` も使えない.

ただ, それは v0 までの話のようで, v1 (投稿現在は Release Candicate) では `Server` 構造体が削除されているっぽい関係からも `socket2` が不必要になっています. なので, `hyper` のお作法に則り, 先の実装を書き換えてみることにします. まず依存を追加.

```console:zsh
> cargo add hyper -F hyper/server -F hyper/http1
    Updating crates.io index
      Adding hyper v0.14.23 to dependencies.
             Features:
             + http1
             + server
             - __internal_happy_eyeballs_tests
             - client
             - ffi
             - full
             - h2
             - http2
             - libc
             - nightly
             - runtime
             - socket2
             - stream
             - tcp

> cargo add hyper@1.0.0-rc.2
    Updating crates.io index
      Adding hyper v1.0.0-rc.2 to dependencies.
             Features:
             + http1
             + server
             - client
             - ffi
             - full
             - h2
             - http-body-util
             - http2
             - libc
             - nightly
             - socket2
```

で処理を書き換え.

```rs:src/main.rs
use std::convert::Infallible;
use std::os::fd::*;

use hyper::http::Response;
use hyper::server::conn::http1::Builder;
use hyper::service::service_fn;

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = unsafe { std::net::TcpListener::from_raw_fd(3) };
    listener.set_nonblocking(true)?;

    let listener = tokio::net::TcpListener::from_std(listener)?;

    let service = service_fn(|req| async {
        let body = req.into_body();
        let res = Response::new(body);
        Ok::<_, Infallible>(res)
    });

    while let Ok((stream, _)) = listener.accept().await {
        let task = Builder::new().serve_connection(stream, service);
        tokio::spawn(task);
    }

    Ok(())
}
```

そしたら実行.

```console:zsh (server)
cargo build --target wasm32-wasi && wasmtime run --tcplisten localhost:4000 target/wasm32-wasi/debug/http-wasi-rs.wasm
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
^C
```

```console:zsh (client)
> curl -X POST -H 'Content-Type: text/plain' -d 'echo' localhost:4000
echo%
```

うむ. 出力は無いが, きちんと動作している. `hyper/http2` を有効にすれば HTTP/2 も使える (但し取り持ちについては実装しなければならない).

## using `...`

結論から言うと, **今回の記事はこれでおしまいです**. 詳しくはこれから書きます.
あとこれが原因で'22 年の年末投稿に間に合いませんでした. 検証したり情報整理したりは年末ではつらすぎたね…

さて, TCP 接続を直接触ったり, 接続を処理させたりしたものの, 実用の範囲ではない. ルーティングやメソッドで処理を振り分けってのはやっぱ最低限欲しいですよね.
というわけでまず `warp` を試してみました. **が**, これがなんと内部依存の `socket2` に阻まれ build が通らない. `default-features = false` でも勿論だめ.
`hyper` の話でも出てきましたが, では実際にはどのような原因で build が落ちているのか？

```:rustc
error[E0583]: file not found for module `sys`
   --> $HOME/.cargo/registry/src/github.com-1ecc6299db9ec823/socket2-0.4.7/src/lib.rs:124:1
    |
124 | mod sys;
    | ^^^^^^^^
    |
    = help: to create the module `sys`, create file "$HOME/.cargo/registry/src/github.com-1ecc6299db9ec823/socket2-0.4.7/src/sys.rs" or "$HOME/.cargo/registry/src/github.com-1ecc6299db9ec823/socket2-0.4.7/src/sys/mod.rs"
```

このエラーが根本なのですが, まあ別にこれだけというわけではなくて,

```:rustc
error: Socket2 doesn't support the compile target
   --> $HOME/.cargo/registry/src/github.com-1ecc6299db9ec823/socket2-0.4.7/src/lib.rs:127:1
    |
127 | compile_error!("Socket2 doesn't support the compile target");
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

はい. **`socket2` は WASI に対応してないんだってさ**. なんなら README にも書いてある.

> from https://github.com/rust-lang/socket2#os-support
>
> ### Tier 1
>
> These OSs are tested with each commit in the CI and must always pass the tests.
> All functions/types/etc., excluding ones behind the `all` feature, must work on
> these OSs.
>
> - Linux
> - macOS
> - Windows
>
> ### Tier 2
>
> These OSs are currently build in the CI, but not tested. Not all
> functions/types/etc. may work on these OSs, even ones **not** behind the `all`
> feature flag.
>
> - Android
> - FreeBSD
> - Fuchsia
> - iOS
> - illumos
> - NetBSD
> - Redox
> - Solaris

**WASI** の姿がないですね. そういうことでしょう.
でそれなら Wasmtime から提供されている API である `wasmtime` を使って環境依存なランタイムを構築してしまう, というのも運用上の選択肢ですが, それって要するに CLI ツールの `wasmtime` ですよね, ということで今回は割愛.
今回求めているのは全機能を Wasmtime ランタイム上に封じ込め, 1 つのバイナリで環境上の差異や動的リンクの一切を無くす, という所にあるからです (へぇ).

所で, "じゃあ `socket2` に依存しないという選択肢は？" と思うのですが, `warp` では厳しそうだと感じました.
`warp` ではそもそも `hyper` の v0 にある `Server` API にかなり依存しているので, 置換も面倒だし `hyper` を v1 に対応させるのも面倒くさそうだなぁ, という印象です. [この辺]() とか顕著ですよね.
ただ, ある程度の基盤を実装すれば `Filter` をハンドラとして扱えるはずなのでどうにか使えるはず. まあ `Filter` 単体で API が存在しないので, どうせ build は通らないんですけどね.

じゃあ `warp` 以外, 例えば `actix-web` は？というと, 結論から言えば `socket2` が含まれているため無理, という感じです.
具体的には, …なんか `actix-rt` 内で依存している `tokio` の version が…古いのかなぁ (よくわかんない). 見た感じ, 一部の API が存在しない (例えば `tokio::net::*` とか, `tokio::task::spawn_blocking` とか). あとなんか修正を試みてみたんですけど, `cfg(tokio_unstable)` は有効はなずなのに "対応してないよ！" って `compile_error!` で言われるんですよね (確か). ちょっと…お手上げ.
まあ fork して依存を更新してみても, 結局は `socket2` に阻まれることとなりましたとさ.

じゃあ次に `rocket` は？というと, これは**なんと v4 なら build が通る！** 但し, 現行の最新は v5 で, これは Release Candicate なので安定版としての最新版ではありますが, それ以外は古いです.
何が古いって, こんなメッセージが表示されるくらいで…

```:rustc
warning: the following packages contain code that will be rejected by a future version of Rust: traitobject v0.1.0
note: to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 9`
```

まあ, そんなことはいいんだ. <b>build が通るなら動くのでは！？</b>と期待したのも束の間, 実際には `TcpListener` 或いは fd から構築するような低レベルな API が存在しないため, 使えませんでした (あーあ).
ちなみに v5 は `socket2` 依存が含まれてました. はい.

あと `iron` とかどうなんでしょう？ 全然知らないけど名前だけ聞くなぁ… **と思ったらbuildが通る！** (まあ古いだけなんですが…) しかも `FromRawFd` な `std::net::TcpListener` から構築できる！
これはチャンス！と思って色々いじくり回してみたのですが, 残念なことになんか非対応の操作をどこかでしているっぽくて (追い切れなかった…) 起動しませんでした. …くそう.
あ, でも最新リリースのv0.6.1はただでさえ **3年前 ('19)** な上, masterもCI落ちてるし最終commitは'21年です. まあ…息を引き取ってるかな…
ちなみに `rocket` と同様の警告が出ます. `traitobject` のやつね.

## おわりに

とあるプロジェクトのバックエンドを書き始めようとした時に「WASI向けに作れば？」と悪魔の囁きをしてきた友人, 感謝しているが私はあなたを許さない.
そのうち `socket2` はWASI向けの実装がなされるのでしょうか？そもそもWASIの仕様って不安定なんですかね. 知らんけど. そういうのってあんまし上流に乗らないイメージがあるので, コアな人たちが実装して使うって感じになるんでしょうか.

私も詳しいことは知りませんので, もし指摘などありましたらコメントまで (うれしい).

**Happy New Year '23!** 本年もよろしくおねがいします！