Diffuser des documents via un Worker

CloudflareBeginner
Pratiquer maintenant

Introduction

Une application de support doit accepter un document et le renvoyer sans rendre son bucket de stockage public. Vous allez connecter un bucket R2 privé à un Worker, implémenter un téléversement limité et diffuser les téléchargements aux appelants. Un flux transmet les blocs au fur et à mesure de leur disponibilité au lieu de charger d’abord tout le téléchargement en mémoire.

Terminez d’abord les leçons « Organiser un bucket de documents » et celles consacrées à la configuration et aux secrets de Workers. Cette VM vierge contient Node.js 22.22.0, Wrangler 4.131.1, des documents synthétiques et un module d’authentification fourni. Ce module protège le point de terminaison de démonstration avec un jeton temporaire afin que la leçon sur le stockage n’expose pas un service de téléversement sans restriction. Vous apprendrez à corriger l’autorisation de l’application plus tard dans ce cours.

Avant de commencer, votre propre compte d’apprentissage doit disposer d’un abonnement R2 actif et de l’autorisation de gérer un nouveau bucket et un Worker. Consultez la tarification de R2 ; le stockage et les opérations, ainsi que l’utilisation de Workers, sont facturés séparément. Aucun domaine acheté n’est nécessaire. Utilisez uniquement des fichiers synthétiques et supprimez le Worker, les objets et le bucket de ce lab à la fin. Chaque VM doit avoir sa propre autorisation ; aucune ressource d’une VM précédente n’est réutilisée.

Connecter le bucket de l’application

Dans cette étape, vous autorisez cette VM et créez un bucket privé indépendant pour l’application. L’autorisation de l’appareil confirme votre compte d’apprentissage. La gestion des buckets R2 utilise un jeton d’API distinct, limité à ce compte.

Démarrez Bash pour utiliser la syntaxe des commandes ci-dessous, puis placez-vous dans le projet préparé et vérifiez ses outils. Gardez ce même terminal ouvert afin que vos variables de nom de ressources restent disponibles :

bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version

Autorisez le code d’appareil affiché dans votre propre navigateur. Vérifiez le compte d’apprentissage ainsi que les portées de lecture du compte et de l’utilisateur demandées avant d’accepter :

npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write
npx wrangler whoami --json

Exigez la valeur loggedIn: true. Lisez le nom du compte même si un seul compte est affiché. Remplacez YOUR_ACCOUNT_ID ci-dessous par l’identifiant réel de 32 caractères de ce compte. openssl rand -hex 6 génère douze caractères hexadécimaux aléatoires afin d’éviter tout conflit avec une exécution précédente de ce lab. Le document here-doc écrit un fichier de configuration standard ; le shell y remplace vos variables.

ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r02-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","main":"src/index.js","workers_dev":true,"compatibility_date":"2026-07-30","r2_buckets":[{"binding":"DOCUMENTS","bucket_name":"$BUCKET"}]}
JSON

Pour gérer le bucket, ouvrez la page API Tokens de votre profil Cloudflare et créez un jeton personnalisé portant le nom de ce lab. Accordez l’autorisation Account → Workers R2 Storage → Edit, puis limitez les Account Resources au compte d’apprentissage dont vous avez enregistré l’identifiant. Définissez une courte durée d’expiration. N’incluez aucun autre compte ni aucune autorisation sans rapport. Ce jeton de gestion sert à administrer les buckets, notamment à les créer et à les supprimer. Dans ce lab, le Worker accède aux objets R2 via son binding DOCUMENTS.

Copiez une seule fois le jeton dans cette invite masquée de la VM. umask 077 limite les droits du fichier à votre utilisateur ; read -s masque la saisie. Le fichier utilise la variable de jeton standard de Wrangler et est exclu de Git.

umask 077
read -r -s -p 'R2 management API token: ' R2_MANAGEMENT_TOKEN; printf '\n'
printf 'CLOUDFLARE_API_TOKEN=%s\n' "$R2_MANAGEMENT_TOKEN" > .env.management
unset R2_MANAGEMENT_TOKEN

