DevGuideTK
← Trilha Backend

Armazenamento de objetos

Onde guardar arquivos, com URLs assinadas e envio direto do navegador.

O problema: onde guardar os arquivos

Os usuários mandam fotos de perfil, notas fiscais, contratos assinados. A primeira ideia costuma ser salvar no disco do servidor. Ela quebra rápido: com duas instâncias da aplicação, cada uma tem o seu disco; num contêiner, o disco some a cada deploy; e o backup precisa de um cuidado à parte. A segunda ideia, guardar o arquivo no banco, enche o banco de bytes que nenhuma consulta usa, e cada backup carrega todos eles.

O armazenamento de objetos resolve isso. Cada arquivo é um objeto, guardado sob uma chave (fotos/981.png) dentro de um bucket, e acessado por HTTP. Não há pastas de verdade, nem edição parcial: um objeto é gravado inteiro e lido inteiro, ou em faixas de bytes. O Amazon S3 criou a interface que quase todos seguem: Google Cloud Storage, Azure Blob Storage, Cloudflare R2 e servidores próprios como Ceph e SeaweedFS.

Disco do servidorBanco de dadosArmazenamento de objetos
Várias instânciasCada uma com seu discoCompartilhadoCompartilhado
Sobrevive à troca da instânciaNãoSimSim
Custo por GBMédioAltoBaixo
Entrega direta ao navegadorPela aplicaçãoPela aplicaçãoPor URL, sem passar pela aplicação

O banco guarda os dados sobre o arquivo (dono, nome original, tamanho, chave); o armazenamento guarda os bytes.

Os testes desta página usaram o SeaweedFS 4.53, um servidor compatível com a API do S3, e os SDKs oficiais da AWS nas cinco linguagens. O MinIO, mais conhecido para testes locais, não estava mais disponível no Docker Hub no momento do teste.

URLs assinadas

O bucket é privado: um pedido anônimo para um objeto recebeu 403 no teste. Para que o navegador baixe ou envie um arquivo sem receber as credenciais, a aplicação gera uma URL assinada (presigned URL): uma URL comum com uma assinatura que vale só para aquele objeto, aquela operação e por um tempo limitado.

https://armazenamento/arquivos/fotos/1.png
  ?X-Amz-Algorithm=AWS4-HMAC-SHA256
  &X-Amz-Credential=.../20261115/us-east-1/s3/aws4_request
  &X-Amz-Expires=300
  &X-Amz-SignedHeaders=content-type;host
  &X-Amz-Signature=dff2ecaa...

Testamos uma URL de envio válida por 5 segundos, com o tipo image/png incluído na assinatura:

TentativaResultado
Enviar um PNG com Content-Type: image/png200
Enviar HTML com Content-Type: text/html, na mesma URL403
Trocar a chave na URL para fotos/2.png403
Enviar o PNG de novo, 6 segundos depois403, a URL venceu

O fluxo de envio direto

Com URLs assinadas, o arquivo não precisa passar pela aplicação:

  1. O navegador pede à aplicação uma URL de envio, dizendo o tipo e o tamanho do arquivo.
  2. A aplicação confere se o usuário pode enviar, escolhe a chave e devolve a URL assinada.
  3. O navegador envia o arquivo direto para o armazenamento.
  4. O navegador avisa a aplicação que terminou, e ela confirma que o objeto existe antes de registrar.

A diferença aparece na memória da aplicação. Medimos um servidor Node que recebe um arquivo de 300 MB e o repassa ao armazenamento:

Forma de repassarPico de memória do servidor
Juntar o arquivo inteiro na memória e enviar716 MB
Repassar em fluxo, em partes de 8 MB139 MB
Envio direto do navegador com URL assinadaNenhum: o arquivo não passa pela aplicação

Dez usuários enviando vídeos ao mesmo tempo, com a primeira forma, somam gigabytes de memória. O envio direto também tira o tráfego da aplicação e não ocupa uma conexão dela pelo tempo inteiro do envio.

O que a assinatura precisa fixar

