Transmitir documentos por meio de um Worker

CloudflareBeginner
Pratique Agora

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.

Binding DOCUMENTS do Worker conectado ao bucket R2 privado

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

Relatório enviado pelo Worker a um bucket privado Standard

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.