Größenlimits für Bild-Uploads in Next.js

Diagramm der Ebenen, die einen großen Upload in Next.js ablehnen können: der Galerie-Dialog, ein Reverse Proxy, request.formData() ohne Limit, readForm() und die Prüfung auf 4,000,000 Bytes

In einem Next.js-Route-Handler ist request.formData() der dokumentierte Weg, einen Upload zu lesen, und nichts in Next.js begrenzt, wie groß dieser Upload sein darf. Diese Seite zeigt, was wir gemessen haben, die kleine Funktion readForm(), die das Beispiel stattdessen verwendet, und die anderen Ebenen, die ein großes Bild ablehnen können, bevor Ihr Code es tut.

formData() liest alles

Wir haben vorübergehend einen Route Handler ergänzt, der nichts anderes tat als await request.formData(), und ihm eine 50-MB-Datei gesendet. Er antwortete mit 200 und fileSize: 50000000, und der Speicher (RSS) des Dev-Servers wuchs während dieses einen Requests um etwa 175 MB. Die Dokumentation zu Route Handlern beschreibt formData() und nennt kein Größenlimit. Sie merkt an, dass Route Handler anders als API-Routen im Pages Router keine Konfiguration für bodyParser brauchen. Es gibt also keinen Konfigurationswert, den Sie setzen könnten. Jedes Limit muss in Ihrem Code stehen.

Die Bytes vor dem Parsen zählen

readForm() in lib/gallery.ts lehnt einen angegebenen Content-Length über dem Limit ab, liest andernfalls den Body-Stream und hört auf, sobald er das Limit überschreitet. Nur ein Body innerhalb des Limits wird über eine Response an den Standardparser übergeben:

TypeScript · lib/gallery.ts
// 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.");
  }
}

Das Limit ist die Datei mit 4,000,000 Bytes plus 64 KB für die Formularfelder und die Multipart-Header um sie herum. Die Prüfung von Content-Length allein würde nicht reichen: Ein Chunked Request hat keine Länge, und wir haben einen gesendet. Das Zählen im Stream hat ihn abgefangen: Das 413 kam zurück, nachdem der Client etwa 4.9 MB eines 20-MB-Bodys gesendet hatte, ohne Verbindungsfehler auf Clientseite, sowohl in next dev als auch in next start. reader.cancel() beendet das Lesen, der Rest wird also nie gepuffert. Zum Vergleich: Express mit multer las alle 20 MB, bevor es antwortete.

Nach dem Parsen prüft saveImage() die Datei selbst. Diese Prüfung entscheidet genau bei 4,000,000 Bytes: Eine Datei dieser Größe wurde gespeichert, eine mit 4,000,001 Bytes mit einem 413 abgelehnt.

Die anderen Ebenen

  1. Der Galerie-Dialog. maxUploadFileSize: 4000000 in den Editor-Optionen lässt den Dialog eine größere Datei ablehnen, bevor er etwas sendet. Er zeigt „big.jpg is 4.3 MB. The maximum allowed size is 3.8 MB.“ an. Darauf kann sich der Server nicht verlassen.
  2. Ein Reverse Proxy. Laut nginx-Dokumentation ist der Standardwert von client_max_body_size 1m. Hinter einem nginx mit Standardeinstellungen erhält ein 2-MB-Foto die eigene 413-Seite von nginx und erreicht Next.js nie.
  3. Eine proxy-Datei (früher Middleware genannt). Laut Next.js-Dokumentation puffert Next.js den Request-Body, wenn eine solche Datei verwendet wird, damit sowohl der Proxy als auch die Route ihn lesen können, bis zu experimental.proxyClientMaxBodySize, standardmäßig 10 MB. Darüber wird der Body beim Limit abgeschnitten und eine Warnung protokolliert. Das Beispiel hat keine Proxy-Datei, daher haben wir das nicht getestet. Nach dieser Beschreibung erreicht ein Body über 10 MB Ihren Route Handler abgeschnitten statt abgelehnt, wenn Sie eine solche Datei hinzufügen.
  4. Server Actions haben ein eigenes serverActions.bodySizeLimit, laut Dokumentation standardmäßig 1 MB. Diese Einstellung gilt für Server Actions. Für einen Route Handler bewirkt sie nichts, und die Uploads des Editors gehen an einen Route Handler.

Das Diagramm oben zeigt die Reihenfolge. Damit die Benutzer das 413 des Beispiels erhalten, muss jede frühere Ebene etwas mehr als 4,000,000 Bytes zulassen.

Was der Browser sieht

Jede Ablehnung durch das Beispiel ist ein HTTP 413 mit {"error":"The image is larger than 4,000,000 bytes."}. Der Galerie-Dialog zeigt bei jedem fehlgeschlagenen Upload seinen eigenen Text, „Upload failed for <name>.“. Ist die Größenprüfung des Dialogs aktiv, sehen die Benutzer daher meist stattdessen die Meldung des Dialogs.

Testen

Starten Sie das Beispielprojekt zu dieser Anleitung mit npm run build und npm start, dann:

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

Der Status ist 413. Um den Fall mit Chunks auszuprobieren, senden Sie den Body ohne Länge, zum Beispiel mit fetch und einem ReadableStream als Body (duplex: "half"). Die Antwort ist dasselbe 413, und sie kommt an, bevor der Upload abgeschlossen ist.

Dieselbe Anleitung für andere Plattformen

JavaScript · PHP · ASP.NET Core · MVC 5 · Web Forms

Weiterlesen