ENbext.dev
DocumentationExtensions

Traduction automatique depuis l’anglais. Consulter l’original.

Sur cette page

Plugins QuickJS

Le niveau QuickJS est le bac à sable du plugin par défaut. Il intègre le moteur QuickJS JavaScript via rquickjs, donnant à chaque plugin son propre runtime isolé avec des limites de ressources strictes et aucun accès direct au système de fichiers, au réseau ou au processus hôte.

Caractéristiques d'exécution#

Propriété Défaut Configurable
Limite de mémoire 64 Mo sandbox.max_memory_mb
Taille de la pile 1 Mo Fixé
Délai d'expiration de l'horloge murale 10 secondes par appel sandbox.max_time_secs
Heure de démarrage < 1 ms N/A
Frais généraux par instance < 1 Mo N/A

Le moteur d'exécution applique ces limites via les hooks d'allocation de mémoire intégrés de QuickJS et un gestionnaire d'interruption qui vérifie l'heure de l'horloge murale après chaque N instructions de bytecode. Si un plugin dépasse sa mémoire ou son budget temps, l'appel renvoie une erreur et le plugin n'est pas terminé - il peut toujours gérer les requêtes ultérieures.

Globales disponibles#

Les plugins QuickJS s'exécutent dans un environnement restreint. Les globaux suivants sont disponibles :

console#

Journalisation structurée standard acheminée vers le pipeline de traçage bext :

javascript
console.log("info message");       // level=INFO
console.info("info message");      // level=INFO
console.warn("warning message");   // level=WARN
console.error("error message");    // level=ERROR
console.debug("debug message");    // level=DEBUG (only with BEXT_PLUGIN_LOG=debug)

bext.stockage#

Stockage clé-valeur limité à chaque plug-in et sauvegardé par des fichiers sur le disque. Les clés sont nettoyées pour empêcher la traversée du chemin (pas de .., /, \ ou octets nuls).

javascript
// Write a value (quota enforced via sandbox.storage_quota_kb)
bext.storage.set("last_seen", Date.now().toString());

// Read a value (returns null if not found)
const val = bext.storage.get("last_seen");

// Delete a value
bext.storage.delete("last_seen");

Le stockage est conservé lors des redémarrages du serveur. Les données de chaque plugin sont isolées dans son propre répertoire sous data/plugins/<plugin-name>/.

bext.fetch#

Client HTTP à débit limité et liste autorisée d'URL. Seules les URL correspondant aux globs permissions.allowed_urls sont autorisées. Une liste blanche vide signifie aucun accès HTTP.

javascript
// plugin.toml: permissions.allowed_urls = ["https://api.example.com/*"]

const response = bext.fetch("https://api.example.com/v1/data", {
  method: "POST",
  headers: [["Content-Type", "application/json"]],
  body: JSON.stringify({ key: "value" }),
});

console.log(response.status);  // 200
console.log(response.body);    // response body as string

Le taux de récupération est plafonné à permissions.max_fetch_per_minute (par défaut : 30). Le dépassement de la limite renvoie une erreur.

bext.config#

Accès en lecture seule à la configuration du plugin depuis bext.config.toml :

javascript
// bext.config.toml:
// [plugins.my-plugin.config]
// threshold = 100
// regions = ["us", "eu"]

const threshold = bext.config.threshold;  // 100
const regions = bext.config.regions;      // ["us", "eu"]

bext.métrique#

Émettez des métriques personnalisées au pipeline d'observabilité bext (Prometheus, OpenTelemetry ) :

javascript
bext.metric("requests_blocked", 1, [
  ["reason", "geo"],
  ["country", "CN"],
]);

Ce qui n'est PAS disponible#

Le bac à sable QuickJS exclut délibérément :

  • Modules intégrés require() et Node.js
  • globalThis.fetch (utilisez plutôt bext.fetch)
  • setTimeout, setInterval, queueMicrotask
  • Deno, Bun, process globaux
  • Accès au système de fichiers ( fs, path )
  • Constructeur eval() et Function() (génération de code désactivée)
  • WebAssembly (utilisez plutôt le niveau WASM)

Exemple complet : plugin de géo-redirection#

Ce plugin redirige les utilisateurs vers un sous-domaine spécifique à une région en fonction d'un en-tête défini par votre CDN :

javascript
// src/index.js
const REGION_MAP = {};

export function onServerStart(config) {
  // Build region -> URL map from config
  for (const [region, url] of Object.entries(config.regions)) {
    REGION_MAP[region] = url;
  }
  console.log(`Geo-redirect loaded with ${Object.keys(REGION_MAP).length} regions`);
}

export function onRequest(ctx) {
  // Read region from CDN header
  const regionHeader = ctx.headers.find(([k]) => k === "x-geo-region");
  if (!regionHeader) {
    return { action: "continue" };
  }

  const region = regionHeader[1].toLowerCase();
  const redirectUrl = REGION_MAP[region];

  if (!redirectUrl) {
    return { action: "continue" };
  }

  // Don't redirect API calls or static assets
  if (ctx.path.startsWith("/api/") || ctx.path.startsWith("/_next/")) {
    return { action: "continue" };
  }

  // Check if user already dismissed the redirect
  const dismissed = bext.storage.get(`dismissed:${ctx.peer_ip}`);
  if (dismissed === "true") {
    return { action: "continue" };
  }

  return {
    action: "respond",
    status: 302,
    headers: [
      ["Location", redirectUrl + ctx.path],
      ["Cache-Control", "private, no-cache"],
    ],
    body: "",
  };
}

export function onResponse(ctx, headers) {
  // Add Vary header so CDN caches per-region
  headers.headers.push(["Vary", "X-Geo-Region"]);
}

Configuration dans bext.config.toml :

toml
[plugins.geo-redirect]
sandbox = "quickjs"
path = "./plugins/geo-redirect/src/index.js"

[plugins.geo-redirect.config.regions]
eu = "https://eu.example.com"
ap = "https://ap.example.com"
us = "https://us.example.com"

Considérations relatives aux performances#

QuickJS est un interpréteur, pas un JIT. Pour un middleware hot-path qui s'exécute à chaque requête, gardez votre gestionnaire onRequest simple :

  • Précalculez les tables de recherche dans onServerStart plutôt que de les reconstruire par requête.
  • Utilisez bext.storage avec parcimonie dans le chemin de la requête : chaque appel implique une lecture du disque.
  • Pour les travaux gourmands en CPU (cryptographie, analyse JSON volumineuse), pensez plutôt au Niveau WASM.

La surcharge typique de onRequest est de 50 à 200 microsecondes en fonction de la complexité du gestionnaire.

Remarque

Les clés bext.storage sont nettoyées pour empêcher la traversée du chemin : toute clé contenant .., /, \ ou des octets nuls est rejetée. Concevez votre espace de noms de clé en conséquence (par exemple, dismissed:1.2.3.4 convient ; un chemin de fichier brut ne l'est pas).

Conseil

QuickJS désactive eval() et le constructeur Function(). Si votre plugin nécessite l'exécution de code dynamique, utilisez plutôt le Niveau V8, qui prend en charge l'intégralité du JavaScript.

En rapport#

  • Présentation du système de plugins — niveaux sandbox, types de fonctionnalités et priorité du middleware
  • Construire un plugin - procédure pas à pas complète à l'aide de ce bac à sable
  • Plugins V8 — alternative JS à plus haut débit pour le middleware hot-path
  • Plugins WASM — langages compilés pour les travaux gourmands en CPU
  • Plugins Lua — une autre option de script légère avec un encombrement encore plus réduit
  • Édition — partagez votre plugin QuickJS sur le registre
Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.