Limites de taille d’envoi d’images en Node.js

Schéma des couches qui peuvent refuser un gros envoi : la boîte de dialogue de la galerie, un proxy inverse, limits.fileSize de multer, le gestionnaire d’erreurs et la vérification des 4,000,000 octets dans saveImage()

Une photo tout droit sortie d’un téléphone dépasse souvent la limite que vous aviez en tête. Dans une configuration Node.js, jusqu’à cinq endroits peuvent la refuser, chacun avec une réponse différente. Cette page les passe en revue dans l’ordre où une requête les rencontre, montre comment l’exemple Express transforme la limite de multer en un 413 JSON propre, et traite d’un comportement de multer qui détermine la part d’un très gros envoi que votre serveur lit malgré tout.

Les couches, dans l’ordre

  1. La boîte de dialogue de la galerie. Avec maxUploadFileSize: 4000000 dans les options de l’éditeur, la boîte de dialogue refuse un fichier plus grand avant de l’envoyer. Un fichier de 4.5 Mo a donné « big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB. » (la boîte de dialogue compte en mégaoctets binaires), et aucune requête n’a été envoyée. C’est une commodité. N’importe qui peut appeler l’URL sans passer par la boîte de dialogue.
  2. Un proxy inverse, si vous en avez un. La documentation de nginx donne à client_max_body_size une valeur par défaut de 1m : derrière un nginx par défaut, une photo de 2 Mo n’atteint donc jamais Node. nginx répond avec sa propre page HTML 413, que la boîte de dialogue signale comme un envoi échoué.
  3. Les limits de multer, que vous définissez. Ses propres valeurs par défaut pour fileSize, files, fields et parts sont Infinity, ce qui, avec le stockage en mémoire, signifie tout le fichier en mémoire.
  4. Le middleware d’erreurs, qui transforme l’erreur de multer en JSON attendu par le navigateur.
  5. La vérification propre à l’exemple dans saveImage(), avec les mêmes 4,000,000 octets, qui ne compte que si la limite de multer est un jour relevée.

Pour que le message de l’exemple soit celui que voient les utilisateurs, chaque couche précédente doit autoriser un peu plus de 4,000,000 octets. L’encodage multipart ajoute quelques centaines d’octets autour du fichier.

La limite de multer

JavaScript · server.mjs
// 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 est la plus grande taille autorisée, et non la première refusée. Nous avons envoyé un fichier d’exactement 4,000,000 octets, et il a été stocké. 4,000,001 octets ont été refusés. Les autres limites sont là parce que le stockage en mémoire conserve tout : un fichier, dix champs texte de 1,024 octets au maximum, douze parties en tout. Le fieldSize par défaut de multer est de 1 Mo par champ texte.

De MulterError à un 413

Quand un fichier dépasse fileSize, multer appelle next(err) avec une MulterError. Son code est "LIMIT_FILE_SIZE", son message est "File too large" et son field est "file". Sans gestionnaire d’erreurs, nous avons obtenu le comportement par défaut d’Express : un statut 500 avec une page HTML, qui inclut la trace de pile à moins que NODE_ENV=production ne soit défini. Le middleware d’erreurs de l’exemple vérifie le code :

JavaScript · server.mjs
// 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." });
});

Toutes les autres erreurs de multer (LIMIT_FILE_COUNT pour un second fichier, LIMIT_UNEXPECTED_FILE pour un fichier envoyé sous un autre nom de champ) deviennent un 400 avec le message de multer. Vérifiez err.code, et non le texte du message.

multer lit d’abord le reste du corps

multer n’envoie pas le 413 dès que la limite est franchie. En cas d’erreur, il lit et jette le reste de la requête avant d’appeler next(err). Un commentaire dans le code source de multer en donne la raison : fermer la connexion pendant que le client envoie encore provoque des erreurs EPIPE chez le client. Nous l’avons mesuré : pour un envoi de 20 Mo sans Content-Length, le client avait envoyé les 20,000,000 octets avant que le 413 ne revienne. L’exemple Next.js, qui arrête la lecture à sa limite, a répondu après environ 4.9 Mo.

La mémoire ne risque rien, car les octets en trop sont jetés. La bande passante et le temps, si : un envoi de 1 Go est lu jusqu’au bout. Si cela compte, refusez les gros corps avant Node, au niveau du proxy inverse, ou vérifiez req.headers["content-length"] dans un middleware placé avant multer. Une vérification de Content-Length seule ne couvre pas les requêtes chunked, qui n’indiquent pas de longueur.

Tester

Démarrez le projet d’exemple de ce guide avec npm start, créez un fichier qui dépasse la limite et envoyez-le :

Shell
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."}

Le statut est 413. Le fichier ne contient que des zéros : il échouerait donc aussi à la vérification de la signature JPEG, mais la limite de multer intervient en premier. Pour voir la vérification propre à la boîte de dialogue, choisissez un fichier de plus de 4,000,000 octets avec le bouton Upload de la galerie. La boîte de dialogue affiche le message de taille et n’envoie rien.

Le même guide pour d’autres plateformes

Next.js · PHP · ASP.NET Core · MVC 5 · Web Forms

À lire ensuite