Uma URL de envio é uma permissão dada ao navegador do usuário, e o usuário pode usá-la como quiser. O que não estiver fixado na assinatura fica por conta dele.

O tipo do arquivo. Uma URL assinada sem o tipo aceitou, no teste, um HTML gravado como fotos/3.png, com Content-Type: text/html. Na leitura, o armazenamento devolveu Content-Type: text/html. Se esse arquivo for servido a partir do domínio do sistema, o navegador o abre como uma página, com acesso aos cookies do domínio: um XSS armazenado, só com um "upload de foto". Duas defesas, as duas testadas:

  • Assinar o tipo: com image/png na assinatura, o envio de text/html recebeu 403.
  • Forçar a resposta na URL de download (response-content-disposition=attachment e response-content-type=application/octet-stream): o mesmo arquivo foi entregue como application/octet-stream, para baixar, e não como página.

O tamanho. Uma URL de envio por PUT, pensada para fotos de perfil, aceitou um arquivo de 20 MB: a assinatura de um PUT não limita o tamanho. O envio por POST com uma política (POST policy) limita: com content-length-range de até 1 MB, um arquivo de 500 KB foi aceito (204) e um de 2 MB recusado com EntityTooLarge.

Por que isso importa quando a IA escreve o código

Um assistente que implementa "upload de arquivo" costuma escolher o caminho mais curto:

  • Salvar no disco do servidor, ou em uma coluna do banco.
  • Receber o arquivo inteiro na memória da aplicação antes de enviá-lo ao armazenamento.
  • Buckets públicos, para "facilitar" a exibição.
  • URLs assinadas sem o tipo e sem limite de tamanho, ou com validade de dias.
  • Arquivos enviados por usuários servidos do próprio domínio, com o tipo que o usuário escolheu.
  • O nome original do arquivo usado como chave, com colisões e caracteres problemáticos.

O próximo nível compara as formas de envio e de entrega, e as configurações de bucket.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Armazenamento de objetos em cinco minutos

    Explique em no máximo cinco minutos de leitura o que é armazenamento de objetos, como o Amazon S3, para alguém que sempre salvou arquivos no disco do servidor.
    
    Cubra só o essencial: objetos, chaves e buckets, por que não guardar arquivos no disco nem no banco, URLs assinadas e o envio direto do navegador.
    
    Use o exemplo de fotos de perfil e notas fiscais. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
  • URLs assinadas por dentro

    Quero entender como funcionam as URLs assinadas do S3.
    
    Aja como um mentor e vá por etapas, esperando a minha resposta em cada uma:
    1. O que entra na assinatura: método, chave, validade e cabeçalhos assinados.
    2. Por que trocar a chave ou o tipo invalida a URL.
    3. A diferença entre assinar um PUT e uma política de POST.
    4. Como forçar o tipo e o Content-Disposition da resposta num download.
    5. Por que a validade deve ser curta, e o que acontece com uma URL vazada.
    
    Em cada etapa, me mostre uma URL ou um pedido e peça que eu diga se ele seria aceito.
  • Os riscos do upload de arquivos

    Quero entender os riscos de segurança de receber arquivos dos usuários.
    
    Explique, com um exemplo de ataque para cada um:
    1. HTML ou SVG enviado como imagem e servido do domínio do sistema (XSS armazenado).
    2. Arquivos enormes enchendo o armazenamento e a conta.
    3. Chaves previsíveis e acesso a arquivos de outros usuários.
    4. Buckets públicos por engano.
    5. Arquivos com conteúdo malicioso para quem vai abri-los.
    
    Para cada um, diga a defesa e como testar.
  • Envio pela aplicação ou direto

    Quero entender as formas de receber arquivos dos usuários.
    
    Compare, com o fluxo de cada uma:
    1. Receber na aplicação em memória.
    2. Receber na aplicação e repassar em fluxo.
    3. Envio direto do navegador com URL assinada por PUT.
    4. Envio direto com POST e política.
    5. Envio em partes para arquivos grandes.
    
    Explique o efeito de cada forma na memória e nas conexões da aplicação, e depois me descreva três funcionalidades e pergunte qual forma eu usaria.