📌 الدليل الرئيسي: WebAssembly في الإنتاج
تشغيل WebAssembly جانب الخادم يُغَيِّر القواعد مقارنة بالمتصفّح. ننتقل من بيئة wasm32-unknown-unknown، حيث كلّ شيء يمرّ عبر JavaScript، إلى هدف WASI حيث المكوّن يمكنه قراءة ملفّ، كشف خدمة HTTP، واستقبال قدرات صريحة من المضيف. Wasmtime، تصونه Bytecode Alliance، صار runtime مرجع نموذج المكوّنات: يدعم WASI 0.2 و0.3، يملك وضع AOT، يُضَمِّن debugger GDB، ويُوَزَّع كـ binary مستقلّ وcrate Rust.
يبني هذا الدليل مشروع WASI كامل: نُجَمِّع برنامج Rust إلى wasm32-wasip2، نُنَفِّذه عبر CLI Wasmtime، نُضيف endpoint HTTP عبر wasi-http، ثم نُضَمِّن المجموع في binary مضيف Rust يقود instantiation ويُقَيِّد القدرات.
الخطوة 1 — تثبيت Wasmtime وهدف Rust WASI
curl https://wasmtime.dev/install.sh -sSf | bash
exec $SHELL
wasmtime --version
المتوقّع 44.0.0 أو أحدث.
rustup target add wasm32-wasip2
rustup show
الهدف wasm32-wasip2 تيير 2 منذ Rust 1.82 ويُنتج مباشرة مكوّن WASI 0.2.
الخطوة 2 — أوّل برنامج WASI في Rust
cargo new --bin hello-wasi
cd hello-wasi
// src/main.rs
use std::env;
fn main() {
let name = env::var("NAME").unwrap_or_else(|_| "anonyme".to_string());
println!("Bonjour, {} ! Le binaire tourne dans Wasmtime.", name);
}
cargo build --target wasm32-wasip2 --release
ls target/wasm32-wasip2/release/hello-wasi.wasm
الـ binary يزن نمطيًّا 100-200 ko في release. file يُرجع « WebAssembly (wasm) binary module ».
الخطوة 3 — تنفيذ module عبر CLI Wasmtime
wasmtime target/wasm32-wasip2/release/hello-wasi.wasm
الخرج « Bonjour, anonyme! » — متغيّر NAME غير مُوَفَّر. نمنح الآن قدرة env صراحة:
wasmtime --env NAME=Mariama target/wasm32-wasip2/release/hello-wasi.wasm
هذه المرّة « Bonjour, Mariama! ». الـ --env KEY=VALUE قابل للتكرار. الفرق مع binary أصلي: كود WebAssembly لا يصل لأيّ متغيّر إلّا ما وَصَّله المضيف صراحة.
الخطوة 4 — منح وصول ملفّي محدود
// src/main.rs
use std::env;
use std::fs;
fn main() {
let path = env::var("INPUT").unwrap_or_else(|_| "/data/message.txt".into());
match fs::read_to_string(&path) {
Ok(s) => println!("Contenu : {}", s.trim()),
Err(e) => eprintln!("Erreur lecture {} : {}", path, e),
}
}
cargo build --target wasm32-wasip2 --release
mkdir -p data
echo "WebAssembly serveur" > data/message.txt
wasmtime --dir ./data::/data --env INPUT=/data/message.txt target/wasm32-wasip2/release/hello-wasi.wasm
الخرج « Contenu : WebAssembly serveur ». إن أزلت --dir، الاستدعاء يفشل بـ « No such file or directory » رغم وجود الملفّ: module لا يرى المسار حتى يُفتَح مسبقًا.
الخطوة 5 — كشف خدمة HTTP عبر wasi-http
WASI 0.2 يضمّ واجهة HTTP قياسية wasi:http/proxy. Wasmtime يعرف تنفيذها عبر wasmtime serve منذ Wasmtime 18.0.0.
cargo install cargo-component
cargo component new --lib http-service --proxy
cd http-service
use wasi::http::types::{Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam};
use wasi::exports::http::incoming_handler::Guest;
struct Component;
impl Guest for Component {
fn handle(_req: IncomingRequest, out: ResponseOutparam) {
let headers = Fields::new();
headers.set(&"content-type".to_string(), &[b"application/json".to_vec()]).unwrap();
let resp = OutgoingResponse::new(headers);
resp.set_status_code(200).unwrap();
let body = resp.body().unwrap();
ResponseOutparam::set(out, Ok(resp));
let stream = body.write().unwrap();
stream.blocking_write_and_flush(br#"{"status":"ok","runtime":"wasmtime"}"#).unwrap();
drop(stream);
OutgoingBody::finish(body, None).unwrap();
}
}
wasi::http::proxy::export!(Component);
cargo component build --release
wasmtime serve --addr 127.0.0.1:8080 target/wasm32-wasip2/release/http_service.wasm
curl -s http://127.0.0.1:8080/ping
# {"status":"ok","runtime":"wasmtime"}
خدمة HTTP كاملة، وظيفية، دون خادم Node أو Go: Wasmtime يتكلّم HTTP ويُمَرِّر الطلب للمكوّن.
الخطوة 6 — تضمين Wasmtime في مضيف Rust
cargo new --bin wasm-host
cd wasm-host
cargo add wasmtime@44 wasmtime-wasi@44 anyhow
use anyhow::Result;
use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::{Config, Engine, Store};
use wasmtime_wasi::p2::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};
struct AppState { ctx: WasiCtx, table: ResourceTable }
impl WasiView for AppState {
fn ctx(&mut self) -> WasiCtxView<'_> {
WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
}
}
fn main() -> Result<()> {
let mut cfg = Config::new();
cfg.wasm_component_model(true);
let engine = Engine::new(&cfg)?;
let component = Component::from_file(&engine, "hello.wasm")?;
let mut linker = Linker::::new(&engine);
wasmtime_wasi::p2::add_to_linker_sync(&mut linker)?;
let ctx = WasiCtxBuilder::new().inherit_stdio().env("NAME", "hôte").build();
let mut store = Store::new(&engine, AppState { ctx, table: ResourceTable::new() });
let cmd = wasmtime_wasi::p2::bindings::sync::Command::instantiate(&mut store, &component, &linker)?;
cmd.wasi_cli_run().call_run(&mut store)?.map_err(|()| anyhow::anyhow!("module failed"))?;
Ok(())
}
هذا الهيكل يُحَمِّل مكوّن CLI hello.wasm، يمنحه stdio المضيف ومتغيّر NAME، ثم يستدعي run. أيّ قدرة غير مُدرَجة (ملفّات، sockets، ساعة) مرفوضة افتراضيًّا. عكس std::process::Command الذي يَرِث كلّ شيء.
الخطوة 7 — وصف الواجهات الخاصّة بـ WIT
package itskc:demo;
interface fx {
record amount { value: f64, currency: string }
record converted { usd: f64, rate: f64 }
convert: func(input: amount) -> result;
}
world fx-service {
export fx;
}
في مكوّن Rust، cargo-component يُوَلِّد traits تلقائيًّا. في المضيف، يمكن استهلاك نفس ملفّ WIT لتحميل عدّة مكوّنات متزامنة بنفس الضمانات المكتَّبة. الـ binary المُنتج قابل للتركيب: نُسَلسِل مكوّنَين خرج أحدهما يُغَذِّي دخل الآخر.
الخطوة 8 — تقييد القدرات في الإنتاج
المبدأ التوجيهي: لا تمنح إلّا القدرات الضرورية بدقّة.
preopened_dir(path, guest_path, dir_perms, file_perms) يكشف مجلّدًا بصلاحيات منفصلة. يمكن السماح بالقراءة فقط على مجلّد إعدادات والكتابة على مجلّد scratch.
env(key, value) وargs(values) يكشفان فقط المتغيّرات والوسائط المختارة. المكوّنات لا تستطيع المرور على بيئة المضيف.
inherit_stdio() أو stdout(...)/stderr(...) يتحكّمان في وجهة كتابات المكوّن. يمكن إعادة توجيهها إلى buffer ذاكرة للتدقيق.
للشبكة، WASI 0.2 يُدخل wasi-sockets. Wasmtime يكشف allow_ip_name_lookup، socket_addr_check. رفض الكلّ افتراضيًّا ثم whitelist موقف الأمن الموصى به.
Wasmtime يكشف حدّين كمّيّين حرجَين في الإنتاج. حدّ الذاكرة عبر StoreLimitsBuilder::memory_size() يسقف linear memory. حدّ fuel عبر Store::set_fuel(n) يلتقط زمن CPU كتعليمات WebAssembly مُنَفَّذة؛ حين يتجاوز module، Wasmtime يرفع trap نظيفًا. مُجَمَّعَين، يُتيحان تنفيذ كود عدائي بضمانات صلبة لا يُوَفِّرها container Linux دون cgroups إضافية.
الخطوة 9 — أخطاء شائعة
| العَرَض | السبب | الحلّ |
|---|---|---|
| error: unknown target « wasm32-wasip2 » | Rust toolchain قبل 1.82 | rustup update stable |
| Permission denied على القراءة | preopen --dir غائب |
أضف --dir HOST::GUEST |
wasmtime serve: unknown subcommand |
Wasmtime < 18.0.0 | حدِّث إلى 44.x |
| مكوّن مرفوض من Wasmtime | مُجَمَّع في wasm32-wasip1 بدل wasip2 |
أعد الترجمة بالهدف الصحيح |
Crash traps: out of fuel |
حدّ fuel منخفض | زد عبر Store::set_fuel أو عطِّل |
| زمن instantiation مرتفع | لا precompile AOT | wasmtime compile + cache قرصي |
للتعمّق
- نشر module WebAssembly Rust على Cloudflare Workers
- عزل agent IA في sandbox WebAssembly
- الدليل الرئيسي WebAssembly في الإنتاج
مصادر رسمية
docs.wasmtime.devdocs.wasmtime.dev/cli-optionscomponent-model.bytecodealliance.orggithub.com/bytecodealliance/cargo-componentgithub.com/WebAssembly/wasi-httpdocs.wasmtime.dev/api/wasmtimewasi.dev/interfaces
مع Wasmtime، نملك بيئة تنفيذ WebAssembly جانب الخادم كاملة: binaries صغيرة، instantiation سريع، قدرات صريحة. الخطوة التالية تَصنيع النشر — edge بـ Cloudflare Workers أو sandbox صارمة لكود مستخدم.