När en stor PDF-uppladdning avvisas: gränsen på 100 MiB för begärandekroppen och felet som pekar i fel riktning
En begärandekropp får vara 100 MiB, alltså 104 857 600 byte inklusive multipart-ramverket; över gränsen svarar slutpunkter för operationer med 400 och code bad_request samt en detalj om ett misslyckat multipart-fält, inte 413, och med Idempotency-Key finns en andra gräns på 100 MiB för svarsbufferten, där du får 500 och inget cachas.

En stor PDF som inte går att ladda upp är oftast inte en trasig fil. Begärandekroppen nådde gränsen per förfrågan: 100 MiB, eller 104 857 600 byte. Den mäts över hela kroppen, multipart-gränser och fälthuvuden inräknade, och exakt den siffran passerar medan en enda byte mer avvisas.
Felet som kommer tillbaka pekar någon annanstans. Slutpunkter för operationer svarar 400 med code satt till bad_request och en detalj som säger att ett multipart-fält inte kunde läsas, utan att nämna storleken någonstans. En klient som förgrenar sig på code arkiverar det som ett parameterfel och går iväg för att kontrollera fältnamn, när det som ska ändras är filstorleken.
Samma 100 MiB styr också den motsatta riktningen. En förfrågan med Idempotency-Key läser in svaret i minnet innan det cachas, mot samma siffra, och över den får anroparen 500 medan operationen redan har körts klart. En siffra, två motsatta felmoder.
100 MiB = 104 857 600 byte (exakt denna siffra passerar)
|
+------------------+------------------+
| |
inkommande (begärandekropp) utgående (svarskropp)
hela kroppen, multipart-gränser bara med Idempotency-Key,
och fälthuvuden inräknade; en en cachemiss och ett
operation och en pipeline delar den upstream-2xx
över: 400 + bad_request över: 500, inget cachat
(detalj: ett multipart-fält misslyckades)
Figurnot: en gräns, och den inkommande och utgående sidan av den ger olika statuskoder med motsatta följder.
Gränsen räknar hela begärandekroppen, gränser inräknade
100 MiB begränsar kroppen för en enskild förfrågan, inte storleken på en enskild fil och inte storleken efter dekomprimering.
- En enskild operation och en pipeline i flera steg delar siffran. Att dela upp arbetet i 10 steg inom ett enda anrop till
/api/v1/pipelinegör inte taket till 1 GB; antalet steg påverkar bara körtiden. - Gränsvärdet självt passerar: en begärandekropp på 104 857 600 byte går igenom, 104 857 601 byte gör det inte.
- Kroppen bär varje fälts huvuden och gränsmarkörerna utöver filbyten, så utrymmet som återstår för en enskild fil är strikt under 100 MiB. En fil på exakt 104 857 600 byte avvisas.
Den sista punkten är där praktiken lättast slinter: curl -F lägger till gränserna åt dig, så att jämföra en filstorlek mot den linjen kan aldrig stämma.
Över gränsen får du 400 och bad_request
Ett svar över gränsen använder inte 413 och innehåller ingen formulering om storlek alls. Reproducera det med en kropp en byte över:
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" }
Tre saker att läsa tillsammans:
- Statusen är 400. En misslyckad läsning av kroppen klassas här som
bad_requestoch går fortfarande genom problem+json, så en klient skriven för ”över gränsen betyder 413” tar fel gren. codeärbad_request, en riktig post i felkodstabellen. Den faller inte igenom till en generisk gren; den delar en kod med ett felstavat fältnamn eller en trasig multipart-kodning, och inget i den tabellen handlar om storlek.hintsäger åt dig att ladda upp en giltig PDF. Filen du laddade upp är med stor sannolikhet en giltig PDF som råkar vara några hundra byte för stor.
Ledtråden är alltså inte statuskoden utan antalet byte i kroppen: vid en 400 vars detail innehåller failed to read multipart field, mät storleken i stället för att gå tillbaka genom formuläret.
413 förekommer faktiskt hos oss, bara inte på slutpunkter för operationer. Varje ingång som tar emot en filuppladdning har höjts till 100 MiB, medan slutpunkter utan uppladdning fortfarande körs på HTTP-ramverkets (Rusts axum) standard på 2 MiB, där en kropp över gränsen får 413 och en rad klartext:
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
Ett faktum, två statuskoder och två svarskroppar beroende på slutpunkten. Att bära med sig endera erfarenheten till den andra leder dig fel.
Det andra felet med samma siffra, på vägen tillbaka
En förfrågan med Idempotency-Key cachar sitt svar så att ett återförsök kan spela upp det. Att cacha innebär att först läsa in svarskroppen i minnet, och den läsningen begränsas av samma 100 MiB, även om den kräver en mycket snävare uppsättning villkor:
- Förfrågan bar
Idempotency-Key. - Cachen missade, så denna nyckel är ny.
- Den uppströms operationen returnerade 2xx.
Misslyckanden cachas inte och passerar rakt igenom, så bara en lyckad stor utdata når denna gräns.
Det som händer där är poängen värd att komma ihåg: du får 500, och inget skrivs till cachen. Operationen har redan körts, ändå ser anroparen ett misslyckande; eftersom inget cachades kör återförsöket hela saken igen. Idempotensnyckeln finns för att eliminera dubbelarbete och misslyckas precis när den behövs som mest, genom att klä en färdig körning som ett serverfel. Cachen lever i processminnet och skrivs aldrig till disk, så en omstart rensar den; för semantiken se Idempotency-Key: säkra återförsök för PDF-jobb.
100 MiB är ingen konfigurerbar plattformsinställning
Siffran går inte att ändra. Ingen miljövariabel höjer eller sänker den, åt något håll; en annan siffra betyder att koden ändras och avbildningen byggs om. Söker du den som en driftsättningsinställning hittar du ingen.
Den är också mer än en kvot. Byten i en begärandekropp läses in i minnet i sin helhet före bearbetningen, så varje samtidig stor uppladdning håller en jämförbar mängd vid sidan av. Att höja taket innebär att acceptera en högre minnestopp tillsammans med det: siffran är också det som hindrar en enskild förfrågan från att dra ner processen.
En egen installation med standardinställningar har ingen omvänd proxy, och portalen kontrollerar inte storleken före inskickning, så avvisandet kommer från serverns egen gräns. Sätter du nginx framför träffar du nginx först: client_max_body_size tillåter bara 1 MiB som standard och svarar 413, en form nära den i jämförelsen ovan och lätt att missta för samma gräns.
Vad du gör när du träffar den
Billigast först:
- Mät innan du skickar. Jämför begärandekroppens byteantal med 104 857 600 innan förfrågan går ut, och lämna utrymme för gränserna. Det slår att läsa en statuskod i efterhand.
- Komprimera filen under gränsen. En skannings storlek kommer mest från bildlagret, och omkomprimering tar rutinmässigt bort en synlig del av den. Komprimera en PDF körs i webbläsaren, utan skript.
- Dela upp arbetet över flera anrop. När innehållet går att dela, dela det och skicka det i några förfrågningar: mindre besvär än att höja taket, och det höjer inte minnesmängden som en förfrågan håller.
- Ändra bara gränsen vid ett hårt krav på en enda förfrågan. Det betyder att ändra kod och bygga om, och att acceptera minneskostnaden från föregående avsnitt.
Den här gränsen kräver att klienten bestämmer i förväg
Båda felen pekar mot en slutsats: 100 MiB måste räknas ut av klienten innan den skickar. Inkommande svaras du med bad_request, en kod som inte säger något om storlek; utgående med 500, vilket ser ut som ett serverfel. Att räkna byten innan förfrågan lämnar är det enda sättet att bedöma som inte beror på vad ett felmeddelande säger.