Transmitir documentos mediante un Worker

CloudflareBeginner
Practicar Ahora

Introducción

Una aplicación de soporte debe aceptar un documento y devolverlo sin hacer público el bucket donde se almacena. Usted conectará un bucket privado de R2 a un Worker, implementará una carga con límite de tamaño y transmitirá las descargas a los clientes. Una transmisión entrega los fragmentos a medida que están disponibles, en lugar de recopilar primero toda la descarga en memoria.

Complete primero Organize a Document Bucket y las lecciones sobre configuración y secretos de Workers. Esta VM nueva contiene Node.js 22.22.0, Wrangler 4.131.1, documentos sintéticos y un módulo de autenticación proporcionado. El módulo protege el endpoint de demostración con un token desechable para que la lección de almacenamiento no exponga un servicio de carga sin restricciones. Más adelante, en este curso, aprenderá a corregir la autorización de la aplicación.

Antes de comenzar, su propia cuenta de aprendizaje necesita una suscripción activa a R2 y permiso para administrar un bucket y un Worker nuevos. Consulte los precios de R2; el almacenamiento y las operaciones, así como el uso de Workers, se contabilizan por separado. No necesita comprar un dominio. Use únicamente archivos sintéticos y elimine el Worker, los objetos y el bucket de este laboratorio al finalizar. Cada VM necesita su propia autorización; no se reutilizan recursos de VMs anteriores.

Conectar el bucket de la aplicación

En este paso, autorizará esta VM y creará un bucket privado independiente para la aplicación. La autorización del dispositivo confirma su cuenta de aprendizaje. La administración de buckets de R2 utiliza un token de API independiente, restringido a esa cuenta.

Inicie Bash para usar la sintaxis de comandos que aparece a continuación. Después, vaya al proyecto preparado y compruebe sus herramientas. Mantenga abierto este mismo terminal para que las variables con los nombres de los recursos sigan 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

Autorice el código de dispositivo mostrado en su propio navegador. Antes de conceder el consentimiento, confirme la cuenta de aprendizaje y los ámbitos solicitados de lectura de la cuenta y del usuario:

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

Compruebe que aparezca loggedIn: true. Lea el nombre de la cuenta aunque solo aparezca una. Sustituya YOUR_ACCOUNT_ID a continuación por el ID real de 32 caracteres de esa cuenta. openssl rand -hex 6 genera doce caracteres hexadecimales aleatorios para evitar que este laboratorio entre en conflicto con una ejecución anterior. El documento here escribe un archivo de configuración estándar; el shell sustituye en él sus 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

Para administrar el bucket, abra la página API Tokens de su perfil de Cloudflare y cree un token personalizado con un nombre relacionado con este laboratorio. Conceda Account → Workers R2 Storage → Edit y limite Account Resources a la cuenta de aprendizaje cuyo ID guardó. Establezca una caducidad breve. No incluya otras cuentas ni permisos que no estén relacionados. Este token de administración sirve para administrar buckets, incluida su creación y eliminación. En este laboratorio, el Worker accede a los objetos de R2 mediante su binding DOCUMENTS.

Copie el token una sola vez en este indicador oculto de la VM. umask 077 restringe el acceso al archivo a su usuario; read -s oculta lo que escribe. El archivo utiliza la variable de token estándar de Wrangler y queda excluido 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

Use --env-file=.env.management únicamente para los comandos de administración de R2; whoami seguirá comprobando la autorización del dispositivo de la VM.

Coloque --env-file al final de cada comando de Wrangler para que su lista de archivos no incluya el nombre del comando. Después de crear cada bucket, si Wrangler pregunta si debe añadir un enlace a la configuración, escriba n y pulse Enter. La configuración ya contiene el enlace previsto.

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

Enumere sus buckets y busque el nombre generado exacto. Los demás buckets pertenecen a otros trabajos; no los modifique.

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

En Dashboard, abra Storage & databases → R2 → Overview, seleccione este bucket exacto y compruebe que la lista de objetos esté vacía. En su configuración, mantenga desactivadas la URL pública de desarrollo y los dominios personalizados. El nombre del bucket en Dashboard confirma su identidad; las comprobaciones de descarga posteriores demostrarán que los bytes almacenados son correctos.

El permiso para los scripts de Worker permite realizar el despliegue. El permiso para KV permite que Wrangler registre la información necesaria para la eliminación; este laboratorio no crea ningún espacio de nombres de KV. El token de administración de R2 sigue siendo una credencial independiente y limitada a la cuenta.

Implementar cargas con límite y descargas mediante transmisión

En este paso, convertirá la vinculación de configuración DOCUMENTS en operaciones con objetos. Una binding es un objeto de ejecución que Cloudflare proporciona al Worker. env.DOCUMENTS hace referencia al bucket privado configurado por nombre; el Worker no necesita un secreto de S3 para utilizarlo.

El archivo src/auth.js proporcionado comprueba un token bearer desechable. Nuestra ruta solo acepta nombres de documentos .txt sencillos. PUT reemplaza los bytes de la clave seleccionada. Este ejemplo permite como máximo 1 MiB (1.048.576 bytes), incluidos los clientes que omiten la cabecera de longitud. Los fragmentos de la carga se recopilan únicamente hasta ese límite, de modo que R2 puede recibir un cuerpo con una longitud conocida. Las descargas pasan object.body directamente a la respuesta y se mantienen como transmisiones.

