Introdução
Um aplicativo de suporte precisa aceitar um documento e devolvê-lo sem tornar público o bucket onde ele está armazenado. Você conectará um bucket privado do R2 a um Worker, implementará um upload com limite e transmitirá os downloads aos clientes. Um stream entrega os blocos à medida que ficam disponíveis, em vez de primeiro coletar todo o download na memória.
Conclua primeiro as lições Organize a Document Bucket e de configuração/segredos do Workers. Esta VM nova contém Node.js 22.22.0, Wrangler 4.131.1, documentos sintéticos e um módulo de autenticação fornecido. O módulo protege o endpoint de demonstração com um token descartável para que a lição de armazenamento não exponha um serviço de upload sem restrições. Mais adiante neste curso, você aprenderá a corrigir a autorização do aplicativo.
Antes de começar, sua própria conta de aprendizado precisa ter uma assinatura ativa do R2 e permissão para gerenciar um novo bucket e um Worker. Consulte os preços do R2; o armazenamento/operações e o uso do Worker são medidos separadamente. Não é necessário ter um domínio comprado. Use somente arquivos sintéticos e remova o Worker, os objetos e o bucket deste laboratório ao final. Cada VM precisa de sua própria autorização; nenhum recurso de uma VM anterior é reutilizado.
Conectar o bucket do aplicativo
Nesta etapa, você autoriza esta VM e cria um bucket privado independente para o aplicativo. A autorização do dispositivo confirma sua conta de aprendizado. O gerenciamento de buckets do R2 usa um token de API separado, restrito a essa conta.
Inicie o Bash para usar a sintaxe de comandos mostrada abaixo. Em seguida, acesse o projeto preparado e verifique as ferramentas. Mantenha este mesmo terminal aberto para que as variáveis com os nomes dos recursos continuem disponíveis:
bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version
Autorize o código de dispositivo exibido no seu próprio navegador. Confirme a conta de aprendizado e os escopos de leitura da conta e do usuário solicitados antes de conceder o consentimento:
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write
npx wrangler whoami --json
Verifique se loggedIn: true aparece. Leia o nome da conta mesmo que apenas uma conta seja listada. Substitua YOUR_ACCOUNT_ID abaixo pelo ID real de 32 caracteres dessa conta. openssl rand -hex 6 gera doze caracteres hexadecimais aleatórios para que este laboratório não colida com uma execução anterior. O here-document grava um arquivo de configuração padrão; o shell substitui nele as suas variáveis.
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 gerenciar o bucket, abra a página API Tokens do seu perfil da Cloudflare e crie um token personalizado com um nome relacionado a este laboratório. Conceda Account → Workers R2 Storage → Edit e restrinja Account Resources à conta de aprendizado cujo ID você salvou. Defina uma expiração curta. Não inclua outras contas nem permissões não relacionadas. Este token de gerenciamento serve para administrar buckets, incluindo sua criação e exclusão. Neste laboratório, o Worker acessa os objetos do R2 por meio do binding DOCUMENTS.
Copie o token uma única vez para este prompt oculto da VM. umask 077 restringe o acesso ao arquivo ao seu usuário; read -s oculta a entrada. O arquivo usa a variável de token padrão do Wrangler e é excluído do 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 somente nos comandos de gerenciamento do R2; o whoami comum continua verificando a autorização do dispositivo da VM.
Coloque --env-file no final de cada comando do Wrangler para que a lista de argumentos de arquivos não inclua o nome do comando. Depois de criar cada bucket, se o Wrangler perguntar se deve adicionar uma vinculação à configuração, digite n e pressione Enter. A configuração já contém a vinculação necessária.
npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management
Liste seus buckets e encontre o nome exato gerado. Os outros buckets pertencem a outros trabalhos; não os altere.
npx wrangler r2 bucket list --env-file=.env.management
No Dashboard, abra Storage & databases → R2 → Overview, selecione exatamente esse bucket e verifique se a lista de objetos está vazia. Nas configurações dele, mantenha desativados a URL pública de desenvolvimento e os domínios personalizados. O nome do bucket no Dashboard confirma a identidade; as verificações de download posteriores comprovarão os bytes armazenados.
A permissão de script do Worker permite a implantação. A permissão do KV permite que o Wrangler registre as exclusões; este laboratório não cria nenhum namespace KV. O token de gerenciamento do R2 continua sendo uma credencial separada e restrita à conta.
Implementar uploads limitados e downloads por streaming
Nesta etapa, você transforma o binding de configuração DOCUMENTS em operações sobre objetos. Um binding é um objeto de runtime que a Cloudflare fornece ao Worker. env.DOCUMENTS faz referência ao bucket privado configurado pelo nome; o Worker não precisa de um segredo do S3 para usá-lo.
O arquivo src/auth.js fornecido verifica um token bearer descartável. Nossa rota aceita somente nomes de documentos .txt simples. PUT substitui os bytes na chave selecionada. Este exemplo permite no máximo 1 MiB (1.048.576 bytes), inclusive para clientes que não informam um cabeçalho de tamanho. Os blocos do upload são coletados somente até esse limite, para que o R2 receba um corpo com tamanho conhecido. Os downloads passam object.body diretamente para a resposta e continuam sendo transmitidos por streaming.
Grave o handler com este here-document:
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() retorna null quando a chave não existe; trate esse caso antes de ler o corpo. writeHttpMetadata restaura o tipo de conteúdo salvo, e httpEtag já está corretamente entre aspas. private, no-store mantém esses documentos protegidos fora de caches compartilhados.
Crie um token de aplicativo aleatório em .dev.vars, que o Wrangler carrega durante o desenvolvimento local. Essa é uma credencial sintética do laboratório, separada das credenciais da sua conta da Cloudflare:
umask 077
printf "ACCESS_TOKEN=%s\n" "$(openssl rand -hex 24)" > .dev.vars
Verifique se o Wrangler consegue empacotar o código sem implantá-lo. A verificação da plataforma executa um runtime local temporário separado, com dados sintéticos novos, para verificar os bytes exatos, os dois caminhos do limite de tamanho e a ausência de objetos grandes demais:
npx wrangler deploy --dry-run
Exercitar o limite de armazenamento local
Nesta etapa, você executa o Worker usando o armazenamento R2 local. Por padrão, wrangler dev usa uma simulação local; portanto, essas requisições não criam nenhum objeto na nuvem. Execute o servidor de desenvolvimento em segundo plano; $! registra o ID do processo deste trabalho para a limpeza.
npx wrangler dev --ip 127.0.0.1 --port 8787 > dev.log 2>&1 &
DEV_PID=$!
Aguarde até que dev.log informe que o servidor está pronto e, em seguida, carregue o token descartável do aplicativo neste terminal. Não o exiba.
cat dev.log
set -a
source .dev.vars
set +a
Faça upload e download do arquivo preparado. --data-binary preserva os bytes; -o salva o download.
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 no upload e uma comparação bem-sucedida sem saída. Teste uma chave inexistente e um upload um byte acima do limite. O Python cria somente um arquivo sintético dentro do limite:
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 e 413 Too large. Essas chamadas do curl omitem --fail de propósito, para que os erros HTTP esperados continuem legíveis. Uma página HTML de erro de um proxy não é a resposta do aplicativo. Execute a verificação da plataforma antes de parar o servidor local.
Implantar e verificar a integração com o bucket privado
Nesta etapa, você repete o fluxo do documento no R2 real. O sucesso local não comprova o binding remoto nem a propriedade da conta.
Pare o servidor de desenvolvimento e publique o Worker:
kill "$DEV_PID"
wait "$DEV_PID" 2>/dev/null || true
npx wrangler deploy
Envie o segredo do aplicativo usando o comando em lote padrão. .dev.vars não é enviado automaticamente pelo deploy.
npx wrangler secret bulk .dev.vars
Copie a URL HTTPS exata do workers.dev exibida na saída da implantação para BASE_URL, sem barra no final. Aguarde até /health retornar ok; se a implantação ainda estiver sendo propagada, repita a leitura por até um minuto.
BASE_URL=https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev
curl -i "$BASE_URL/health"
Envie o relatório para o bucket remoto, faça o download e compare os arquivos:
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 e bytes idênticos. Repita as verificações negativas no 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 e 413 Too large. No Dashboard, abra este Worker e verifique o binding do R2; depois abra exatamente esse bucket para encontrar documents/report.txt. A URL pública de desenvolvimento e os domínios personalizados continuam desativados. O Worker fornece o caminho de acesso; a privacidade do bucket não significa que toda rota do Worker seja automaticamente segura.