Utilisez --env-file=.env.management uniquement pour les commandes de gestion R2 ; whoami continue de vérifier l’autorisation de l’appareil de la VM.

Placez --env-file à la fin de chaque commande Wrangler pour que sa liste de fichiers ne soit pas étendue au nom de la commande. Après la création de chaque compartiment, si Wrangler propose d’ajouter une liaison à la configuration, saisissez n et appuyez sur Entrée. La configuration contient déjà la liaison prévue.

npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management

Répertoriez vos buckets et recherchez le nom généré exact. Les autres buckets appartiennent à d’autres travaux ; ne les modifiez pas.

npx wrangler r2 bucket list --env-file=.env.management

Dans le Dashboard, ouvrez Storage & databases → R2 → Overview, sélectionnez ce bucket exact et vérifiez que la liste des objets est vide. Dans ses paramètres, laissez désactivées l’URL de développement publique et les domaines personnalisés. Le nom du bucket dans le Dashboard confirme son identité ; les vérifications de téléchargement ultérieures prouveront que les octets stockés sont corrects.

L’autorisation du script Worker permet le déploiement. L’autorisation KV permet à Wrangler d’assurer le suivi de la suppression ; ce lab ne crée aucun espace de noms KV. Le jeton de gestion R2 reste un identifiant distinct limité au compte.

Implémenter des téléversements limités et des téléchargements en streaming

Dans cette étape, vous transformez la liaison de configuration DOCUMENTS en opérations sur les objets. Une liaison est un objet d’exécution que Cloudflare fournit au Worker. env.DOCUMENTS fait référence au bucket privé configuré par son nom ; le Worker n’a pas besoin d’un secret S3 pour l’utiliser.

Le fichier src/auth.js vérifie un jeton bearer temporaire. Notre route n’accepte que des noms de documents simples en .txt. PUT remplace les octets associés à la clé sélectionnée. Cet exemple autorise au maximum 1 Mio (1 048 576 octets), y compris pour les clients qui omettent l’en-tête de longueur. Les blocs du téléversement sont collectés uniquement jusqu’à cette limite, afin que R2 puisse recevoir un corps dont la longueur est connue. Les téléchargements transmettent directement object.body à la réponse et restent diffusés en streaming.

Écrivez le gestionnaire avec ce document here-doc :

cat > src/index.js <<'JS'
import { authorized } from "./auth.js";
const MAX_BYTES = 1024 * 1024;
export default {
  async fetch(request, env) {
    const path = new URL(request.url).pathname;
    if (path === "/health" && request.method === "GET") return new Response("ok");
    if (!await authorized(request, env)) return new Response("Unauthorized", { status: 401 });
    if (!/^\/documents\/[a-z0-9-]+\.txt$/.test(path)) return new Response("Not found", { status: 404 });
    const key = path.slice(1);
    if (request.method === "PUT") {
      if (Number(request.headers.get("Content-Length")) > MAX_BYTES)
        return new Response("Too large", { status: 413 });
      // Count actual bytes too: a request may omit Content-Length.
      const reader = request.body?.getReader();
      if (!reader) return new Response("Body required", { status: 400 });
      const chunks = [];
      let total = 0;
      for (;;) {
        const { value, done } = await reader.read();
        if (done) break;
        total += value.byteLength;
        if (total > MAX_BYTES) {
          await reader.cancel();
          return new Response("Too large", { status: 413 });
        }
        chunks.push(value);
      }
      const bytes = new Uint8Array(total);
      let offset = 0;
      for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength; }
      await env.DOCUMENTS.put(key, bytes, { httpMetadata: { contentType: "text/plain" } });
      return new Response("Stored", { status: 201 });
    }
    if (request.method !== "GET") return new Response("Method not allowed", { status: 405, headers: { Allow: "GET, PUT" } });
    const object = await env.DOCUMENTS.get(key);
    if (object === null) return new Response("Not found", { status: 404 });
    const headers = new Headers();
    object.writeHttpMetadata(headers);
    headers.set("ETag", object.httpEtag);
    headers.set("Cache-Control", "private, no-store");
    return new Response(object.body, { headers });
  }
};
JS

