4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

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

前置き

WASI, The WebAssembly System Interfaceという 変態的 革新的技術をご存知でしょうか. めちゃめちゃざっくり言うと, WASM の親戚みたいな感じで, 尚且つ syscall とかが絡むようなこともできちゃうっていうか syscall ができる (多分) すごーいやつです.

で, 今回お世話になる WASI の実行環境, ランタイムは Wasmtime というやつです. なんか Lucet ってやつが有ったり無かったりしたとか, あと WasmEdge とかいうくそ速そうなやつや, Wasmer っていうエコシステム呼ばわりされているものがあったりしますが, 今回は割愛, ソースは ここ. でもそもそも Wasmtime 以外今回の要件を満たしていないのかな?なんて気もしている. ちなみに Wasmtime の実装は Rust です, 嬉しい! (キャッキャ)

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

環境

zsh
> uname -snrmo
Linux archlinux 5.15.85-1-lts x86_64 GNU/Linux
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)`
~/.cargo/config.toml より抜粋
[target.'cfg(all())']
rustflags = ["-C", "target-cpu=native", "-Z", "share-generics=y"]

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

導入

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

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 とのことで, けっこう小さいじゃーんという気持ち

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

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

> cd http-wasi-rs

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

Cargo.toml
[package]
name = "http-wasi-rs"
version = "0.0.0"
edition = "2021"
src/main.rs
fn main() {
    println!("hi, from rust on wasmtime!");
}

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

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 で修正しようにもなんか上手く行かなかったので, 以下のように修正.

~/.cargo/config.toml より抜粋
[target.'cfg(all())']
rustflags = ["-Z", "share-generics=y"]
【余談】rustflags の適用順について

補足情報として,

zsh
> rustc --target wasm32-wasi --print target-cpus
Available CPUs for this target:
    bleeding-edge
    generic
    mvp
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-wasitarget-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 をいじくりまわしてみた. (多分適応順には変わりがない)

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

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

すると,

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

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 へ指定しときました.

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


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

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

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 しか対応していない ことが原因で build が通らない. じゃあ tokio + hyper は?というと, サーバーを立てるのに必須な hyper/tcp の build が同様の理由で通らない.

というわけで, 取り敢えず接続の待ち受けまでは自分で書いてみることにした.
そして実装がこちら, 参考は これ.

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(())
}

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

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 へのリンクが 貼ってあった りしたが, 正直参考にならなかったのでさっさと Issue を探すことに. で見つけた (PR だけど).

Add CLI options for preopened listen sockets by haraldh · Pull Request #3729 · bytecodealliance/wasmtime

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

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 を見てみることに.

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

えぇ (fd 違うじゃん)

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)
...

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

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)
...

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

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

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 の恩恵はこういうところに出るのだ…素晴らしい… (惚れ惚れ)

実行してみる.

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
zsh (client)
> curl -X POST -H 'Content-Type: text/plain' -d 'echo' 127.0.0.1:4000
echo%

ちゃんと返ってきてる.

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

using TcpListener of tokio

生の TCP 接続とか扱ってたら埒が明かないので, 取り敢えず tokio が使えることを確かめたい. さっきの PR で提示されてた examplemio の fork だったし, 多分動くってことなんだろう.
というわけで依存を追加. 不安定と云われている flag も追加. バグを恐れずに行け?

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

この指定の仕方, "解ってね" ってメッセージを感じるけど, 英弱なので分からなかった どこにも書いてなかったので, tokio-rs/tokio で .toml 内の "unstable" というキーワードを探した所, netlify.toml に記述があるのを発見しましたが, 正攻法じゃないかもしれない.

↑ これは少し前まで書き置いていた文章.
実際には ここ に堂々と書いてありました. はい.

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

良い眺めだ.

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 にはどう足掻いても対応してないのね. じゃあ…

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

あ, 通った通った. これなら使えるのね.
じゃあお待ちかね, TCP 接続待ち受けの非同期化と洒落込みましょうか.
但し tokio::net::TcpListenerFromRawFd じゃないので, std::net::TcpListener を構築した上で tokio::net::TcpListener::from_std を介して構築します.
で, 最初は イキって 折角ならと core::async_iter::AsyncIteratorStream に実装してやろうかと思ったのですが, 極めて残念なことにこいつがまだ 実用化されてない 様子だったので, Iterator の恩恵は受けられなくなりましたとさ (元からそんなに受けてないけど).

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_u8while let Ok(_) = ... で受けて使うようにしています.

で動作確認.

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
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 も 立ってる. ので hyper/tcp も使えない.

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

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

で処理を書き換え.

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(())
}

そしたら実行.

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
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`

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

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

おわりに

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

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

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

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?