A linha DOCUMENTS conecta este Worker ao bucket exato. Os nomes de recursos gerados no exemplo serão diferentes dos seus.

A linha mostra report.txt, text/plain e 41 B, com Public Access Disabled. Os nomes e datas são exemplos. Bucket Size pode demorar para atualizar e mostrar 0 B; a linha do objeto e o download bem-sucedido confirmam a existência do relatório.
Remover o aplicativo e o bucket remotos
Nesta etapa, você exclui somente o Worker e os objetos deste laboratório enquanto ainda está autorizado. O bucket privado não desaparece quando o Worker é excluído.
npx wrangler delete
Confirme o nome exato do Worker gerado. Exclua explicitamente o único objeto enviado e depois exclua o 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
A requisição grande demais não deveria ter criado documents/large.txt. Se o bucket estiver inesperadamente não vazio, inspecione somente este bucket e remova a chave sintética exata depois de diagnosticar a falha no contrato de tamanho. Nesse caso, a verificação funcional anterior não foi aprovada.
Atualize as listas de Workers e buckets no Dashboard e execute a verificação de limpeza da plataforma. Falhas de autenticação ou de rede são inconclusivas, não indicam uma exclusão bem-sucedida.
Revogar as credenciais restantes
Nesta etapa, você revoga o token de gerenciamento deste laboratório na página de tokens de API do seu perfil, remove o segredo local do aplicativo e encerra a autorização da VM. Faça isso somente depois que a verificação de limpeza anterior for aprovada.
rm .env.management .dev.vars
unset ACCESS_TOKEN
npx wrangler logout
npx wrangler whoami --json || true
Exija loggedIn: false. A revogação do token de gerenciamento é uma etapa manual separada no Dashboard; excluir somente o arquivo local não o revoga. Mantenha intactos o login comum do Dashboard e os tokens de outros laboratórios.
Resumo
Vincular o armazenamento privado do R2 a um Worker, aceitar uploads com limite, transmitir os bytes exatos dos documentos, tratar erros e remover os recursos de nuvem pertencentes a este laboratório.