get() renvoie null lorsqu’une clé est absente ; traitez ce cas avant de lire son corps. writeHttpMetadata restaure le type de contenu enregistré et httpEtag est déjà correctement entouré de guillemets. private, no-store empêche ces documents protégés d’être placés dans des caches partagés.

Créez un jeton d’application aléatoire dans .dev.vars, que Wrangler charge pour le développement local. Il s’agit d’un identifiant synthétique propre au lab, distinct des identifiants de votre compte Cloudflare :

umask 077
printf "ACCESS_TOKEN=%s\n" "$(openssl rand -hex 24)" > .dev.vars

Vérifiez que Wrangler peut regrouper le code sans le déployer. La vérification de la plateforme lance un environnement d’exécution local temporaire distinct, avec de nouvelles données synthétiques, afin de vérifier les octets exacts, les deux chemins de limite de taille et l’absence d’objets trop volumineux :

npx wrangler deploy --dry-run

Tester la limite du stockage local

Dans cette étape, vous exécutez le Worker avec le stockage R2 local. wrangler dev utilise par défaut une simulation locale ; ces requêtes ne créent donc aucun objet dans le cloud. Exécutez le serveur de développement en arrière-plan ; $! enregistre l’identifiant de processus de ce travail pour le nettoyage.

npx wrangler dev --ip 127.0.0.1 --port 8787 > dev.log 2>&1 &
DEV_PID=$!

Attendez que dev.log indique que le serveur est prêt, puis chargez le jeton d’application temporaire dans ce terminal. Ne l’affichez pas.

cat dev.log
set -a
source .dev.vars
set +a

Téléversez et téléchargez le fichier préparé. --data-binary préserve ses octets ; -o enregistre le téléchargement.

curl -i -X PUT -H "Authorization: Bearer $ACCESS_TOKEN" --data-binary @document.txt http://127.0.0.1:8787/documents/report.txt
curl -fsS -H "Authorization: Bearer $ACCESS_TOKEN" http://127.0.0.1:8787/documents/report.txt -o local-download.txt
cmp document.txt local-download.txt

Exigez 201 Stored pour le téléversement et une comparaison réussie sans sortie. Testez une clé absente ainsi qu’un téléversement dépassant la limite d’un octet. Python crée uniquement un fichier synthétique de taille limitée :

curl -i -H "Authorization: Bearer $ACCESS_TOKEN" http://127.0.0.1:8787/documents/missing.txt
python3 -c "open('oversized.txt','wb').write(b'x' * (1024 * 1024 + 1))"
curl -i -X PUT -H "Authorization: Bearer $ACCESS_TOKEN" --data-binary @oversized.txt http://127.0.0.1:8787/documents/large.txt

Exigez 404 Not found et 413 Too large. Ces appels curl omettent volontairement --fail afin que les erreurs HTTP attendues restent lisibles. Une page HTML d’erreur provenant d’un proxy n’est pas la réponse de l’application. Exécutez la vérification de la plateforme avant d’arrêter le serveur local.

Déployer et vérifier l’intégration avec le bucket privé

Dans cette étape, vous répétez le flux de traitement des documents sur R2 réel. Une réussite en local ne prouve ni la liaison distante ni la propriété du compte.

Arrêtez le serveur de développement et publiez le Worker :

kill "$DEV_PID"
wait "$DEV_PID" 2>/dev/null || true
npx wrangler deploy

Téléversez le secret de l’application avec la commande groupée standard. .dev.vars n’est pas téléversé automatiquement lors du déploiement.

npx wrangler secret bulk .dev.vars

Copiez l’URL HTTPS workers.dev exacte affichée dans la sortie du déploiement dans BASE_URL, sans barre oblique finale. Attendez que /health renvoie ok ; si le déploiement est encore en cours de propagation, répétez la requête pendant une minute au maximum.

