ENbext.dev

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

Sur cette page

Actions du serveur

Les actions du serveur bext sont des fonctions exportées simples dans des fichiers src/actions/ marqués de la directive "use server". Le répartiteur PRISM les découvre automatiquement au démarrage et les achemine POST /_bext/action/<exportName> à la fonction correspondante. Il n'y a pas de génération de proxy côté client, pas de capture de fermeture, pas d'ID basé sur le hachage ; le nom de la fonction est l'itinéraire.

Le modèle est intentionnellement plus étroit que le « serveur d'utilisation » de RSC : pas de transformations d'action en ligne, pas de conventions uniquement React. En échange, vous obtenez un format filaire prévisible qui fonctionne avec des formulaires HTML simples, avec fetch, et à partir de tout autre langage en tant que HTTP POST JSON ou FormData standard.

Démarrage rapide#

src/actions/subscribe.ts:

ts
"use server";

export async function subscribe(form: FormData) {
  const email = form.get("email");
  if (typeof email !== "string" || !email.includes("@")) {
    throw new Error("invalid email");
  }
  await db.subscribers.insert({ email });
  return { ok: true };
}

Utilisez-le à partir d'une page :

tsx
<form action="/_bext/action/subscribe" method="POST">
  <input name="email" type="email" required />
  <button>Subscribe</button>
</form>

C'est toute l'API. Soumettre le formulaire POST à ​​l'action ; avec JS désactivé, le navigateur suit un 303 vers le référent (afin que l'utilisateur voie le rendu de la page suivante) ; avec JS activé, le Assistant <Form> vous permet d'intercepter et de mettre à jour la page en place.

L'assistant <Form>#

tsx
import { Form, FORM_CLIENT_RUNTIME } from "@bext-stack/framework";

<Form name="subscribe">
  <input name="email" type="email" required />
  <button>Subscribe</button>
</Form>

Celui-ci émet un standard <form action="/_bext/action/subscribe" method="POST" data-bext-form="subscribe">. Sans environnement d'exécution client chargé, cela fonctionne exactement comme le formulaire HTML brut ci-dessus.

Si vous incluez également FORM_CLIENT_RUNTIME une fois dans votre mise en page :

tsx
<body>
  {/* …page… */}
  <div dangerouslySetInnerHTML={{ __html: FORM_CLIENT_RUNTIME }} />
</body>

… Ensuite, les soumissions sur les formulaires [data-bext-form] sont interceptées, publiées via fetch avec un en-tête x-bext-form: 1, et le résultat est envoyé sur le formulaire sous la forme d'un bext:result CustomEvent avec detail = { ok, status, result }. Un événement bext:pending se déclenche avant la requête – utile pour les spinners.

js
document.querySelector('form[data-bext-form="subscribe"]')
  .addEventListener("bext:result", (e) => {
    if (e.detail.ok) showToast("Subscribed!");
  });

Le temps d'exécution est d'environ 860 octets en ligne.

Protocole filaire#

