Limites de taille d’envoi d’images avec Next.js
Dans un gestionnaire de route Next.js, request.formData() est la façon documentée de lire un envoi, et rien dans Next.js ne limite la taille de cet envoi. Cette page présente ce que nous avons mesuré, la petite fonction readForm() que l’exemple utilise à la place, et les autres couches qui peuvent refuser une grosse image avant votre code.
formData() lit tout
Nous avons ajouté un gestionnaire de route temporaire qui se contentait de await request.formData(), et lui avons envoyé un fichier de 50 Mo. Il a répondu 200 avec fileSize: 50000000, et la mémoire du serveur de développement (RSS) a augmenté d’environ 175 Mo pendant cette seule requête. La documentation des gestionnaires de route décrit formData() sans mentionner aucune limite de taille. Elle précise que, contrairement aux routes API du Pages Router, les gestionnaires de route n’ont besoin d’aucune configuration bodyParser. Il n’y a donc aucune valeur de configuration à définir. Toute limite doit se trouver dans votre code.
Compter les octets avant l’analyse
readForm(), dans lib/gallery.ts, refuse un Content-Length déclaré supérieur à la limite ; sinon, elle lit le flux du corps et s’arrête dès que la limite est dépassée. Seul un corps qui respecte la limite est transmis à l’analyseur standard, via une Response :
// request.formData() reads the whole body into memory, however large. This reads at most
// MAX_BYTES plus room for the other fields, answers 413 above that, then parses it.
export async function readForm(request: Request): Promise<FormData> {
const limit = MAX_BYTES + 64 * 1024;
if (Number(request.headers.get("content-length")) > limit) {
throw new GalleryError(413, TOO_BIG);
}
const chunks: Uint8Array[] = [];
let size = 0;
const reader = request.body?.getReader();
while (reader) {
const { done, value } = await reader.read();
if (done) break;
size += value.length;
if (size > limit) {
await reader.cancel();
throw new GalleryError(413, TOO_BIG);
}
chunks.push(value);
}
const body = new Response(Buffer.concat(chunks), {
headers: { "content-type": request.headers.get("content-type") ?? "" },
});
try {
return await body.formData();
} catch {
throw new GalleryError(400, "Expected a multipart/form-data request.");
}
}La limite correspond au fichier de 4,000,000 octets plus 64 Ko pour les champs du formulaire et les en-têtes multipart qui les entourent. La vérification de Content-Length seule ne suffirait pas : une requête chunked n’indique pas de longueur, et nous en avons envoyé une. Le comptage sur le flux l’a interceptée : le 413 est revenu après que le client a envoyé environ 4.9 Mo d’un corps de 20 Mo, sans erreur de connexion côté client, aussi bien avec next dev qu’avec next start. reader.cancel() arrête la lecture : le reste n’est donc jamais mis en mémoire tampon. À titre de comparaison, Express avec multer a lu les 20 Mo en entier avant de répondre.
Après l’analyse, saveImage() vérifie le fichier lui-même. C’est cette vérification qui tranche à exactement 4,000,000 octets : un fichier de cette taille a été stocké, et un fichier de 4,000,001 octets a été refusé avec un 413.
Les autres couches
- La boîte de dialogue de la galerie.
maxUploadFileSize: 4000000dans les options de l’éditeur fait refuser par la boîte de dialogue un fichier plus grand avant tout envoi. Elle affiche « big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB. ». Le serveur ne peut pas s’y fier. - Un proxy inverse. La documentation de nginx donne à
client_max_body_sizeune valeur par défaut de 1m. Derrière un nginx par défaut, une photo de 2 Mo reçoit la page 413 propre à nginx et n’atteint jamais Next.js. - Un fichier
proxy(ce qu’on appelait auparavant middleware). La documentation de Next.js indique que, lorsqu’un tel fichier est utilisé, Next.js met le corps de la requête en mémoire tampon pour que le proxy et la route puissent tous deux le lire, jusqu’àexperimental.proxyClientMaxBodySize, 10 Mo par défaut. Au-delà, le corps est tronqué à la limite et un avertissement est journalisé. L’exemple n’a pas de fichier proxy : nous ne l’avons donc pas testé. D’après cette description, si vous en ajoutez un, un corps de plus de 10 Mo atteint votre gestionnaire de route tronqué au lieu d’être refusé. - Les Server Actions ont leur propre
serverActions.bodySizeLimit, 1 Mo par défaut d’après la documentation. Ce réglage concerne les Server Actions. Il n’a aucun effet sur un gestionnaire de route, et les envois de l’éditeur passent par un gestionnaire de route.
Le schéma en haut de la page montre l’ordre. Pour que le 413 de l’exemple soit celui que reçoivent les utilisateurs, chaque couche précédente doit autoriser un peu plus de 4,000,000 octets.
Ce que voit le navigateur
Chaque refus de l’exemple est un HTTP 413 avec {"error":"The image is larger than 4,000,000 bytes."}. La boîte de dialogue de la galerie affiche son propre texte pour tout envoi échoué, « Upload failed for <name>. » : avec la vérification de taille de la boîte de dialogue activée, les utilisateurs voient donc généralement plutôt le message de la boîte de dialogue.
Tester
Démarrez le projet d’exemple de ce guide avec npm run build et npm start, puis :
head -c 4500000 /dev/zero > big.jpg
curl -F "file=@big.jpg" http://localhost:5102/rte/upload
{"error":"The image is larger than 4,000,000 bytes."}Le statut est 413. Pour essayer le cas chunked, envoyez le corps sans longueur, par exemple avec fetch et un corps ReadableStream (duplex: "half"). La réponse est le même 413, et elle arrive avant la fin de l’envoi.
Le même guide pour d’autres plateformes
JavaScript · PHP · ASP.NET Core · MVC 5 · Web Forms