Escriba el controlador con este documento here:

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() devuelve null cuando falta una clave; compruébelo antes de leer su cuerpo. writeHttpMetadata restaura el tipo de contenido guardado y httpEtag ya está entrecomillado correctamente. private, no-store mantiene estos documentos protegidos fuera de las cachés compartidas.

Cree un token de aplicación aleatorio en .dev.vars, que Wrangler carga durante el desarrollo local. Es una credencial sintética del laboratorio, independiente de las credenciales de su cuenta de Cloudflare:

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

Compruebe que Wrangler pueda empaquetar el código sin realizar el despliegue. La comprobación de la plataforma ejecuta un entorno local temporal independiente con datos sintéticos nuevos para verificar los bytes exactos, las dos rutas del límite de tamaño y la ausencia de objetos demasiado grandes:

npx wrangler deploy --dry-run

Probar el límite de almacenamiento local

En este paso, ejecutará el Worker con el almacenamiento R2 local. De forma predeterminada, wrangler dev utiliza una simulación local, por lo que estas solicitudes no crean ningún objeto en la nube. Ejecute el servidor de desarrollo en segundo plano; $! registra el ID del proceso de este trabajo para poder limpiarlo.

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

Espere hasta que dev.log indique que el servidor está listo y, después, cargue el token de aplicación desechable en este terminal. No lo muestre en pantalla.

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

Cargue y descargue el archivo preparado. --data-binary conserva sus bytes; -o guarda la descarga.

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

Exija 201 Stored en la carga y una comparación correcta sin salida. Pruebe una clave inexistente y una carga que supere el límite en un byte. Python crea únicamente un archivo sintético con un tamaño limitado:

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

Exija 404 Not found y 413 Too large. Estas llamadas de curl omiten deliberadamente --fail para que los errores HTTP esperados sigan siendo legibles. Una página HTML de error procedente de un proxy no es la respuesta de la aplicación. Ejecute la comprobación de la plataforma antes de detener el servidor local.

Desplegar y verificar la integración con el bucket privado

En este paso, repetirá el flujo de documentos en R2 real. El éxito local no demuestra que la vinculación remota ni la propiedad de la cuenta sean correctas.

Detenga el servidor de desarrollo y publique el Worker:

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

Cargue el secreto de la aplicación mediante el comando masivo estándar. .dev.vars no se carga automáticamente durante el despliegue.

npx wrangler secret bulk .dev.vars

Copie la URL HTTPS exacta de workers.dev que aparece en la salida del despliegue y asígnela a BASE_URL, sin una barra final. Espere a que /health devuelva ok; si el despliegue todavía se está propagando, repita la lectura durante un máximo de un minuto.

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

Cargue el informe en el bucket remoto, descárguelo y compárelo:

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

Exija 201 Stored y bytes idénticos. Repita las comprobaciones negativas contra el endpoint público:

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"

Exija 401 Unauthorized, 404 Not found y 413 Too large. En Dashboard, abra este Worker y compruebe su vinculación con R2; después, abra el bucket exacto para encontrar documents/report.txt. La URL pública de desarrollo y los dominios personalizados siguen desactivados. El Worker proporciona la ruta de acceso; la privacidad del bucket no significa que todas las rutas del Worker sean seguras automáticamente.

Binding DOCUMENTS del Worker conectado al bucket privado R2

La fila DOCUMENTS conecta este Worker con su bucket exacto. Los nombres generados del ejemplo serán diferentes de los tuyos.

Informe subido mediante el Worker a un bucket privado Standard

La fila muestra report.txt, text/plain y 41 B, con Public Access Disabled. Los nombres y fechas son ejemplos. Bucket Size puede tardar en actualizarse y mostrar 0 B; la fila del objeto y la descarga correcta confirman que el informe existe.

Eliminar la aplicación y el bucket remotos

En este paso, eliminará únicamente el Worker y los objetos de este laboratorio mientras mantiene la autorización activa. El bucket privado no desaparece cuando se elimina su Worker.

npx wrangler delete

Confirme el nombre exacto del Worker generado. Elimine explícitamente el único objeto cargado y, después, elimine el 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 solicitud demasiado grande no debería haber creado documents/large.txt. Si el bucket no está vacío de forma inesperada, inspeccione únicamente este bucket y elimine la clave sintética exacta después de diagnosticar el fallo del contrato de tamaño. Esa reparación significa que la comprobación funcional anterior no se ha superado.

Actualice las listas de Workers y buckets en Dashboard y ejecute la comprobación de limpieza de la plataforma. Los fallos de autenticación o de red no son concluyentes; no demuestran que la eliminación se haya realizado correctamente.

Revocar las credenciales restantes

En este paso, revocará el token de administración de este laboratorio en la página API Tokens de su perfil, eliminará el secreto local de la aplicación y cerrará la autorización de la VM. Hágalo únicamente después de que la comprobación de limpieza anterior se haya realizado correctamente.

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

Exija loggedIn: false. La revocación del token de administración es un paso manual independiente en Dashboard; eliminar únicamente el archivo local no lo revoca. Mantenga intactos el inicio de sesión normal en Dashboard y los tokens de otros laboratorios.

Resumen

Vincule el almacenamiento privado de R2 a un Worker, acepte cargas con un límite de tamaño, transmita los bytes exactos de los documentos, gestione los errores y elimine los recursos de nube que haya creado.