Limites de taille d’envoi avec ASP.NET Core : Kestrel, IIS et MVC

Schéma : cinq couches qui peuvent refuser un gros envoi, de maxAllowedContentLength d’IIS et MaxRequestBodySize de Kestrel jusqu’à la limite de la route, l’analyseur de formulaire et la vérification propre à l’application

Avec ASP.NET Core, une grande photo peut être refusée à six endroits, et seul le dernier est votre code. Les autres répondent à leur manière : un 413 sans corps, un ProblemDetails 400 de MVC, une page d’erreur IIS, ou une connexion coupée que le navigateur signale comme une erreur réseau. Cette page liste les couches dans l’ordre où une requête les rencontre, avec ce que chacune a renvoyé lors de nos tests.

Les couches, dans l’ordre

  1. L’éditeur. Avec maxUploadFileSize: 4000000 dans les options, la galerie 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. Le serveur ne peut pas s’appuyer sur cette vérification.
  2. IIS, si l’application s’exécute in-process derrière lui. D’après la documentation de Microsoft, le filtrage des requêtes rejette les corps qui dépassent maxAllowedContentLength (en octets, 30,000,000 par défaut) avec la page 404.13 propre à IIS, avant qu’ASP.NET Core ne s’exécute. Nous n’avons pas testé IIS pour cette page.
  3. Le MaxRequestBodySize de Kestrel, 30,000,000 octets par défaut. Un envoi de 31,000,000 octets a reçu un 413.
  4. La limite propre à la route. [RequestSizeLimit] remplace la limite de Kestrel pour une action. Dans une Minimal API, le même attribut fonctionne comme métadonnée du point de terminaison.
  5. L’analyseur multipart. FormOptions.MultipartBodyLengthLimit vaut 134,217,728 octets par défaut. Définissez-le par action avec [RequestFormLimits], ou par point de terminaison avec .WithFormOptions.
  6. Votre vérification : 4,000,000 octets dans l’exemple, avec une réponse 413 en JSON.

Le code

C# · RteFiles.cs
public const long MaxFileBytes = 4_000_000;
// The request is a little bigger than the file: multipart boundaries, headers, the "folder" field.
public const long MaxRequestBytes = MaxFileBytes + 64 * 1024;

La limite de requête est la limite de fichier plus 64 Ko pour les délimiteurs multipart et le champ folder. Cet écart est voulu : un fichier qui dépasse à peine 4,000,000 octets atteint quand même le code et reçoit une réponse lisible.

C# · Controllers/RteController.cs
// POST /rte/upload   file, folder
[HttpPost("upload")]
[RequestSizeLimit(RteFiles.MaxRequestBytes)]                              // the whole request body
[RequestFormLimits(MultipartBodyLengthLimit = RteFiles.MaxRequestBytes)]  // the multipart parser
public async Task<IActionResult> Upload(IFormFile? file, [FromForm] string? folder) =>
    Send(await RteFiles.Save(HttpContext, file, folder));
C# · RteEndpoints.cs
rte.MapPost("/upload", async (HttpContext http, IFormFile? file, [FromForm] string? folder) =>
    Json(await RteFiles.Save(http, file, folder)))
    .DisableAntiforgery()
    // Kestrel accepts 30,000,000-byte bodies by default; this route needs ~4 MB.
    .WithMetadata(new RequestSizeLimitAttribute(RteFiles.MaxRequestBytes))
    .WithFormOptions(multipartBodyLengthLimit: RteFiles.MaxRequestBytes);

Ce que voit le navigateur

Quand une requête dépasse la limite de la route, Kestrel répond 413 et ferme la connexion pendant que le navigateur est encore en train d’envoyer. Nous avons envoyé 10 Mo depuis Chromium avec une XHR, en contournant la vérification propre à l’éditeur, dans les quatre exemples ASP.NET Core. À chaque fois, la XHR s’est terminée dans onerror avec le statut 0, jamais avec un 413. rte-files.js signale alors « The server could not be reached. ». curl, qui attend la réponse, a bien reçu le 413. C’est donc le maxUploadFileSize de l’éditeur qui donne aux utilisateurs un message utile pour les gros fichiers. Gardez-le égal à la limite du serveur.

MVC répond 400, pas 413

Dans une Minimal API, un corps qui dépasse la limite de la route échoue pendant la liaison des paramètres, et le point de terminaison répond 413 avec un corps vide. Dans un [ApiController], l’échec a lieu pendant la liaison du modèle, et la validation automatique du modèle répond par un ProblemDetails 400 :

Corps de la réponse HTTP 400, envoi de 10,000,000 octets vers l’exemple MVC (traceId omis)
{"type":"https://tools.ietf.org/html/rfc9110#section-15.5.1","title":"One or more validation errors occurred.","status":400,
 "errors":{"":["Failed to read the request form. Request body too large. The max request body size is 4065536 bytes."]}}

Un fichier de 4,100,000 octets a donné la même réponse, car avec l’enveloppe multipart la requête dépassait tout juste 4,065,536 octets. Le 413 {"error": ...} de la vérification propre à l’exemple exige que toute la requête tienne sous la limite de la route, ce qui est le cas d’un fichier de 4,050,000 octets.

Au-delà de MultipartBodyLengthLimit, avec la limite de requête désactivée, un point de terminaison Minimal API a lui aussi répondu 400 (envoi de 135,000,000 octets face à la valeur par défaut de 134,217,728).

Relever la limite

Modifiez ensemble MaxFileBytes dans RteFiles.cs et maxUploadFileSize dans la page. MaxRequestBytes suit MaxFileBytes : la limite de la route et la limite multipart évoluent donc avec elle. Au-delà de 30,000,000 octets, chaque couche placée en amont doit aussi l’autoriser : la limite globale de Kestrel s’applique toujours aux autres routes, et la documentation de Microsoft indique qu’IIS nécessite de relever maxAllowedContentLength dans web.config, car [RequestSizeLimit] ne peut pas la dépasser.

Tester

Lancez le projet d’exemple de ce guide avec dotnet run (port 5302), et l'exemple Minimal API (port 5301) à côté :

Shell
head -c 4050000 /dev/zero > big.jpg
curl -F "file=@big.jpg" http://localhost:5302/rte/upload
{"error":"The image is larger than 4 MB."}

head -c 10000000 /dev/zero > huge.jpg
curl -s -w "\nHTTP %{http_code}\n" -F "file=@huge.jpg" http://localhost:5301/rte/upload

HTTP 413

La première réponse est un 413 de la vérification de l’exemple, sur l’un ou l’autre port. La seconde est le 413 de Kestrel, sans corps. Envoyez huge.jpg au port 5302 pour voir le 400 de MVC décrit plus haut.

Le même guide pour d’autres plateformes

JavaScript · Next.js · PHP · MVC 5 · Web Forms

À lire ensuite