LoginSignup
0
0

WebAssemblyコンポーネントでHTTPハンドラーを作成

Posted at

WASI Preview 2 の wasi:http を用いた HTTP ハンドラー処理を WebAssembly コンポーネントで実装してみました。

今回も「cargo componentでWASI Preview 2を使ったコンポーネントを作成」と同様の方法を用います。

コンポーネント作成

HTTP ハンドラーの wit は次のようにするだけでした。

  • wasi:http/incoming-handler を export する

実際の wit ファイルはこのようになります。

wit/world.wit
package component:http-sample;

world sample {
    export wasi:http/incoming-handler@0.2.0;
}

WASI の wit をローカル参照する方法で wasi:http を使う場合の依存設定はこのようになりました。(wasi:cli を利用する際の依存設定へ wasi:http を加えるだけ)

Cargo.toml
[package]
name = "http_sample"
version = "0.1.0"
edition = "2021"

[dependencies]
bitflags = "2.5.0"
wit-bindgen-rt = "0.24.0"

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

[package.metadata.component]
package = "component:http-sample"

[package.metadata.component.dependencies]

[package.metadata.component.target.dependencies]
"wasi:http" = { path = "./WASI/preview2/http" }
"wasi:cli" = { path = "./WASI/preview2/cli" }
"wasi:clocks" = { path = "./WASI/preview2/clocks" }
"wasi:filesystem" = { path = "./WASI/preview2/filesystem" }
"wasi:io" = { path = "./WASI/preview2/io" }
"wasi:random" = { path = "./WASI/preview2/random" }
"wasi:sockets" = { path = "./WASI/preview2/sockets" }

実装はこのようにしてみました。
ok:<URLパス> という文字列をレスポンスとして返すだけです。

content-length レスポンスヘッダーを設定しておかないと、wasmtime の処理でエラーとなったのでご注意ください。

src/lib.rs
#[allow(warnings)]
mod bindings;

use bindings::exports::wasi::http::incoming_handler::Guest;
use bindings::wasi::http::types::{ErrorCode, HeaderError, Headers, OutgoingResponse, ResponseOutparam};
use bindings::wasi::io::streams::StreamError;

struct Component;

impl Guest for Component {
    fn handle(
        request: bindings::exports::wasi::http::incoming_handler::IncomingRequest,
        response_out: bindings::exports::wasi::http::incoming_handler::ResponseOutparam,
    ) {
        let s = format!("ok:{}", request.path_with_query().unwrap_or_default());

        ResponseOutparam::set(response_out, create_response(s));
    }
}

fn create_response(s: String) -> Result<OutgoingResponse, ErrorCode> {
    let c = s.as_bytes();

    let h = Headers::new();
    h.append(&"content-length".to_string(), &c.len().to_string().into())?;

    let r = OutgoingResponse::new(h);

    let b = r.body()?;
    let w = b.write()?;
    // レスポンスボディの書き込み
    w.write(c)?;
    w.flush()?;

    Ok(r)
}

impl From<()> for ErrorCode {
    fn from(_value: ()) -> Self {
        ErrorCode::InternalError(None)
    }
}

impl From<StreamError> for ErrorCode {
    fn from(value: StreamError) -> Self {
        ErrorCode::InternalError(Some(value.to_string()))
    }
}

impl From<HeaderError> for ErrorCode {
    fn from(value: HeaderError) -> Self {
        ErrorCode::InternalError(Some(value.to_string()))
    }
}

bindings::export!(Component with_types_in bindings);

ビルドと実行

wasm32-unknown-unknown でビルドします。

ビルド
$ cargo component build --target wasm32-unknown-unknown --release

wasmtime の serve コマンドで実行します。
これで Web サーバーが立ち上がり、リクエストを WebAssembly コンポーネントへ中継してくれます。

実行
$ wasmtime serve target/wasm32-unknown-unknown/release/http_sample.wasm
Serving HTTP on http://0.0.0.0:8080/

アクセスしてみると、正常に動作している事を確認できました。

動作確認
$ curl -v http://localhost:8080/a/1

...省略
< HTTP/1.1 200 OK
< content-length: 7
< date: Sat, 27 Apr 2024 02:22:32 GMT
< 
* Connection #0 to host localhost left intact
ok:/a/1
0
0
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
0
0