Plugins WASM
Le niveau WASM exécute des plugins en tant que modules WebAssembly dans le runtime wasmtime. Il offre des performances quasi natives avec une forte isolation de la mémoire et des limites de ressources déterministes via les budgets de carburant. Il s'agit d'une fonctionnalité Pro.
Comment ça marche#
- Vous écrivez votre plugin dans Rust (ou Go/C) et le compilez dans un module
.wasmciblantwasm32-wasi. - bext charge le module via wasmtime au démarrage du serveur.
- Chaque appel de cycle de vie consomme du « carburant » – une abstraction de temps d’eau qui limite les calculs. Lorsque le carburant est épuisé, l'appel est terminé.
- Les fonctions hôtes du module d'importation
"bext"fournissent le stockage, HTTP, les métriques et l'accès à la file d'attente.
Budgets de carburant#
Chaque hook de cycle de vie a un budget de carburant par défaut. Une unité de carburant correspond à peu près à une instruction WASM.
| Crochet | Carburant par défaut | Durée approximative du mur |
|---|---|---|
on_server_start |
500,000,000 | ~500 ms |
on_request_complete |
100,000,000 | ~100 ms |
on_cache_write |
50,000,000 | ~50 ms |
on_cache_invalidate |
50,000,000 | ~50 ms |
on_reload |
200,000,000 | ~200 ms |
cleanup |
100,000,000 | ~100 ms |
Remplacer les budgets dans bext.config.toml :
[plugins.my-wasm-plugin]
sandbox = "wasm"
path = "./plugins/my-plugin.wasm"
[plugins.my-wasm-plugin.fuel]
on_server_start = 1_000_000_000
on_request_complete = 200_000_000
Importations de fonctions hôtes#
Les plugins WASM accèdent aux fonctionnalités de l'hôte via des fonctions importées du module "bext". Tous les types complexes sont transmis sous forme de chaînes codées JSON via des paires (ptr, len).
Fonctions hôtes disponibles#
| Fonction | Signature | Description |
|---|---|---|
bext.log |
(level_ptr, level_len, msg_ptr, msg_len) |
Journalisation structurée |
bext.storage_get |
(key_ptr, key_len) -> (ptr, len) |
Lire à partir du magasin KV limité |
bext.storage_set |
(key_ptr, key_len, val_ptr, val_len) -> i32 |
Écrivez au magasin KV |
bext.storage_delete |
(key_ptr, key_len) -> i32 |
Supprimer l'entrée KV |
bext.http_fetch |
(req_ptr, req_len) -> (ptr, len) |
Client HTTP (débit limité) |
bext.get_config |
(key_ptr, key_len) -> (ptr, len) |
Lire la valeur de configuration du plugin |
bext.emit_metric |
(name_ptr, name_len, value: f64, tags_ptr, tags_len) |
Émettre une métrique |
La requête et la réponse http_fetch utilisent ce format JSON :
// Request
{ "url": "https://api.example.com/data", "method": "POST",
"headers": [["Content-Type", "application/json"]], "body": "{}" }
// Response
{ "status": 200,
"headers": [["content-type", "application/json"]],
"body": "{\"result\": \"ok\"}" }
Autorisations du bac à sable#
[plugins.my-wasm-plugin.permissions]
allowed_urls = ["https://api.example.com/*"]
storage_quota_kb = 1024
max_fetch_per_minute = 30
kv_namespaces = ["analytics"]
queue_names = ["events"]
- Allowed_urls — modèles globaux pour HTTP sortant. Vide = pas d'accès au réseau.
- storage_quota_kb — stockage KV maximum par plugin (par défaut : 1 024 Ko).
- max_fetch_per_minute — limite de débit de compartiment de jetons pour les récupérations HTTP (par défaut : 30).
- kv_namespaces — espaces de noms KV partagés auxquels le plugin peut accéder. Vide = limité au plugin uniquement.
- queue_names — files d'attente de messages sur lesquelles le plugin peut publier ou consommer.
Construire un plugin Rust WASM#
1. Créez le projet#
cargo new --lib my-analytics-plugin
cd my-analytics-plugin
2. Configurez Cargo.toml#
[package]
name = "my-analytics-plugin"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
bext-plugin-api = "0.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
3. Implémentez le trait LifecyclePlugin#
use bext_plugin_api::lifecycle::LifecyclePlugin;
use serde::Deserialize;
#[derive(Deserialize)]
struct Config {
endpoint: String,
batch_size: usize,
}
struct AnalyticsPlugin {
config: Option<Config>,
buffer: Vec<String>,
}
impl LifecyclePlugin for AnalyticsPlugin {
fn name(&self) -> &str {
"my-analytics"
}
fn priority(&self) -> u32 {
1000
}
fn on_server_start(&self, config_json: &str) -> Result<(), String> {
let config: Config = serde_json::from_str(config_json)
.map_err(|e| format!("parse config: {e}"))?;
// Store config for later use
bext::log("info", &format!("Analytics endpoint: {}", config.endpoint));
Ok(())
}
fn on_request_complete(&self, event_json: &str) -> Result<(), String> {
// Buffer events, flush when batch is full
bext::storage_set("pending", event_json)?;
bext::emit_metric("analytics_events", 1.0, &[("source", "wasm")]);
Ok(())
}
fn cleanup(&self) -> Result<(), String> {
bext::log("info", "Flushing remaining analytics events");
Ok(())
}
}
4. Compiler vers WASM#
cargo build --target wasm32-wasi --release
cp target/wasm32-wasi/release/my_analytics_plugin.wasm ./plugin.wasm
5. Configurer dans bext#
[plugins.my-analytics]
sandbox = "wasm"
path = "./plugins/my-analytics/plugin.wasm"
[plugins.my-analytics.config]
endpoint = "https://analytics.example.com/ingest"
batch_size = 100
[plugins.my-analytics.permissions]
allowed_urls = ["https://analytics.example.com/*"]
max_fetch_per_minute = 60
Débogage des plugins WASM#
Activez la journalisation WASI pour voir stdout/stderr depuis votre module WASM :
BEXT_PLUGIN_LOG=debug bext dev
Les interruptions wasmtime (manque de carburant, violations de mémoire, instructions inaccessibles) sont enregistrées avec une trace de pile complète lorsque la journalisation du débogage est activée.
Pour profiler la consommation de carburant :
bext plugin stats my-analytics
Cela rapporte la consommation de carburant par crochet, le nombre d'appels et le temps d'exécution moyen.
Plugins Go et C#
Tout langage compilé en wasm32-wasi fonctionne. Pour Go, utilisez TinyGo :
tinygo build -o plugin.wasm -target=wasi ./main.go
Pour C, utilisez wasi-sdk :
$WASI_SDK_PATH/bin/clang --sysroot=$WASI_SDK_PATH/share/wasi-sysroot \
-o plugin.wasm plugin.c
La fonction hôte ABI est la même quelle que soit la langue source : chaînes JSON transmises via des paires (ptr, len).
L’épuisement du carburant interrompt l’appel du hook et consigne une erreur, mais ne provoque ni arrêt ni redémarrage de l’extension. L’appel suivant dispose d’un nouveau budget. Dimensionnez vos budgets avec prudence et utilisez bext plugin stats pour mesurer l’utilisation réelle avant de les augmenter en production.
Si vous avez besoin de Python dans votre plugin WASM (par exemple pour exécuter un pipeline de données), consultez Plugins Python — Python s'exécute en tant que sous-mode Pyodide dans le même environnement d'exécution bext-plugin-wasm.
En rapport#
- Présentation du système de plugins — Architecture sandbox à trois niveaux et types de fonctionnalités
- Construire un plugin — échafaudez et testez votre premier plugin
- Plugins Python — Python via Pyodide dans le runtime WASM
- Plugins V8 — Alternative JavaScript à WASM pour le middleware hot-path
- Plugins nsjail — lorsque vous avez besoin d'un espace utilisateur Linux complet au lieu de WASM
- Édition — partagez votre plugin sur le registre
Links#
- WebAssembly WASI aperçu 1 — l'appel système ABI
wasm32-wasicible - c'était l'heure — le runtime WebAssembly bext intègre
- PetitGo — Compilateur Go qui cible
wasm32-wasi