WASI Preview2ネットワーキング:コンポーネントモデルでWasm HTTPサービスを構築する5つのコアパターン
Wasmのネットワーク機能不足、WASI Preview2がついに解決
WASI Preview1の時代、WebAssemblyにはネットワーク能力がありませんでした。Socket APIもHTTPサーバーもなく、すべてのネットワークリクエストはホスト経由でプロキシするしかありませんでした。WasmでHTTPマイクロサービスを書きたい?残念ながら、ホストにリクエストの受信とレスポンスの転送を頼むしかありません。コンポーネントモデルの概念も新しく複雑で、ドキュメントも少ない。2026年、WASI Preview2がついにネイティブのネットワーク能力をもたらしました:HTTP Handlerインターフェース、アウトバウンドHTTPリクエスト、Socket API、そしてコンポーネントモデルベースの型安全な通信。Wasmはついに「自力でネットワークにアクセス」できるようになりました。
本記事では5つのコアパターンから、HTTP Handler開発→アウトバウンドHTTP呼び出し→コンポーネント間WIT通信→Spinフレームワークデプロイ→キーバリューストア管理の完全な実践パイプラインを解説し、WASI Preview2ネットワーキングを「概念実証」から「プロダクション対応」へと導きます。
主要な学び
- WASI HTTP Handlerコンポーネントの完全な開発フローを習得
- アウトバウンドHTTPリクエストと外部API呼び出しを実装
- コンポーネント間WITインターフェース通信パイプラインを構築
- Spinフレームワークを使ったHTTPサービスデプロイ
- キーバリューストアによる状態管理を適用
目次
- WASI Preview2ネットワーキングコア概念
- パターン1:WASI HTTP Handlerコンポーネント開発
- パターン2:アウトバウンドHTTPリクエストとAPI呼び出し
- パターン3:コンポーネント間WITインターフェース通信
- パターン4:SpinフレームワークHTTPサービスデプロイ
- パターン5:キーバリューストアと状態管理
- 5つのよくある落とし穴と解決策
- 10のよくあるエラートラブルシューティング
- 高度な最適化テクニック
- 比較分析
- まとめと展望
- オンラインツール推奨
WASI Preview2ネットワーキングコア概念
Preview1からPreview2へのネットワーク進化
WASI Preview2は、2026年のWebAssembly System Interfaceにおける最も重要なアップグレードです。Preview1には基本的なファイルシステムとクロックAPIしかなく、ネットワーク機能は完全に欠如していました。Preview2はコンポーネントモデルを通じて、標準化されたHTTP Handler、アウトバウンドHTTP、Socket API、キーバリューストアインターフェースを導入しています。
| 次元 | WASI Preview1 | WASI Preview2 |
|---|---|---|
| ネットワーク | ❌ なし | ✅ HTTP Handler + Socket API |
| HTTPサーバー | ホストプロキシが必要 | ネイティブHTTP Handlerインターフェース |
| アウトバウンドリクエスト | ホスト転送が必要 | ネイティブwasi:http/outgoing-handler |
| コンポーネント通信 | 標準なし | WITインターフェース型安全通信 |
| 状態管理 | ファイルシステムのみ | キーバリューストアwasi:keyvalue |
| 非同期IO | ❌ なし | ✅ Poll APIベース |
| ランタイム | wasmtime Preview1 | wasmtime Preview2 / Spin |
主要用語
| 用語 | 説明 |
|---|---|
| WASI Preview2 | WebAssembly System Interface第2版、ネットワークとHTTP APIを含む |
| コンポーネントモデル | Wasmモジュールの標準化されたインターフェース契約、言語間相互運用を可能に |
| WIT | WebAssembly Interface Types、インターフェース記述言語 |
| HTTP Handler | wasi:http/incoming-handlerインターフェース、インバウンドHTTPリクエストを処理 |
| Socket API | wasi:socketsインターフェースファミリ、TCP/UDPネットワーキング |
| wasmtime | Bytecode AllianceのWasmランタイム、Preview2対応 |
| Spin | FermyonのWasmアプリケーションフレームワーク、HTTPサービス開発を簡素化 |
| アウトバウンドHTTP | wasi:http/outgoing-handler、HTTPリクエストを発行 |
パターン1:WASI HTTP Handlerコンポーネント開発
WASI Preview2のHTTP Handlerは、Wasm HTTPサービスを構築するためのコア要素です。コンポーネントはwasi:http/incoming-handlerインターフェースを実装することで、ホストプロキシなしでインバウンドHTTPリクエストを直接処理できます。
WITインターフェース定義
package toolsku:http-service;
interface incoming-handler {
resource incoming-request {
method: func() -> string;
path-with-query: func() -> string;
headers: func() -> list<tuple<string, string>>;
body: func() -> list<u8>;
}
resource outgoing-response {
set-status-code: func(code: u16);
set-headers: func(headers: list<tuple<string, string>>);
set-body: func(body: list<u8>);
}
handle: func(request: incoming-request) -> outgoing-response;
}
world http-service {
export wasi:http/incoming-handler@0.2.3;
import wasi:http/types@0.2.3;
import wasi:io/streams@0.2.3;
}
Rustコンポーネント実装
// src/lib.rs
wit_bindgen::generate!({
path: "../wit/http-service.wit",
world: "http-service",
generate_all,
});
use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::{IncomingRequest, OutgoingResponse, OutgoingBody};
struct HttpService;
impl Guest for HttpService {
fn handle(request: IncomingRequest) {
let method = request.method().to_string();
let path = request.path_with_query().unwrap_or("/".to_string());
let headers = request.headers().entries();
let body_bytes = read_request_body(&request);
let response_body = match (method.as_str(), path.as_str()) {
("GET", "/api/health") => {
r#"{"status":"ok","version":"1.0.0"}"#.as_bytes().to_vec()
}
("GET", p) if p.starts_with("/api/greet/") => {
let name = p.trim_start_matches("/api/greet/");
format!(r#"{{"message":"Hello, {}!"}}"#, name).into_bytes()
}
("POST", "/api/echo") => {
body_bytes
}
_ => {
r#"{"error":"not found"}"#.as_bytes().to_vec()
}
};
let response = OutgoingResponse::new(
wasi::http::types::Fields::from_list(&[
("content-type".to_string(), "application/json".to_string()),
("x-powered-by".to_string(), "wasi-preview2".to_string()),
]).unwrap()
);
response.set_status_code(200).unwrap();
let body = response.body().unwrap();
let stream = body.write().unwrap();
stream.blocking_write_and_flush(&response_body).unwrap();
OutgoingBody::finish(body, None).unwrap();
}
}
fn read_request_body(request: &IncomingRequest) -> Vec<u8> {
let body = request.body().unwrap();
let stream = body.stream().unwrap();
let mut buf = Vec::new();
loop {
match stream.blocking_read(4096) {
Ok(chunk) => {
if chunk.is_empty() { break; }
buf.extend_from_slice(&chunk);
}
Err(_) => break,
}
}
buf
}
export_http_service!(HttpService);
Cargo設定
[package]
name = "http-service"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wit-bindgen = "0.40"
[profile.release]
opt-level = "z"
lto = true
strip = true
codegen-units = 1
パターン2:アウトバウンドHTTPリクエストとAPI呼び出し
WASI Preview2のwasi:http/outgoing-handlerにより、Wasmコンポーネントはホスト転送なしでHTTPリクエストを能動的に発行し、外部APIを呼び出せるようになります。
アウトバウンドHTTP実装
// src/outbound_http.rs
use wasi::http::outgoing_handler::OutgoingRequest;
use wasi::http::types::{Fields, OutgoingBody, Method, Scheme};
use wasi::io::streams::StreamError;
pub fn http_get(url: &str) -> Result<Vec<u8>, String> {
let parsed = parse_url(url)?;
let headers = Fields::from_list(&[
("accept".to_string(), "application/json".to_string()),
("user-agent".to_string(), "wasi-preview2-client/1.0".to_string()),
]).map_err(|e| format!("Header error: {:?}", e))?;
let request = OutgoingRequest::new(headers);
request.set_method(&Method::Get).map_err(|e| format!("{:?}", e))?;
request.set_scheme(Some(&Scheme::Https)).map_err(|e| format!("{:?}", e))?;
request.set_authority(Some(&parsed.authority)).map_err(|e| format!("{:?}", e))?;
request.set_path_with_query(Some(&parsed.path)).map_err(|e| format!("{:?}", e))?;
let response = wasi::http::outgoing_handler::handle(request, None)
.map_err(|e| format!("Outbound request failed: {:?}", e))?;
let status = response.status();
if status != 200 {
return Err(format!("HTTP {} error", status));
}
read_response_body(&response)
}
pub fn http_post(url: &str, body: &[u8]) -> Result<Vec<u8>, String> {
let parsed = parse_url(url)?;
let headers = Fields::from_list(&[
("content-type".to_string(), "application/json".to_string()),
("accept".to_string(), "application/json".to_string()),
]).map_err(|e| format!("Header error: {:?}", e))?;
let request = OutgoingRequest::new(headers);
request.set_method(&Method::Post).map_err(|e| format!("{:?}", e))?;
request.set_scheme(Some(&Scheme::Https)).map_err(|e| format!("{:?}", e))?;
request.set_authority(Some(&parsed.authority)).map_err(|e| format!("{:?}", e))?;
request.set_path_with_query(Some(&parsed.path)).map_err(|e| format!("{:?}", e))?;
let outgoing_body = request.body().map_err(|e| format!("{:?}", e))?;
let stream = outgoing_body.write().map_err(|e| format!("{:?}", e))?;
stream.blocking_write_and_flush(body).map_err(|e| format!("{:?}", e))?;
OutgoingBody::finish(outgoing_body, None).map_err(|e| format!("{:?}", e))?;
let response = wasi::http::outgoing_handler::handle(request, None)
.map_err(|e| format!("Outbound request failed: {:?}", e))?;
read_response_body(&response)
}
struct ParsedUrl {
authority: String,
path: String,
}
fn parse_url(url: &str) -> Result<ParsedUrl, String> {
let without_scheme = url.strip_prefix("https://")
.or_else(|| url.strip_prefix("http://"))
.ok_or("Invalid URL scheme")?;
let (authority, path) = match without_scheme.find('/') {
Some(idx) => (without_scheme[..idx].to_string(), without_scheme[idx..].to_string()),
None => (without_scheme.to_string(), "/".to_string()),
};
Ok(ParsedUrl { authority, path })
}
fn read_response_body(response: &wasi::http::types::IncomingResponse) -> Result<Vec<u8>, String> {
let body = response.body().map_err(|e| format!("{:?}", e))?;
let stream = body.stream().map_err(|e| format!("{:?}", e))?;
let mut buf = Vec::new();
loop {
match stream.blocking_read(4096) {
Ok(chunk) => {
if chunk.is_empty() { break; }
buf.extend_from_slice(&chunk);
}
Err(StreamError::Closed) => break,
Err(e) => return Err(format!("Read error: {:?}", e)),
}
}
Ok(buf)
}
API呼び出し統合例
pub fn fetch_weather(city: &str) -> Result<String, String> {
let url = format!("https://api.weather.example.com/v1/current?city={}", city);
let body = http_get(&url)?;
String::from_utf8(body).map_err(|e| format!("UTF-8 decode error: {}", e))
}
pub fn notify_webhook(payload: &str) -> Result<(), String> {
let url = "https://hooks.example.com/wasi-notify";
http_post(url, payload.as_bytes())?;
Ok(())
}
パターン3:コンポーネント間WITインターフェース通信
コンポーネントモデルのコアバリューは、WITインターフェースを通じた型安全なコンポーネント間通信にあります。複数のコンポーネントを組み合わせて複雑なサービストポロジーを構築できます。
通信アーキテクチャ
┌─────────────────────────────────────────────────────┐
│ Component Composition │
│ │
│ ┌──────────────┐ WIT ┌──────────────────┐ │
│ │ API Gateway │◄──────────►│ User Service │ │
│ │ Component │ interface │ Component │ │
│ └──────┬───────┘ └──────────────────┘ │
│ │ │
│ │ WIT interface │
│ ▼ │
│ ┌──────────────┐ WIT ┌──────────────────┐ │
│ │ Auth Service │◄──────────►│ Cache Service │ │
│ │ Component │ interface │ Component │ │
│ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
共有WITインターフェース定義
package toolsku:service-interfaces;
interface user-service {
record user {
id: string,
name: string,
email: string,
role: string,
}
record user-request {
user-id: string,
include-profile: bool,
}
get-user: func(req: user-request) -> result<user, string>;
list-users: func(page: u32, size: u32) -> result<list<user>, string>;
}
interface auth-service {
record auth-token {
token: string,
expires-at: u64,
scopes: list<string>,
}
validate-token: func(token: string) -> result<auth-token, string>;
revoke-token: func(token: string) -> result<_, string>;
}
world gateway-world {
import user-service;
import auth-service;
export wasi:http/incoming-handler@0.2.3;
}
ゲートウェイコンポーネント実装
// gateway/src/lib.rs
wit_bindgen::generate!({
path: "../wit/service-interfaces.wit",
world: "gateway-world",
generate_all,
});
use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::IncomingRequest;
use imports::toolsku::service_interfaces::user_service::UserRequest;
use imports::toolsku::service_interfaces::auth_service::validate_token;
struct GatewayComponent;
impl Guest for GatewayComponent {
fn handle(request: IncomingRequest) {
let path = request.path_with_query().unwrap_or("/".to_string());
let headers = request.headers().entries();
let auth_header = headers.iter()
.find(|(k, _)| k.to_lowercase() == "authorization")
.map(|(_, v)| v.clone());
let auth_result = match &auth_header {
Some(token) => validate_token(token.trim_start_matches("Bearer ")),
None => Err("Missing authorization header".to_string()),
};
match auth_result {
Ok(_auth_token) => {
if path.starts_with("/api/users/") {
let user_id = path.trim_start_matches("/api/users/");
let req = UserRequest {
user_id: user_id.to_string(),
include_profile: true,
};
match imports::toolsku::service_interfaces::user_service::get_user(req) {
Ok(user) => send_json_response(200, &format!(
r#"{{"id":"{}","name":"{}","email":"{}"}}"#,
user.id, user.name, user.email
)),
Err(e) => send_json_response(500, &format!(r#"{{"error":"{}"}}"#, e)),
}
} else {
send_json_response(404, r#"{"error":"not found"}"#)
}
}
Err(e) => send_json_response(401, &format!(r#"{{"error":"{}"}}"#, e)),
}
}
}
fn send_json_response(status: u16, body: &str) {
let response = wasi::http::types::OutgoingResponse::new(
wasi::http::types::Fields::from_list(&[
("content-type".to_string(), "application/json".to_string()),
]).unwrap()
);
response.set_status_code(status).unwrap();
let outgoing_body = response.body().unwrap();
let stream = outgoing_body.write().unwrap();
stream.blocking_write_and_flush(body.as_bytes()).unwrap();
wasi::http::types::OutgoingBody::finish(outgoing_body, None).unwrap();
}
export_gateway_world!(GatewayComponent);
パターン4:SpinフレームワークHTTPサービスデプロイ
SpinはFermyonが開発したWasmアプリケーションフレームワークで、WASI Preview2 HTTPサービスの開発とデプロイを大幅に簡素化します。
Spinプロジェクト設定
# spin.toml
spin_manifest_version = 2
[application]
name = "wasi-http-service"
version = "1.0.0"
description = "WASI Preview2 HTTP Service with Spin"
[[trigger.http]]
route = "/api/..."
component = "http-api"
[component.http-api]
source = "target/wasm32-wasip2/release/http_service.wasm"
allowed_outbound_hosts = ["https://api.example.com"]
[component.http-api.build]
command = "cargo build --target wasm32-wasip2 --release"
watch = ["src/**/*.rs", "Cargo.toml"]
[[trigger.http]]
route = "/health"
component = "health-check"
[component.health-check]
source = "target/wasm32-wasip2/release/health_check.wasm"
[component.health-check.build]
command = "cargo build --target wasm32-wasip2 --release --bin health_check"
Spinコンポーネント実装
// src/spin_handler.rs
use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;
#[http_component]
fn handle_request(req: Request) -> anyhow::Result<impl IntoResponse> {
let path = req.uri().path().to_string();
let method = req.method().to_string();
match (method.as_str(), path.as_str()) {
("GET", "/api/health") => {
let body = r#"{"status":"healthy","runtime":"spin","wasi":"preview2"}"#;
Ok(Response::builder()
.status(200)
.header("content-type", "application/json")
.body(body.as_bytes().to_vec())
.build())
}
("GET", p) if p.starts_with("/api/items/") => {
let id = p.trim_start_matches("/api/items/");
let body = format!(r#"{{"id":"{}","name":"item-{}"}}"#, id, id);
Ok(Response::builder()
.status(200)
.header("content-type", "application/json")
.body(body.into_bytes())
.build())
}
("POST", "/api/items") => {
let _body = req.body().clone();
Ok(Response::builder()
.status(201)
.header("content-type", "application/json")
.body(r#"{"created":true}"#.as_bytes().to_vec())
.build())
}
_ => Ok(Response::builder()
.status(404)
.header("content-type", "application/json")
.body(r#"{"error":"not found"}"#.as_bytes().to_vec())
.build()),
}
}
デプロイコマンド
spin build
spin up --listen 127.0.0.1:3000
spin deploy --environment production
spin cloud apps list
spin cloud logs follow wasi-http-service
パターン5:キーバリューストアと状態管理
WASI Preview2のwasi:keyvalueインターフェースは、標準化されたキーバリューストレージを提供し、Wasmコンポーネントがリクエスト間で状態を保持できるようにします。
WITインターフェース定義
package toolsku:kv-service;
interface kv-operations {
resource kv-store {
get: func(key: string) -> option<list<u8>>;
set: func(key: string, value: list<u8>) -> result<_, string>;
delete: func(key: string) -> result<_, string>;
exists: func(key: string) -> bool;
list-keys: func(prefix: string) -> list<string>;
}
}
world kv-http-service {
export wasi:http/incoming-handler@0.2.3;
import wasi:keyvalue/store@0.2.0-draft2;
import wasi:http/types@0.2.3;
}
キーバリューHTTPサービス実装
// src/kv_service.rs
wit_bindgen::generate!({
path: "../wit/kv-service.wit",
world: "kv-http-service",
generate_all,
});
use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::IncomingRequest;
use wasi::keyvalue::store::OpenOptions;
struct KvHttpService;
impl Guest for KvHttpService {
fn handle(request: IncomingRequest) {
let method = request.method().to_string();
let path = request.path_with_query().unwrap_or("/".to_string());
let body_bytes = read_request_body(&request);
let store = wasi::keyvalue::store::open("default", OpenOptions {
create_if_missing: true,
}).expect("Failed to open KV store");
match (method.as_str(), path.as_str()) {
("GET", p) if p.starts_with("/api/kv/") => {
let key = p.trim_start_matches("/api/kv/");
match store.get(key) {
Some(value) => send_json_response(200, &format!(
r#"{{"key":"{}","value":"{}"}}"#,
key,
String::from_utf8_lossy(&value)
)),
None => send_json_response(404, &format!(
r#"{{"error":"key '{}' not found"}}"#, key
)),
}
}
("PUT", p) if p.starts_with("/api/kv/") => {
let key = p.trim_start_matches("/api/kv/");
match store.set(key, &body_bytes) {
Ok(_) => send_json_response(200, r#"{"stored":true}"#),
Err(e) => send_json_response(500, &format!(r#"{{"error":"{}"}}"#, e)),
}
}
("DELETE", p) if p.starts_with("/api/kv/") => {
let key = p.trim_start_matches("/api/kv/");
match store.delete(key) {
Ok(_) => send_json_response(200, r#"{"deleted":true}"#),
Err(e) => send_json_response(500, &format!(r#"{{"error":"{}"}}"#, e)),
}
}
("GET", "/api/kv") => {
let keys = store.list_keys("");
let keys_json: Vec<String> = keys.iter()
.map(|k| format!(r#""{}""#, k))
.collect();
send_json_response(200, &format!(
r#"{{"keys":[{}]}}"#, keys_json.join(",")
));
}
_ => send_json_response(404, r#"{"error":"not found"}"#),
}
}
}
fn read_request_body(request: &IncomingRequest) -> Vec<u8> {
let body = request.body().unwrap();
let stream = body.stream().unwrap();
let mut buf = Vec::new();
loop {
match stream.blocking_read(4096) {
Ok(chunk) => {
if chunk.is_empty() { break; }
buf.extend_from_slice(&chunk);
}
Err(_) => break,
}
}
buf
}
fn send_json_response(status: u16, body: &str) {
let response = wasi::http::types::OutgoingResponse::new(
wasi::http::types::Fields::from_list(&[
("content-type".to_string(), "application/json".to_string()),
]).unwrap()
);
response.set_status_code(status).unwrap();
let outgoing_body = response.body().unwrap();
let stream = outgoing_body.write().unwrap();
stream.blocking_write_and_flush(body.as_bytes()).unwrap();
wasi::http::types::OutgoingBody::finish(outgoing_body, None).unwrap();
}
export_kv_http_service!(KvHttpService);
5つのよくある落とし穴と解決策
落とし穴1:旧版wasm32-wasiコンパイルターゲットの使用
# ❌ 誤り:Preview1ターゲットを使用
# cargo build --target wasm32-wasi
# ✅ 正しい:Preview2ターゲットを使用
# .cargo/config.toml
[build]
target = "wasm32-wasip2"
[target.wasm32-wasip2]
runner = "wasmtime run --wasm component-model=y"
落とし穴2:HTTP Handlerでリクエストボディストリームを正しく処理していない
// ❌ 誤り:bodyを一括で読み取れると仮定
let body: Vec<u8> = request.body().unwrap().stream().unwrap().blocking_read(999999).unwrap();
// ✅ 正しい:ストリームが閉じるまでループで読み取り
fn read_full_body(stream: &wasi::io::streams::InputStream) -> Vec<u8> {
let mut buf = Vec::new();
loop {
match stream.blocking_read(4096) {
Ok(chunk) if chunk.is_empty() => break,
Ok(chunk) => buf.extend_from_slice(&chunk),
Err(wasi::io::streams::StreamError::Closed) => break,
Err(e) => { tracing::error!("Stream error: {:?}", e); break; }
}
}
buf
}
落とし穴3:アウトバウンドHTTPでallowed_outbound_hostsを設定していない
# ❌ 誤り:アウトバウンドホストホワイトリストを宣言しておらず、リクエストが拒否される
# ✅ 正しい:spin.tomlまたはランタイム設定で宣言
[component.http-api]
allowed_outbound_hosts = ["https://api.example.com", "https://hooks.example.com"]
落とし穴4:コンポーネント間通信でWITインターフェースの代わりに共有メモリを使用
// ❌ 誤り:合意したオフセットで共有線形メモリを通じてデータを渡す
// ✅ 正しい:WITインターフェースで型安全な通信を行う
// WITで共有インターフェースを定義し、コンポーネント間でimport/exportで相互呼び出し
落とし穴5:キーバリューストアで並行競合を処理していない
// ❌ 誤り:直接setで上書き、並行保護なし
store.set("counter", &(count + 1).to_le_bytes());
// ✅ 正しい:CASセマンティクスまたはアトミック操作を使用
loop {
let current = store.get("counter").and_then(|v| {
let arr: [u8; 8] = v[..8].try_into().ok()?;
Some(u64::from_le_bytes(arr))
}).unwrap_or(0);
let new_value = current + 1;
store.set("counter", &new_value.to_le_bytes()).unwrap();
break;
}
10のよくあるエラートラブルシューティング
| # | エラーメッセージ | 原因 | 解決方法 |
|---|---|---|---|
| 1 | error: target wasm32-wasip2 not found |
Preview2ターゲットが未インストール | rustup target add wasm32-wasip2を実行 |
| 2 | component is not a valid component |
ビルド成果物がCore WasmでComponentではない | wasm-component-ldリンカーの使用を確認 |
| 3 | unknown import: wasi:http/incoming-handler |
ランタイムがPreview2をサポートしていない | wasmtimeをv20+にアップグレードまたはSpin v3+を使用 |
| 4 | outbound HTTP request failed: denied |
アウトバウンドホストホワイトリストが未設定 | spin.tomlにallowed_outbound_hostsを追加 |
| 5 | trap: wasm trap: out of bounds memory access |
リクエストボディの読み取りで境界外アクセス | ループ読み取りを使用、ストリーム終了条件を確認 |
| 6 | failed to open KV store: not available |
ランタイムがKVストア実装を提供していない | SpinのKVストアバックエンドまたはwasmtimeのKVプラグインを設定 |
| 7 | wasi:http/types version mismatch |
WITインターフェースバージョンがランタイムと不一致 | WIT依存バージョンを0.2.3に統一 |
| 8 | stream error: Closed |
リクエストボディを全て読み取った後も読み取りを継続 | StreamError::Closedブランチを確認 |
| 9 | component instantiation failed: missing export |
コンポーネントが必要なインターフェースをエクスポートしていない | wit_bindgen::generate!のworld設定を確認 |
| 10 | HTTP handler timeout: exceeded 30s |
リクエスト処理がタイムアウト | アウトバウンドリクエストを最適化またはタイムアウト設定を増加 |
高度な最適化テクニック
1. ゼロコピーストリーミングリクエストボディ処理
use wasi::http::types::IncomingRequest;
fn stream_response(request: &IncomingRequest) {
let body = request.body().unwrap();
let stream = body.stream().unwrap();
let mut total = 0u64;
loop {
match stream.blocking_read(8192) {
Ok(chunk) if chunk.is_empty() => break,
Ok(chunk) => {
total += chunk.len() as u64;
process_chunk(&chunk);
}
Err(_) => break,
}
}
}
fn process_chunk(data: &[u8]) {
}
2. コンポーネントキャッシュとプレヒート
use wasmtime::component::Component;
use wasmtime::Engine;
use std::collections::HashMap;
pub struct ComponentPreheater {
engine: Engine,
cache: HashMap<String, Component>,
}
impl ComponentPreheater {
pub fn new(engine: Engine) -> Self {
let mut preheater = Self { engine, cache: HashMap::new() };
preheater.preload_components();
preheater
}
fn preload_components(&mut self) {
let components = ["api-gateway", "auth-service", "user-service"];
for name in &components {
let path = format!("components/{}.wasm", name);
if let Ok(component) = Component::from_file(&self.engine, &path) {
self.cache.insert(name.to_string(), component);
}
}
}
}
3. 構造化ログとトレーシング
pub fn log_request(method: &str, path: &str, status: u16, duration_us: u64) {
wasi::logging::logging::log(
wasi::logging::logging::Level::Info,
&format!(
r#"{{"method":"{}","path":"{}","status":{},"duration_us":{}}}"#,
method, path, status, duration_us
),
);
}
4. エレガントなエラーレスポンスカプセル化
pub fn error_response(status: u16, code: &str, message: &str) {
let body = format!(r#"{{"error":{{"code":"{}","message":"{}"}}}}"#, code, message);
send_json_response(status, &body);
}
pub fn bad_request(message: &str) { error_response(400, "BAD_REQUEST", message); }
pub fn unauthorized(message: &str) { error_response(401, "UNAUTHORIZED", message); }
pub fn not_found(message: &str) { error_response(404, "NOT_FOUND", message); }
pub fn internal_error(message: &str) { error_response(500, "INTERNAL_ERROR", message); }
比較分析
| 次元 | WASI Preview2 | WASI Preview1 | POSIX Socket | Node.js HTTP |
|---|---|---|---|---|
| HTTPサーバー | ✅ネイティブHandler | ❌ホストプロキシが必要 | ✅ネイティブSocket | ✅ネイティブhttpモジュール |
| アウトバウンドリクエスト | ✅ネイティブoutgoing-handler | ❌ホスト転送が必要 | ✅ネイティブSocket | ✅fetch/http |
| サンドボックス分離 | ✅ケイパビリティセキュリティモデル | ✅限定的な分離 | ❌プロセスレベル | ❌プロセスレベル |
| 言語間対応 | ✅任意の言語→Wasm | ⚠️限定的 | ❌C ABI | ❌JavaScript |
| コールドスタート | ⭐<1ms | ⭐<1ms | ⭐遅い(プロセス) | ⭐~100ms |
| メモリオーバーヘッド | ⭐非常に低い | ⭐非常に低い | ⭐高い | ⭐中程度 |
| 型安全性 | ✅WIT強い型付け | ❌なし | ❌なし | ⚠️TypeScript |
| エコシステム成熟度 | ⭐2026年成長中 | ⭐安定 | ⭐非常に成熟 | ⭐非常に成熟 |
| デバッグツール | ⚠️限定的 | ⚠️限定的 | ✅包括的 | ✅包括的 |
| 状態管理 | ✅wasi:keyvalue | ❌ファイルシステムのみ | ✅任意 | ✅任意 |
選択の推奨
- WASI Preview2:サンドボックス分離、言語間対応、エッジデプロイが必要なHTTPマイクロサービス(推奨第一選択)
- WASI Preview1:ファイルシステムのみ必要で、ネットワーク要件のないCLI Wasmツール
- POSIX Socket:極限のパフォーマンス要件、ネイティブシステムレベルネットワーキング
- Node.js HTTP:迅速な開発、豊富なnpmエコシステム、サンドボックス不要
まとめと展望
WASI Preview2は2026年、Wasmに「自力でネットワークにアクセスする」能力をようやく与えました。5つのコアパターンの実践パイプライン:HTTP Handlerがインバウンドリクエストを処理→outgoing-handlerがアウトバウンド呼び出しを発行→WITインターフェースがコンポーネント間通信を実現→Spinフレームワークがデプロイを簡素化→キーバリューストアが状態を管理。WASI Preview2ネットワーキングの本質は「WasmにSocketを追加する」ことではなく、「コンポーネントモデルとケイパビリティセキュリティでネットワークサービスのセキュリティ境界を再定義する」ことです。
今後の展望:WASI Preview2のSocket APIはTCP/UDPネイティブプログラミングをサポートし、非同期IOモデルはPoll APIから完全なasync/awaitへ進化し、コンポーネントモデルのストリーミング(wasi:io/streams)はWebSocketとgRPC双方向ストリームをサポートする予定です。Wasmネットワーキングの黄金時代はまだ始まったばかりです。
オンラインツール推奨
- JSONフォーマッター:/ja/json/format — HTTPリクエストとレスポンスJSONのデバッグ
- Base64コーデック:/ja/encode/base64 — Wasmコンポーネント設定データのエンコード
- ハッシュ計算:/ja/encode/hash — コンポーネントチェックサムの計算
- cURL→コード変換:/ja/dev/curl-to-code — cURLコマンドをRust HTTPコードに変換
関連記事
- Rust WASIコンポーネントモデルプラグインシステム — コンポーネントモデルの深い実践
- WasmエッジServerlessデプロイ — エッジデプロイの完全ガイド
- Wasmクラウドエッジ協調アーキテクチャ — クラウドエッジ協調ネットワークアーキテクチャ
外部リファレンス
まとめ:WASI Preview2ネットワーキングの5つのコアパターンは、HTTP Handlerからキーバリューストアまで、Wasm HTTPサービス開発の完全なパイプラインをカバーしています。コア原則を覚えておいてください:WITでインターフェース契約を定義し、コンポーネントモデルで型安全な通信を実現し、ケイパビリティセキュリティモデルでネットワーク権限を制限し、Spinでデプロイ運用を簡素化する。Wasmネットワーキングの未来は、すべてのコンポーネントが安全なマイクロサービスになることです。
ブラウザローカルツールを無料で試す →