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