Limites de tamanho para upload de imagens em Node.js
Uma foto tirada direto do celular muitas vezes é maior do que o limite que você tinha em mente. Em uma configuração com Node.js, até cinco lugares podem recusá-la, cada um com uma resposta diferente. Esta página passa por eles na ordem em que a requisição os encontra, mostra como o exemplo em Express transforma o limite do multer em um 413 limpo em JSON e trata de um comportamento do multer que decide quanto de um upload enorme o seu servidor lê de qualquer forma.
As camadas, em ordem
- A caixa de diálogo da galeria. Com
maxUploadFileSize: 4000000nas opções do editor, a caixa de diálogo recusa um arquivo maior antes de enviá-lo. Um arquivo de 4.5 MB gerou "big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB." (a caixa de diálogo conta em megabytes binários), e nenhuma requisição foi enviada. Isso é uma conveniência. Qualquer pessoa pode chamar a URL sem a caixa de diálogo. - Um proxy reverso, se você tiver um. A documentação do nginx dá a
client_max_body_sizeo padrão de 1m, então, atrás de um nginx com a configuração padrão, uma foto de 2 MB nunca chega ao Node. O nginx responde com a própria página HTML de 413, que a caixa de diálogo informa como upload com falha. - Os
limitsdo multer, que você define. Os padrões do próprio multer parafileSize,files,fieldsepartssãoInfinitye, com armazenamento em memória, isso significa o arquivo inteiro na memória. - O middleware de erro, que transforma o erro do multer no JSON que o navegador espera.
- A verificação do próprio exemplo em
saveImage(), que usa os mesmos 4,000,000 bytes e só importa se o limite do multer for aumentado algum dia.
Para que a mensagem do exemplo seja a que as pessoas veem, todas as camadas anteriores precisam permitir um pouco mais do que 4,000,000 bytes. A codificação multipart acrescenta algumas centenas de bytes em volta do arquivo.
O limite do multer
// multer is the multipart parser most Express apps already use. Memory storage keeps the
// file in req.file.buffer, so its bytes can be checked before anything touches the disk;
// the limits keep that buffer small. multer allows exactly fileSize bytes and fails with
// LIMIT_FILE_SIZE above that (turned into a 413 in the error handler below).
const parseForm = multer({
storage: multer.memoryStorage(),
limits: { fileSize: MAX_BYTES, files: 1, fields: 10, fieldSize: 1024, parts: 12 },
defParamCharset: "utf8", // file names are sent as UTF-8; multer's default is latin1
});fileSize é o maior tamanho permitido, não o primeiro recusado. Enviamos um arquivo de exatamente 4,000,000 bytes e ele foi armazenado. Um de 4,000,001 bytes foi recusado. Os outros limites existem porque o armazenamento em memória guarda tudo: um arquivo, dez campos de texto de no máximo 1,024 bytes, doze partes no total. O fieldSize padrão do multer é 1 MB por campo de texto.
De MulterError a um 413
Quando um arquivo ultrapassa fileSize, o multer chama next(err) com um MulterError. O code dele é "LIMIT_FILE_SIZE", a message é "File too large" e o field é "file". Sem um handler de erro, recebemos o padrão do Express: status 500 com uma página HTML e, a menos que NODE_ENV=production esteja definido, essa página inclui o stack trace. O middleware de erro do exemplo verifica o código:
// Express 5 sends errors thrown in async handlers here, so the routes need no try/catch.
app.use((err, req, res, next) => {
if (err instanceof GalleryError) {
return res.status(err.status).json({ error: err.message });
}
if (err instanceof multer.MulterError) {
const tooBig = err.code === "LIMIT_FILE_SIZE";
return res
.status(tooBig ? 413 : 400)
.json({ error: tooBig ? "The image is larger than 4,000,000 bytes." : "Invalid upload: " + err.message });
}
console.error(err);
res.status(500).json({ error: "Server error." });
});Todos os outros erros do multer (LIMIT_FILE_COUNT para um segundo arquivo, LIMIT_UNEXPECTED_FILE para um arquivo com outro nome de campo) viram um 400 com a mensagem do multer. Verifique err.code, não o texto da mensagem.
O multer lê o resto do corpo primeiro
O multer não envia o 413 no instante em que o limite é ultrapassado. Em caso de erro, ele lê e descarta o resto da requisição antes de chamar next(err). Um comentário no código-fonte do multer dá o motivo: fechar a conexão enquanto o cliente ainda está enviando causa erros EPIPE no cliente. Medimos isso: em um upload de 20 MB enviado sem Content-Length, o cliente já tinha enviado todos os 20,000,000 bytes quando o 413 voltou. O exemplo em Next.js, que para de ler ao atingir o limite, respondeu depois de cerca de 4.9 MB.
A memória está segura, porque os bytes extras são descartados. A banda e o tempo, não: um upload de 1 GB é lido até o fim. Se isso importa, recuse corpos grandes antes do Node, no proxy reverso, ou verifique req.headers["content-length"] em um middleware antes do multer. Uma verificação de Content-Length sozinha não cobre requisições chunked, que não informam tamanho.
Teste
Inicie o projeto de exemplo deste guia com npm start, crie um arquivo acima do limite e envie-o:
head -c 4500000 /dev/zero > big.jpg
curl -F "file=@big.jpg" http://localhost:5101/rte/upload
{"error":"The image is larger than 4,000,000 bytes."}O status é 413. O arquivo é todo de zeros, então também falharia na verificação de assinatura JPEG, mas o limite do multer vem primeiro. Para ver a verificação da própria caixa de diálogo, escolha um arquivo acima de 4,000,000 bytes no botão Upload da galeria. A caixa de diálogo mostra a mensagem de tamanho e não envia nada.
O mesmo guia para outras plataformas
Next.js · PHP · ASP.NET Core · MVC 5 · Web Forms