[Rust] NGINX UnitでWASMを動かす

2023.10.11

Introduction

NGINX UnitはOSSのアプリケーションサーバです。
追加モジュールをインストールすることで、
Java/Go/Python/PHP/Nodeなどを使ってリクエストを処理することができます。

ここをみたらWASMにプレビューとして対応したようなので、
動かしてみます。

NGINX Unit?

nginx-unit

NGINX UnitはWASMを含むプログラムを実行可能な、
LightWeightなアプリサーバです。
動的に設定変更可能でスケールしやすく、パフォーマンスもすぐれているとのことです。
アプリサーバのコントロールはREST APIを使って行うため、
API経由でデプロイや設定変更を行います。
他にも、各アプリがそれぞれのプロセスで実行されるので個別のランタイムで実行できたり、
Dockerなどのコンテナ環境との統合もやりやすいとのこと。

Environment

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 13.5.2
  • NGINX Unit : 1.31.0
  • Rust : 1.72.0

Setup

ではまず、ここを参考にNGINX Unitをインストールします。
Macなら普通にHomebrewでインストールできるのでそのように。
WASM用モジュールのunit-wasmも同じくインストールします。

% brew install nginx/unit/unit
% brew install unit-wasm

バージョンを表示して1.31以上ならOKです。

% unitd --version
unit version: 1.31.0
configured as ./configure --prefix=・・・・・・・・

また、-hで設定ファイルやログファイルのパスが確認できます。

% unitd -h

unit options:

  --control ADDRESS    set address of control API socket
                       default: "unix:/opt/homebrew/var/run/unit/control.sock"

  --pid FILE           set pid filename
                       default: "/opt/homebrew/var/run/unit/unit.pid"

  --log FILE           set log filename
                       default: "/opt/homebrew/var/log/unit/unit.log"

  --modulesdir DIR     set modules directory name
                       default: "/opt/homebrew/lib/unit/modules"

  --statedir DIR       set state directory name
                       default: "/opt/homebrew/var/state/unit"

  --tmpdir DIR         set tmp directory name
                       default: "/tmp"

curlで設定状況を確認してみます。
起動してれば現在のNGINX Unitのステータスが返ってきます。

% curl --unix-socket /opt/homebrew/var/run/unit/control.sock http://127.0.0.1
{
    "certificates": {},
    "js_modules": {},
    "config": {
        "listeners": {},
        "applications": {}
    },

    "status": {
        "connections": {
            "accepted": 0,
            "active": 0,
            "idle": 0,
            "closed": 0
        },

        "requests": {
            "total": 0
        },

        "applications": {}
    }
}

Try

NGINX Unitのセットアップができたので、ここを参考に、
RustでWASMモジュールを作成してみましょう。

WASMターゲットを追加し、
NGINX Unit用WASMモジュールを実装するためのcrateも追加します。

% rustup target add wasm32-wasi
% cargo init --lib wasm_on_unit
% cd wasm_on_unit/

% cargo add unit-wasm

Cargo.tomlにlibセクションを記述します。
cdylib(動的共有ライブラリ - C Dynamic Library)を指定し、
動的共有ライブラリとしてコンパイルされるようにします。

[lib]
crate-type = ["cdylib"]

WASMサンプル用のRustファイルを作成したプロジェクトにもってきます。
サンプルではリクエストをうけたらのリクエスト内容を取得して詳細を返しています。

% wget -O src/lib.rs https://raw.githubusercontent.com/nginx/unit-wasm/main/examples/rust/echo-request/src/lib.rs

ターゲットを指定してビルドします。

% cargo build --target wasm32-wasi

↑のWASMモジュールをNGINX Unitに登録します。
下記のようにjsonファイル(wasm_config.json)を作成します。

{
    "listeners": {
        "127.0.0.1:8080": {
            "pass": "applications/wasm"
        }
    },

    "applications": {
        "wasm": {
            "type": "wasm",
            "module": "/path/your/wasm_on_unit/target/wasm32-wasi/debug//wasm_on_unit.wasm",
            "request_handler": "uwr_request_handler",
            "malloc_handler": "luw_malloc_handler",
            "free_handler": "luw_free_handler",
            "module_init_handler": "uwr_module_init_handler",
            "module_end_handler": "uwr_module_end_handler"
        }
    }
}

ここでは起動ポートやビルドしたWASMモジュールのパス、
リクエストハンドラ名など、モジュールの情報を記述します。   

curlでjsonを指定してモジュールの登録。

% curl -X PUT -d @wasm_config.json --unix-socket /opt/homebrew/var/run/unit/control.sock http://127.0.0.1/config/
{
    "success": "Reconfiguration done."
}

登録したWASMモジュールを実行してみましょう。

% curl http://localhost:8080
 * Welcome to WebAssembly in Rust on Unit! [libunit-wasm (0.2.0/0x00020000)] *

[Request Info]
REQUEST_PATH = /
METHOD       = GET
VERSION      = HTTP/1.1
QUERY        =
REMOTE       = 127.0.0.1
LOCAL_ADDR   = 127.0.0.1
LOCAL_PORT   = 8080
SERVER_NAME  = localhost

[Request Headers]
Host = localhost:8080
User-Agent = curl/7.71.1
Accept = */*

Summary

今回はNGINX UnitでWASMモジュールを動かしてみました。
WASMはまだプレビュー対応なのでもう少し待ちましょう。

References