WebAssembly Gateway Interface (WAGI) を試す

# はじめに

WebAssembly Gateway Interface (WAGI) を試しに動かしてみました。

# WAGIとは

WAGIは、[Common Gateway Interface](https://datatracker.ietf.org/doc/html/rfc3875 "rfc3875") (CGI) のWebAssembly版の実装です。

https://github.com/deislabs/wagi

ただし、いくつかCGIと異なる部分があります[^1]。
2つほど抜粋すると、WAGIには次のような特徴があります。

- CGIではリクエストを受け取るたびに子プロセスを起動して処理をするが、WAGIではマルチスレッドで処理される
- ファイルシステムへのアクセスが制限される
    - （これは、後述する`modules.toml` で指定することでそのファイルへのアクセスが許されるようになります。）

[^1]: https://github.com/deislabs/wagi/blob/main/docs/architecture.md#differences-from-cgi-11

# インストール

今回は、Ubuntu20.04環境にビルド済みのバイナリをインストールします。
レポジトリの[リリースぺ－ジ](https://github.com/deislabs/wagi/releases "Releases · deislabs/wagi")からダウンロードして、`/usr/local/bin`に配置します。

```shell-session:bash
$ wget https://github.com/deislabs/wagi/releases/download/v0.4.0/wagi-v0.4.0-linux-amd64.tar.gz
$ tar -xvf wagi-v0.4.0-linux-amd64.tar.gz
$ sudo mv wagi /usr/local/bin
```

インストールができていることを確認します。

```shell-session:bash
$ wagi --version
WAGI Server 0.4.0
```

# とりあえず動かしてみる

WAGIのレポジトリには`examples`フォルダがあり、WAGIを試しに実行できます。

```shell-session:bash
$ git clone https://github.com/deislabs/wagi.git
$ cd wagi
$ wagi -c examples/modules.toml
```

> デフォルトの場合、`localhost:3000`がリッスンされます。
外部からのリクエストを受け付けたい場合やポートを変更したい場合は`-l` オプションを利用します。 
例えば、次のようにして外部からのリクエストを3333ポートで受け付けるようにします。

> ```shell-session:bash
$ wagi -c examples/modules.toml -l 0.0.0.0:3333
```

別のターミナルからHTTPリクエストを送信して、レスポンスが返ってくることを確認します。

```shell-session:bash
$ curl -v localhost:3000
*   Trying 127.0.0.1:3000...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/html;charset=UTF-8
< content-length: 12
< date: Fri, 17 Dec 2021 05:43:49 GMT
<
Oh hi world
* Connection #0 to host localhost left intact
```

`Oh hi world`とレスポンスが返って来ました！

## modules.toml

WAGIを起動した際に `-c examples/modules.toml`とオプションを指定しました。
この`modules.toml`がWAGIのコンフィグファイルです。これに従って、WAGIはURIと対応するWebAssemblyモジュールを決定します。オプションで指定をしなかった場合は、CWDの`modules.toml`が読み込まれます。

`examples/modules.toml`では、からエンドポイントが`/`の部分を抜粋すると、次のようになっています。

```toml:examples/modules.toml
[[module]]
# Example executing a Web Assembly Text file
route = "/"
module = "examples/hello.wat"
```

ここでは、`route`と`module`が指定されています。
WAGIは`/`にアクセスが来たら、`examples/hello.wat`を用いてHTTPレスポンスを作って返します。

`examples/hello.wat`を確認すると10行目にHTTPレスポンスがハードコードされていることが確認できます。

```text
(module
    ;; This is the example Hello World WAT from the documentation at
    ;; https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-tutorial.md
    ;;
    ;; It has been adapted to send CGI headers.
    (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
    (memory 1)
    (export "memory" (memory 0))

    (data (i32.const 8) "content-type: text/html;charset=UTF-8\n\nOh hi world\n")

    (func $main (export "_start")
        (i32.store (i32.const 0) (i32.const 8))
        (i32.store (i32.const 4) (i32.const 51))

        (call $fd_write
            (i32.const 1)
            (i32.const 0)
            (i32.const 1)
            (i32.const 20)
        )
        drop
    )
)
```

# エンドポイントを追加する

最後に、適当なWebAssemblyモジュールを作成して、WAGIのエンドポイントに追加します。
HTMLファイルを読んで、HTTPヘッダをつけてレスポンスを作成するようにします。

今回は以下の`cat.rs`と`cat.html`を用意しました。

```rust:examples/cat.rs
fn main() {
    println!("Content-Type: text/html; charset=UTF-8");
    println!();
    let html = std::fs::read_to_string("cat.html").unwrap_or(String::from("にゃー"));
    println!("{}", html);
}
```

```html:examples/cat.html
<!DOCTYPE html>
<html>

<head>
    <meta charset='utf-8'>
    <title>CAT</title>
</head>

<body>
    <pre>
    　　　　　　　　　　　　　　　　　　　_,,..．='~1
    　　　　　　　　　　 _,.､='＂~~,才＜／彡　 }
    　　　　　　　　 ,.=ﾞー- 　▼　　````.X彡-ﾅ
    　　　　　_,,.::=7;;;.〃　　　 人　 .〟二二キ二　　
    　 _,.．='´~こ/;;;´,,..　　 -ｲ　.`'''´ 　 　 　Ｙ
    .<　二二＝ｵ;;,,..-´　　　　　　　　　　　　 }
    　乂　.二=壬　 ／　　　　　　　　　　　　/
    　　 ゝ､,,,,,..ｷ;／　　　　　　　　　,.．--．/
    　　　　　　ゞi　　　　　　　　 ,／　　　　 ＼
    　　　　　　　 ＼　　　　_,,,,,__.ﾚ 　　 　　 　 i
    　　　　　　　　　＼,.／　　　 ゝ_r_,.イﾞ　　　!
    　　　　　　　　　　 /　　　　　￤　　　　　　 ＼　　　　　,. ､
    　　　　　　　　　　 {　　　 ゝJ┘　　　　　 　　 .>-､,　/6 ６∂
    　　　　　　　　　　 ＼　　　　　　　　　　　　　　　　 ﾞ'i　.ω }
    　　　　　　　　　　　　ﾞキ　　　　　　　　　　　　　　　i　　　/
    　　　　　　　　　　　　　ｷ　　　　　　　　　　　　　　 i　 　 /
    　　　　　　　　　　　　　 ｷ;;　　　　　　　　　　　　　　　　/
    　　　　　　　　　　　　　 ﾒﾞﾞ　　　　　　　　　　　　　　 ,,ノ
    　　　　　　　　　　　　　{キ;. 　　　　　　　　　　　　　ノ
    　　　　　　　　　　　　　ｷ≠　　　　　　　　　r=ｫ'匕ゝ､＿　　,,....,
    　　　　　　　　　　　　　　Ｙ.--''''''''　　　　 ノ弋≠ ≠;≠ｷ千;;彡ﾞ
    　　　　　　　　　　　　　 iﾞ　o　３　　　　,.）　　　`'''…ー'''''´´
    　　　　　　　　　　　　　（__　8　,..〟'''´´
    　　　　　　　　　　　　　　ゝ-'''´
</pre>
</body>

</html>
```

`cat.rs`をコンパイルして、`cat.wasm`を作成します。

```shell-session:bash
$ rustc --target=wasm32-wasi examples/cat.rs -o examples/cat.wasm
```

`modules.toml`には、次のように追記します。

```toml:examples/modules.toml
# 省略

[[module]]
route = "/cat"
module = "examples/cat.wasm"
volumes = {"." = "examples"}
```

WebAssemblyモジュールにファイルへのアクセスを許可するため、`volumes`ディレクティブを利用します。
今回は、`examples`フォルダをモジュールの`.`にマウントしています。
（Toml Parserが `Expected "}", [ \t] or [A-Za-z0-9_\-] but "\"" found.`と警告してくるように、本来のTomlの構文に違反しているのはちょっと気になりますね。）

これで準備が整ったので、WAGIを再起動します。

```shell-session:bash
$ wagi -c examples/modules.toml
No log_dir specified, using temporary directory /tmp/.tmpsPMv9G for logs
```

別のターミナルから、`/cat`にアクセスしてみます。

```shell-session:bash
$ curl -v localhost:3000/cat
*   Trying 127.0.0.1:3000...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /cat HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-type: text/html; charset=UTF-8
< content-length: 2140
< date: Fri, 17 Dec 2021 07:33:58 GMT
<
<!DOCTYPE html>
<html>

<head>
    <meta charset='utf-8'>
    <title>CAT</title>
</head>

<body>
    <pre>
    　　　　　　　　　　　　　　　　　　　_,,..．='~1
    　　　　　　　　　　 _,.､='＂~~,才＜／彡　 }
    　　　　　　　　 ,.=ﾞー- 　▼　　````.X彡-ﾅ
    　　　　　_,,.::=7;;;.〃　　　 人　 .〟二二キ二　　
    　 _,.．='´~こ/;;;´,,..　　 -ｲ　.`'''´ 　 　 　Ｙ
    .<　二二＝ｵ;;,,..-´　　　　　　　　　　　　 }
    　乂　.二=壬　 ／　　　　　　　　　　　　/
    　　 ゝ､,,,,,..ｷ;／　　　　　　　　　,.．--．/
    　　　　　　ゞi　　　　　　　　 ,／　　　　 ＼
    　　　　　　　 ＼　　　　_,,,,,__.ﾚ 　　 　　 　 i
    　　　　　　　　　＼,.／　　　 ゝ_r_,.イﾞ　　　!
    　　　　　　　　　　 /　　　　　￤　　　　　　 ＼　　　　　,. ､
    　　　　　　　　　　 {　　　 ゝJ┘　　　　　 　　 .>-､,　/6 ６∂
    　　　　　　　　　　 ＼　　　　　　　　　　　　　　　　 ﾞ'i　.ω }
    　　　　　　　　　　　　ﾞキ　　　　　　　　　　　　　　　i　　　/
    　　　　　　　　　　　　　ｷ　　　　　　　　　　　　　　 i　 　 /
    　　　　　　　　　　　　　 ｷ;;　　　　　　　　　　　　　　　　/
    　　　　　　　　　　　　　 ﾒﾞﾞ　　　　　　　　　　　　　　 ,,ノ
    　　　　　　　　　　　　　{キ;. 　　　　　　　　　　　　　ノ
    　　　　　　　　　　　　　ｷ≠　　　　　　　　　r=ｫ'匕ゝ､＿　　,,....,
    　　　　　　　　　　　　　　Ｙ.--''''''''　　　　 ノ弋≠ ≠;≠ｷ千;;彡ﾞ
    　　　　　　　　　　　　　 iﾞ　o　３　　　　,.）　　　`'''…ー'''''´´
    　　　　　　　　　　　　　（__　8　,..〟'''´´
    　　　　　　　　　　　　　　ゝ-'''´
</pre>
</body>

</html>
* Connection #0 to host localhost left intact
```

かわいいですね🐈