BASE_URL=https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev
curl -i "$BASE_URL/health"

Téléversez le rapport vers le bucket distant, téléchargez-le et comparez les fichiers :

curl -i -X PUT -H "Authorization: Bearer $ACCESS_TOKEN" --data-binary @document.txt "$BASE_URL/documents/report.txt"
curl -fsS -H "Authorization: Bearer $ACCESS_TOKEN" "$BASE_URL/documents/report.txt" -o remote-download.txt
cmp document.txt remote-download.txt

Exigez 201 Stored et des octets identiques. Répétez les vérifications négatives sur le point de terminaison public :

curl -i "$BASE_URL/documents/report.txt"
curl -i -H "Authorization: Bearer $ACCESS_TOKEN" "$BASE_URL/documents/missing.txt"
curl -i -X PUT -H "Authorization: Bearer $ACCESS_TOKEN" --data-binary @oversized.txt "$BASE_URL/documents/large.txt"

Exigez 401 Unauthorized, 404 Not found et 413 Too large. Dans le Dashboard, ouvrez ce Worker et vérifiez sa liaison R2 ; ouvrez ensuite le bucket exact pour y trouver documents/report.txt. Son URL de développement publique et ses domaines personnalisés restent désactivés. Le Worker fournit le chemin d’accès ; la confidentialité du bucket ne signifie pas que toutes les routes du Worker sont automatiquement sûres.

Binding DOCUMENTS du Worker connecté au bucket R2 privé

La ligne DOCUMENTS relie ce Worker à son bucket exact. Les noms générés de cet exemple diffèrent des vôtres.

Rapport envoyé par le Worker dans un bucket privé Standard

La ligne affiche report.txt, text/plain et 41 B, avec Public Access Disabled. Les noms et dates sont des exemples. Bucket Size peut rester à 0 B pendant sa mise à jour ; la ligne de l’objet et le téléchargement réussi prouvent que le rapport existe.

Supprimer l’application et le bucket distants

Dans cette étape, vous supprimez uniquement le Worker et les objets de ce lab, tout en restant autorisé. Le bucket privé ne disparaît pas lorsque son Worker est supprimé.

npx wrangler delete

Confirmez le nom exact du Worker généré. Supprimez explicitement l’unique objet téléversé, puis supprimez le bucket :

BUCKET=$(node -p "JSON.parse(require('fs').readFileSync('wrangler.jsonc')).r2_buckets[0].bucket_name")
npx wrangler r2 object delete "$BUCKET/documents/report.txt" --remote --env-file=.env.management
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management

La requête trop volumineuse ne devrait pas avoir créé documents/large.txt. Si le bucket n’est pas vide de manière inattendue, inspectez uniquement ce bucket et supprimez la clé synthétique exacte après avoir diagnostiqué l’échec du contrat de taille. Dans ce cas, la vérification fonctionnelle précédente n’est pas réussie.

Actualisez les listes des Workers et des buckets dans le Dashboard, puis exécutez la vérification de nettoyage de la plateforme. Les échecs d’authentification ou de réseau ne permettent pas de conclure à une suppression réussie.

Révoquer les identifiants restants

Dans cette étape, vous révoquez le jeton de gestion de ce lab sur la page API Tokens de votre profil, supprimez le secret local de l’application et fermez l’autorisation de la VM. Effectuez cette étape uniquement après la réussite de la vérification de nettoyage précédente.

rm .env.management .dev.vars
unset ACCESS_TOKEN
npx wrangler logout
npx wrangler whoami --json || true

Exigez loggedIn: false. La révocation du jeton de gestion est une vérification manuelle distincte dans le Dashboard ; supprimer uniquement le fichier local ne le révoque pas. Laissez intactes votre session Dashboard habituelle et les jetons des autres labs.

Résumé

Associer un stockage R2 privé à un Worker, accepter des téléversements limités, diffuser les octets exacts des documents, gérer les erreurs et supprimer les ressources cloud créées.