Wanneer een grote PDF-upload wordt geweigerd: de limiet van 100 MiB op de body van een verzoek en de fout die u de verkeerde kant op stuurt
Een body van een verzoek mag 100 MiB zijn, oftewel 104.857.600 bytes inclusief multipart-framing; boven de limiet antwoorden endpoints voor bewerkingen met 400 en code bad_request plus een detail over een mislukt multipart-veld, niet met 413, en met Idempotency-Key geldt een tweede limiet van 100 MiB op de antwoordbuffer, waarboven u 500 krijgt en niets wordt gecachet.

Een grote PDF die niet wil uploaden is meestal geen beschadigd bestand. De body van het verzoek raakte de limiet per verzoek: 100 MiB, oftewel 104.857.600 bytes. Die wordt over de hele body gemeten, multipart-grenzen en veldheaders inbegrepen, en precies die waarde komt erdoor terwijl één byte meer wordt geweigerd.
De fout die terugkomt, wijst een andere kant op. Endpoints voor bewerkingen antwoorden met 400, code staat op bad_request en het detail meldt dat een multipart-veld niet gelezen kon worden, zonder ergens de grootte te noemen. Een client die op code vertakt, bergt dit op als parameterfout en gaat veldnamen controleren, terwijl de bestandsgrootte het is die moet veranderen.
Diezelfde 100 MiB beheerst ook de tegenovergestelde richting. Een verzoek met Idempotency-Key leest het antwoord in het geheugen voordat het wordt gecachet, tegen dezelfde waarde, en daarboven krijgt de aanroeper 500 terwijl de bewerking al klaar is. Eén getal, twee tegengestelde faalwijzen.
100 MiB = 104.857.600 bytes (precies deze waarde komt erdoor)
|
+------------------+------------------+
| |
inkomend (body van het verzoek) uitgaand (antwoordlichaam)
hele body, multipart-grenzen en alleen met Idempotency-Key,
veldheaders inbegrepen; één een cachemiss en een
bewerking en een pipeline delen upstream-2xx
het; daarboven: 400 + bad_request daarboven: 500, niets gecachet
(detail: een multipart-veld mislukt)
Toelichting bij de figuur: één limiet, en de inkomende en uitgaande kant ervan geven verschillende statuscodes terug met tegengestelde gevolgen.
De limiet telt de hele body van het verzoek, grenzen inbegrepen
De 100 MiB begrenzen de body van één verzoek, niet de grootte van één bestand en niet de grootte na decompressie.
- Een enkele bewerking en een pijplijn met meerdere stappen delen die waarde. Het werk in 10 stappen binnen één aanroep van
/api/v1/pipelinesplitsen maakt het plafond niet 1 GB; het aantal stappen beïnvloedt alleen de looptijd. - De grenswaarde zelf komt erdoor: een body van 104.857.600 bytes gaat door, 104.857.601 bytes niet.
- De body bevat naast de bestandsbytes ook de headers van elk veld en de grenstekens, dus de ruimte die voor één bestand overblijft ligt strikt onder 100 MiB. Een bestand van precies 104.857.600 bytes wordt geweigerd.
Dat laatste punt is waar de praktijk het makkelijkst misgaat: curl -F voegt de grenzen zelf toe, dus een bestandsgrootte met die lijn vergelijken kan nooit goed uitkomen.
Boven de limiet krijgt u 400 en bad_request
Een antwoord boven de limiet gebruikt geen 413 en bevat helemaal geen tekst over grootte. Reproduceer het met een body van één byte te veel:
head -c 104857601 /dev/zero > /tmp/over.bin
curl -s -X POST "$API_BASE/api/v1/misc/compress-pdf" \
-H "X-API-KEY: $API_KEY" \
-F "fileInput=@/tmp/over.bin"
HTTP/1.1 400 Bad Request
content-type: application/problem+json
{ "code": "bad_request",
"detail": "failed to read multipart field: Error parsing `multipart/form-data` request",
"hint": "Fix request parameters or upload a valid PDF.",
"status": 400,
"title": "Bad Request",
"type": "https://pdf123.xyz/developers/errors#bad_request" }
Drie dingen om samen te lezen:
- De status is 400. Het niet kunnen lezen van de body wordt hier als
bad_requestgeclassificeerd en gaat nog steeds via problem+json, dus een client die geschreven is voor “over de limiet betekent 413” neemt de verkeerde vertakking. codeisbad_request, een echte vermelding in de foutcodetabel. Die valt niet terug op een generieke tak; hij deelt één code met een verkeerd gespelde veldnaam of een kapotte multipart-codering, en niets in die tabel gaat over grootte.- De
hintzegt dat u een geldige PDF moet uploaden. Het bestand dat u uploadde is zeer waarschijnlijk een geldige PDF die toevallig een paar honderd bytes te groot is.
De aanwijzing is dus niet de statuscode maar het aantal bytes van de body: meet bij een 400 waarvan het detail de tekst failed to read multipart field bevat, eerst de grootte in plaats van het formulier opnieuw langs te lopen.
413 komt op deze site wel voor, alleen niet op endpoints voor bewerkingen. Elk toegangspunt dat een bestandsupload aanneemt is verhoogd naar 100 MiB, terwijl endpoints zonder upload nog steeds op de standaardwaarde van 2 MiB van het HTTP-framework (axum van Rust) draaien; daar krijgt een te grote body 413 en één regel platte tekst:
head -c 2097153 /dev/zero > /tmp/big.json
curl -s -w '\n%{http_code}\n' -X POST "$API_BASE/api/v1/auth/login" \
-H 'Content-Type: application/json' \
--data-binary @/tmp/big.json
Failed to buffer the request body: length limit exceeded
413
Eén feit, twee statuscodes en twee antwoordlichamen, afhankelijk van het endpoint. Wie een van beide ervaringen op de andere overbrengt, wordt misleid.
De andere fout met hetzelfde getal, op de terugweg
Een verzoek met Idempotency-Key cachet zijn antwoord zodat een nieuwe poging het opnieuw kan afspelen. Cachen betekent dat het antwoordlichaam eerst in het geheugen wordt gelezen, en dat lezen is begrensd op dezelfde 100 MiB, al geldt het onder veel smallere voorwaarden:
- Het verzoek had
Idempotency-Keybij zich. - De cache miste, dus deze sleutel is nieuw.
- De upstream-bewerking gaf 2xx terug.
Mislukkingen worden niet gecachet en gaan er direct doorheen, dus alleen een geslaagde grote uitvoer bereikt deze limiet.
Wat daar gebeurt, is het punt om te onthouden: u krijgt 500, en er wordt niets naar de cache geschreven. De bewerking is al uitgevoerd, toch ziet de aanroeper een mislukking; omdat er niets is gecachet, doet de nieuwe poging het hele werk opnieuw. De idempotency-sleutel bestaat om dubbel werk te voorkomen en faalt precies wanneer hij het hardst nodig is, door een afgeronde taak als serverfout te verkleden. De cache leeft in het procesgeheugen en wordt nooit naar schijf geschreven, dus een herstart wist hem; voor de semantiek zie Idempotency-Key: veilige nieuwe pogingen voor PDF-taken.
100 MiB is geen instelbare platforminstelling
De waarde kan niet worden gewijzigd. Geen enkele omgevingsvariabele verhoogt of verlaagt hem, in geen van beide richtingen; een ander getal betekent code wijzigen en de image opnieuw bouwen. Zoekt u hem als deployment-instelling, dan zult u er geen vinden.
Hij is ook meer dan een quotum. De bytes van de body van een verzoek worden voor de verwerking volledig in het geheugen gelezen, dus elke gelijktijdige grote upload houdt er een vergelijkbare hoeveelheid naast vast. Het plafond verhogen betekent ook een hogere geheugenpiek accepteren: dat getal is ook wat voorkomt dat één verzoek het proces omlaag trekt.
Een standaard zelfgehoste installatie heeft geen reverse proxy, en het portaal controleert de grootte niet vóór het versturen, dus die weigering komt van de limiet van de server zelf. Zet u nginx ervoor, dan loopt u eerst tegen nginx aan: client_max_body_size staat standaard maar 1 MiB toe en antwoordt met 413, een vorm die dicht bij die uit de vergelijking hierboven ligt en makkelijk voor dezelfde limiet wordt aangezien.
Wat u moet doen als u ertegenaan loopt
Eerst het goedkoopste:
- Meet voordat u verstuurt. Vergelijk het aantal bytes van de body van het verzoek vóór het versturen met 104.857.600 en laat ruimte voor de grenzen. Dat is beter dan achteraf een statuscode lezen.
- Comprimeer het bestand onder de limiet. De grootte van een scan komt vooral uit de beeldlaag, en opnieuw comprimeren haalt er geregeld een zichtbaar deel af. PDF comprimeren draait in de browser, zonder script.
- Verdeel het werk over meerdere aanroepen. Als de inhoud zich laat splitsen, splits hem en verstuur hem in een paar verzoeken: minder gedoe dan het plafond verhogen, en het verhoogt het geheugen dat één verzoek vasthoudt niet.
- Verander de limiet alleen bij een harde eis om alles in één verzoek te doen. Dat betekent code wijzigen en opnieuw bouwen, en de geheugenkosten uit de vorige sectie accepteren.
Deze limiet vraagt de client vooraf te beslissen
Beide fouten wijzen naar één conclusie: de 100 MiB moet de client zelf berekenen voordat hij verstuurt. Inkomend krijgt u bad_request terug, een code die niets over grootte zegt; uitgaand 500, wat op een serverfout lijkt. De bytes tellen voordat het verzoek de deur uit gaat, is de enige beoordeling die niet afhangt van wat een foutmelding zegt.