Completar y cancelar cargas multiparte

CloudflareBeginner
Practicar Ahora

Introducción

Un cargador de copias de seguridad debe finalizar las cargas correctas y liberar las partes abandonadas después de una interrupción. Dividirá un archivo sintético acotado, completará su sesión multiparte e inspeccionará y cancelará otra sesión sin terminar, sin afectar a los objetos completados.

Complete primero las lecciones anteriores sobre objetos de R2 y credenciales con permisos limitados. Esta VM nueva tiene Node.js 22.22.0, Wrangler 4.131.1 y AWS SDK 3.888.0. Creará un bucket privado nuevo de tipo Standard y sus propias credenciales de corta duración. R2 debe estar activo; revise los límites de las cargas multiparte y los precios. Las partes incompletas cuentan para el almacenamiento. Este laboratorio transfiere únicamente un archivo sintético pequeño y no requiere ningún dominio. No reutilice cargas ni buckets anteriores.

Cree su bucket privado de documentos

En este paso, autorizará esta VM y creará un bucket temporal. 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 conservar disponibles las variables con los nombres de los recursos:

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. Confirme la cuenta de aprendizaje y los permisos de lectura de cuenta y usuario solicitados antes de conceder el consentimiento:

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

Exija 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 colisiones con ejecuciones anteriores de este laboratorio. El documento here-document escribe un archivo de configuración estándar; el shell sustituye en él los valores de sus variables.

ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r06-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","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 el nombre de 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 no relacionados. Este token de administración sirve para administrar buckets, incluida su creación y eliminación. Más adelante en este mismo paso, creará un token de objetos separado, limitado a este bucket, para que el SDK de S3 trabaje con los objetos.

Copie el token una sola vez en este indicador oculto de la VM. umask 077 restringe el archivo a su usuario; read -s oculta la entrada. 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 normal 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 exacto generado. 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 e inspeccione su lista de objetos vacía. En la configuración del bucket, deje 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.

La API compatible con S3 permite que los SDK de almacenamiento estándar accedan a R2. Utiliza un par de claves de acceso independiente, en lugar del token de dispositivo de Wrangler. En R2 Overview, vaya a Account Details → API Tokens → Manage y cree un User API token con el nombre del recurso generado para este laboratorio. Elija Object Read & Write, restrínjalo a este bucket nuevo exacto y seleccione una caducidad breve si el formulario ofrece esa opción. No elija todos los buckets ni el acceso Admin. Mantenga abierta esta página del token hasta guardar el secreto de un solo uso.

Use los siguientes indicadores de Bash en la VM. read -s oculta la entrada; umask 077 hace que el archivo de credenciales solo pueda leerlo su usuario. Estos nombres son las variables de entorno estándar del AWS SDK. Pegue el Access Key ID y el Secret Access Key en sus respectivos indicadores y pulse Enter. No pegue el valor del token de API general.

umask 077
read -r -s -p 'Access Key ID: ' AWS_ACCESS_KEY_ID; printf '\n'
read -r -s -p 'Secret Access Key: ' AWS_SECRET_ACCESS_KEY; printf '\n'
printf 'AWS_ACCESS_KEY_ID=%s\nAWS_SECRET_ACCESS_KEY=%s\n' "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" > .env.s3
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY

Escriba un cliente reutilizable para el SDK estándar. El SDK requiere una cadena de región; R2 utiliza auto. Leer la configuración existente mantiene las operaciones de la CLI y del SDK dirigidas a la misma cuenta y al mismo bucket.