Auteur Réponse du serveur
Soumettre le formulaire du navigateur (pas de JS) 303 → Referer (donc la page se recharge avec les effets de l'action visibles)
fetch() avec x-bext-form: 1 Corps JSON de ce que l'action a renvoyé
Lancers d'action 500 avec { error: e.message } (ou 303 retour pour le chemin sans JS)
L'action renvoie un Response transmis textuellement — utilisez-le pour les redirections explicites après le succès

La même action sert les deux chemins. Aucune branche de code nécessaire.

Analyse du corps – par type de contenu#

Le répartiteur inspecte le Content-Type de la requête et convertit le corps avant d'appeler votre fonction :

Type de contenu Forme des arguments
application/json valeur JS analysée
application/x-www-form-urlencoded URLSearchParams (API compatible FormData)
multipart/form-data MultipartFormData (API compatible FormData ; les champs du fichier sont MultipartFile )
text/* et autres chaîne brute

Ainsi, form.get("email") fonctionne que la page utilise <form> (codée en urlen) ou <form enctype="multipart/form-data">.

MultipartFile expose name, type, size, bytes(), text() et arrayBuffer() — toutes les lectures sont asynchrones (correspond à l'API Web File). Voir téléchargements de fichiers pour le chemin de diffusion en continu.

Capuchon de corps par action#

La limite du corps par défaut est de 10 Mo. Augmentez-le par action avec une directive de commentaire en haut du fichier :

ts
"use server";
// @bext:max-body 100mb
export async function uploadVideo(form: FormData) { … }

Unités reconnues (insensible à la casse, espace facultatif avant l'unité) : octets nus, kb, mb, gb. Les directives invalides sont ignorées. Le plafond s'applique à l'ensemble du corps de la requête et est vérifié lors de la vidange : les requêtes surdimensionnées obtiennent un 413 sans jamais atteindre la fonction.

Le plafond est par fichier source (un plafond couvre toutes les exportations dans src/actions/uploadVideo.ts).

Téléchargements de fichiers et streaming#

Pour les carrosseries en plusieurs parties supérieures à ~1 Mo (configurable via BEXT_PRISM_SPOOL_THRESHOLD ), le répartiteur met la demande en file d'attente vers un Fichier temporaire /tmp/bext-stream-* plutôt que de mise en mémoire tampon. Le wrapper d'action détecte cela et passe par un analyseur de streaming basé sur des morceaux qui lit les octets à la demande via deux ponts natifs V8 :

  • __bextReadChunk(path, offset, length) → Uint8Array | null — lit octets à partir d'un chemin de spool confiné (V8 ne peut pas lire de fichiers arbitraires - le pont applique le préfixe /tmp/bext-stream-).
  • __bextFileSize(path) → number — taille du fichier pour la terminaison des analyses.

La mémoire liée pendant l'analyse est l'état de l'analyseur plus un morceau plus la longueur de l'aiguille de limite - indépendamment de la taille totale du téléchargement. Les parties du fichier sont renvoyées sous forme d'objets MultipartFile dont bytes() / Les méthodes text() / arrayBuffer() sont lues paresseusement ; l'analyseur lui-même ne matérialise jamais un fichier entier.

Les téléchargements de 50 Mo et 1 Go suivent tous deux le même chemin, avec une mémoire d'analyse constante. Le fichier temporaire est automatiquement supprimé au retour de la répartition.

ts
"use server";
// @bext:max-body 200mb
export async function uploadVideo(form: any) {
  const file = form.get("video");
  if (!file || typeof file.bytes !== "function") {
    throw new Error("video field required");
  }
  // Stream to your storage of choice. file.bytes() materializes the
  // whole file into a Uint8Array — for very large uploads, prefer
  // arrayBuffer() into your storage SDK if it accepts ArrayBuffer
  // chunks.
  const data = await file.bytes();
  await s3.putObject({ Bucket: "uploads", Key: file.name, Body: data });
  return { ok: true, size: data.length };
}

Renvoi de Response par rapport aux valeurs simples#

  • Renvoie un Response (par exemple redirect("/thanks") ) → le framework le rend textuellement. Utilisez-le pour les redirections explicites après succès.
  • Renvoie un objet simple → JSON pour le chemin de récupération ; 303-retour au référent pour le chemin sans JS. Même action, les deux soumissionnaires.

Vérification croisée du formulaire/champ d'action au moment de la construction#

bext parcourt les sources de page pour <Form name="X"> et <form action="/_bext/action/X"> bloque, récupère l'enfant Déclarations <input name="…"> et références croisées avec les form.get("…") appelle à l’intérieur du corps de chaque action. Des disparités apparaissent sous la forme d'entrées tracing::warn pendant le développement :

text
[bext check] action `subscribe` reads form.get("emial") but no
<Form name="subscribe"> declares that field. Declared: email,
newsletter

La vérification est basée sur les expressions régulières et tolère les modèles dynamiques : form.get(varName), form.get(`x-${id}`), `` dynamique — sans faux positifs. Chaque combinaison unique (app_root, action, missing_field) enregistre trois journaux au maximum une fois par processus ; corrigez la faute de frappe, redémarrez, l'avertissement a disparu.

Les actions appelées uniquement via fetch (aucun formulaire correspondant sur aucune page) ignorent complètement la vérification.

Trois pipelines de répartition#

La même action s'exécute sur trois pipelines, tous avec parité pour l'analyse du corps sensible au type de contenu, le repli 303 PE et le Directive @bext:max-body :

Pipeline Où il vit
Développeur (serveur Bun) framework/src/serve.ts
Ensemble de production __fetch JSC framework/src/build.ts
bext -serveur PRISM -natif (rouille) crates/bext-server/src/ssr_pipeline/prism.rs

Le chemin Rust expose en outre le pont spool-to-disk + V8 pour les téléchargements volumineux.

Limites#

  • Pas de capture de fermeture : les actions sont des exportations de niveau supérieur, sans les variables peuvent être référencées par site d'appel.
  • Pas de jetons CSRF automatiques. POST de même origine avec application/x-www-form-urlencoded ou multipart/form-data sont autorisés par défaut ; montez un middleware pour des contrôles d'origine plus stricts si vous en avez besoin.
  • Aucune réponse en streaming des actions - elles renvoient une seule Response ou valeur. Pour les événements envoyés par le serveur/les interrogations longues, utilisez les routes API ( src/app/api/*/route.ts ).
  • Le partage en plusieurs parties avec téléchargements de fichiers nécessite l'en-tête x-bext-form. désactive sur le chemin PE sans JS afin que le répartiteur prenne le chemin de spool ; l'assistant <Form> s'en charge pour vous.
Attention

Les actions n'ont pas de protection CSRF intégrée. Requêtes POST cross-origin avec application/x-www-form-urlencoded ou multipart/form-data sont autorisés par défaut. Pour les actions qui modifient l'état sensible, ajoutez une vérification d'origine ou utilisez un type de contenu JSON (que les navigateurs n'envoient pas d'origine croisée sans un contrôle en amont). Voir Sécurité et WAF pour la configuration CORS.

Conseil

Pour un travail de longue durée déclenché par une action (envoi d'un e-mail, téléchargement de processus, mise en file d'attente d'une tâche en arrière-plan), revenez rapidement de l'action et transférez-la au moteur Flux durables ou au Planificateur de tâches plutôt que d'attendre à l'intérieur de l'action.

En rapport#

  • Îles de serveurs — différer les rendus lents du serveur sans bloquer le shell de la page
  • Flux durables — workflows en plusieurs étapes récupérables en cas d'accident à déclencher à partir d'actions
  • Sécurité et WAF — Considérations CORS et CSRF pour les points de terminaison d'action
  • Pipeline de rendu PRISM — comment les actions s'intègrent à la passe de compilation SSR
  • Cadres — PRISM — le framework PRISM qui héberge les actions du serveur
Modifier cette page ↗Besoin d’aide ? ↗
Rechercher dans la documentation

Saisissez un sujet, une commande ou une question.