cat > storage.mjs <<'JS'
import { S3Client } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("wrangler.jsonc", "utf8"));
export const Bucket = config.r2_buckets[0].bucket_name;
export const s3 = new S3Client({
  region: "auto",
  endpoint: `https://${config.account_id}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
  }
});
JS

Cargue el manual sintético que debe conservarse usando Wrangler. Debe permanecer sin cambios durante las operaciones de finalización y cancelación:

npx wrangler r2 object put "$BUCKET/retained/handbook.txt" --remote --file retained.txt --content-type text/plain --env-file=.env.management

Inicie una carga multiparte acotada

En este paso, iniciará una carga multiparte: una sesión de carga en el servidor que acepta partes numeradas antes de ensamblar el objeto final. Las partes cargadas todavía no forman un objeto que se pueda descargar. Guardar el ID de carga le permite reanudar o cancelar esta sesión exacta.

Cree un archivo binario sintético de 6 MiB con Python estándar. La primera parte será de 5 MiB y la parte final de 1 MiB. R2 requiere tamaños de parte compatibles; las partes que no sean la última deben tener al menos 5 MiB y deben usar tamaños iguales. Este archivo pequeño demuestra el protocolo sin realizar una transferencia grande.

python3 - <<'DATA'
from pathlib import Path
Path("archive.bin").write_bytes(bytes(range(256)) * (6 * 1024 * 1024 // 256))
DATA
cat > start.mjs <<'JS'
import { CreateMultipartUploadCommand } from "@aws-sdk/client-s3";
import { writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const Key = "exports/archive.bin";
const result = await s3.send(new CreateMultipartUploadCommand({ Bucket, Key, ContentType: "application/octet-stream" }));
writeFileSync("upload.json", JSON.stringify({ Key, UploadId: result.UploadId }));
console.log("Started multipart upload for", Key);
JS
node --env-file=.env.s3 start.mjs

Conserve upload.json: identifica esta operación, pero no indica que haya terminado correctamente. No vuelva a ejecutar start sin necesidad; cada llamada crea otra carga incompleta que deberá limpiarse. Sigue usando el ID de carga guardado al crearla. En el endpoint R2 probado, la lista devolvió otra cadena de ID opaco; compara la clave exacta del objeto y usa el ID guardado con ListParts para confirmar la sesión activa.

Cargue las partes ordenadas y complete el objeto

En este paso, enviará las dos partes e indicará a R2 qué identificadores de partes devueltos forman el objeto final. La numeración de las partes comienza en 1. La solicitud de finalización incluye cada ETag exactamente como lo devolvió la carga de esa parte; no equivale a calcular usted mismo el hash del archivo fuente completo.

cat > complete.mjs <<'JS'
import { UploadPartCommand, CompleteMultipartUploadCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync, writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const { Key, UploadId } = JSON.parse(readFileSync("upload.json", "utf8"));
const bytes = readFileSync("archive.bin");
const size = 5 * 1024 * 1024;
const Parts = [];
for (let offset = 0, PartNumber = 1; offset < bytes.length; offset += size, PartNumber++) {
  const result = await s3.send(new UploadPartCommand({ Bucket, Key, UploadId, PartNumber, Body: bytes.subarray(offset, offset + size) }));
  Parts.push({ PartNumber, ETag: result.ETag });
  console.log("Uploaded part", PartNumber);
}
await s3.send(new CompleteMultipartUploadCommand({ Bucket, Key, UploadId, MultipartUpload: { Parts } }));
const object = await s3.send(new GetObjectCommand({ Bucket, Key }));
writeFileSync("completed.bin", await object.Body.transformToByteArray());
console.log("Completed and downloaded", Key);
JS
node --env-file=.env.s3 complete.mjs

Exija dos líneas de partes cargadas, seguidas de la línea de finalización. Compare el archivo descargado byte por byte:

cmp archive.bin completed.bin && printf "Multipart bytes match\n"

El ETag de un objeto multiparte no es necesariamente un MD5 del archivo final. La comparación byte por byte demuestra directamente que el contenido se conservó. En Dashboard, abra el bucket de este laboratorio e inspeccione exports/archive.bin; el manual conservado también debe seguir presente.

Desmarca View prefixes as folders para ver las dos claves completas como en este ejemplo. El nombre generado de tu bucket será distinto. 6.29 MB es la presentación decimal de 6 MiB (6,291,456 bytes). El resumen superior Bucket Size: 0 B puede actualizarse con retraso; confirma el contenido mediante las filas de objetos y los bytes verificados por la API.

Objeto multiparte completado y manual conservado

Inspeccione una carga incompleta

En este paso, dejará deliberadamente incompleta una carga nueva y después enumerará la sesión y sus partes. Las partes incompletas consumen almacenamiento aunque una lista normal de objetos no muestre un archivo completado. Por eso, la limpieza necesita un inventario de cargas además de un inventario de objetos.

cat > abandon.mjs <<'JS'
import { CreateMultipartUploadCommand, UploadPartCommand, ListMultipartUploadsCommand, ListPartsCommand } from "@aws-sdk/client-s3";
import { readFileSync, writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const Key = "temporary/unfinished.bin";
const result = await s3.send(new CreateMultipartUploadCommand({ Bucket, Key }));
const UploadId = result.UploadId;
writeFileSync("abandoned.json", JSON.stringify({ Key, UploadId }));
await s3.send(new UploadPartCommand({ Bucket, Key, UploadId, PartNumber: 1, Body: readFileSync("archive.bin").subarray(0, 5 * 1024 * 1024) }));
const uploads = await s3.send(new ListMultipartUploadsCommand({ Bucket }));
console.log(uploads.Uploads.map(upload => ({ key: upload.Key, uploadId: upload.UploadId })));
const parts = await s3.send(new ListPartsCommand({ Bucket, Key, UploadId }));
console.log(parts.Parts.map(part => ({ part: part.PartNumber, bytes: part.Size })));
JS
node --env-file=.env.s3 abandon.mjs

El inventario de cargas multiparte contiene temporary/unfinished.bin. La solicitud ListParts usa el ID guardado y debe devolver la parte 1 con 5,242,880 bytes. No compares el texto del ID de la lista con el guardado ni crees otra sesión para repetir una lectura. Usa el ID guardado con las API de listado y ejecuta la comprobación mientras esta carga siga existiendo.

Cancele únicamente la sesión abandonada

En este paso, liberará las partes sin terminar cancelando el ID de carga exacto. Cancelar una carga es diferente de eliminar un objeto completado. La operación debe dejar intactos tanto el archivo completado como el manual.

cat > abort.mjs <<'JS'
import { AbortMultipartUploadCommand, ListMultipartUploadsCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const { Key, UploadId } = JSON.parse(readFileSync("abandoned.json", "utf8"));
await s3.send(new AbortMultipartUploadCommand({ Bucket, Key, UploadId }));
const uploads = await s3.send(new ListMultipartUploadsCommand({ Bucket }));
console.log("Incomplete uploads:", uploads.Uploads || []);
JS
node --env-file=.env.s3 abort.mjs

Ahora, este bucket nuevo debe mostrar una lista de cargas multiparte vacía. La comprobación de la plataforma también descarga ambos objetos completados para demostrar que permanecen sin cambios. Nunca interprete un listado fallido como una lista vacía.

Limpie los archivos completados y el bucket

En este paso, eliminará exactamente los dos objetos completados después de que la comprobación de cancelación sea correcta. La limpieza explícita no espera a la regla predeterminada del ciclo de vida de las cargas incompletas.

npx wrangler r2 object delete "$BUCKET/exports/archive.bin" --remote --env-file=.env.management
npx wrangler r2 object delete "$BUCKET/retained/handbook.txt" --remote --env-file=.env.management
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management
npx wrangler r2 bucket list --env-file=.env.management

Confirme únicamente el nombre generado de este bucket. Exija que no aparezca en un inventario correcto y ejecute la comprobación de limpieza de la plataforma antes de revocar las credenciales.

Revoque la credencial del laboratorio y cierre la sesión

En este paso, cerrará el acceso que dejó este ejercicio. En la página R2 API Tokens, revoque únicamente el token de objetos cuyo nombre corresponde a este laboratorio. En la página API Tokens de su perfil, revoque el token de administración de R2 independiente que creó para este laboratorio. Eliminar un bucket no revoca un token, y cerrar sesión en Wrangler no revoca las credenciales de S3.

Después de revocar los tokens, elimine el archivo local de credenciales y cierre la sesión de esta VM:

rm .env.s3 .env.management
npx wrangler logout

Inspeccione la identidad estructurada. Su estado distinto de cero es esperado cuando la sesión está cerrada:

npx wrangler whoami --json || true

Exija loggedIn: false y mantenga abierta su sesión habitual de Dashboard. Las comprobaciones de la plataforma verifican la eliminación de las credenciales locales y el cierre de sesión de Wrangler. La revocación de ambos tokens es un punto de comprobación manual en Dashboard en este ejercicio; no se deduce de la eliminación de los archivos.

Resumen

Complete un objeto multiparte con bytes exactos, inspeccione y cancele partes sin terminar, conserve otros objetos y elimine las credenciales de almacenamiento.