How-to2026-08-277 min di lettura

Quando un PDF grande non si carica: il limite di 100 MiB per richiesta e l'errore che indica la direzione sbagliata

Il corpo di una richiesta può arrivare a 100 MiB, cioè 104,857,600 byte, con l'inquadratura multipart inclusa. Oltre il limite, gli endpoint di operazione rispondono 400 con il codice bad_request e un dettaglio su un campo multipart non letto, non 413, quindi un problema di dimensione viene letto come un problema di parametri. Con Idempotency-Key c'è un secondo limite di 100 MiB sul buffer di risposta; oltre quello ricevi 500 e nulla viene messo in cache.

PDF123 · Updated 2026-08-27

Un PDF grande che non si carica di solito non è un file corrotto. Il corpo della richiesta ha toccato il limite per richiesta: 100 MiB, ossia 104,857,600 byte. Si misura sull'intero corpo, delimitatori multipart e intestazioni di ogni campo inclusi, e quella cifra esatta passa mentre un byte in più viene rifiutato.

L'errore che torna indietro punta altrove. Gli endpoint di operazione rispondono 400 con code uguale a bad_request e un dettaglio che dice che un campo multipart non è stato letto, senza alcun accenno alla dimensione. Un client che ramifica su code archivia il caso come errore di parametri e va a controllare i nomi dei campi, quando la cosa da cambiare è la dimensione del file.

Gli stessi 100 MiB governano anche la direzione opposta. Una richiesta con Idempotency-Key legge la risposta in memoria prima di metterla in cache, contro la stessa cifra, e oltre quella il chiamante riceve 500 mentre l'operazione è già conclusa. Un solo numero, due modi di fallire opposti.

                     100 MiB = 104,857,600 byte (questa cifra esatta passa)
                                   |
                +------------------+------------------+
                |                                     |
   in entrata (corpo della richiesta)           in uscita (corpo della risposta)
   corpo intero, delimitatori multipart e       solo con Idempotency-Key,
   intestazioni di campo inclusi; un'operazione   un errore di cache e un
   e una pipeline lo condividono                2xx a monte
   oltre: 400 + bad_request                     oltre: 500, niente in cache
   (dettaglio: un campo multipart non letto)

Nota alla figura: un unico limite, e i suoi lati in entrata e in uscita restituiscono codici di stato diversi con conseguenze opposte.

Il limite conta l'intero corpo della richiesta, delimitatori inclusi

I 100 MiB limitano il corpo di una singola richiesta, non la dimensione di un file né la dimensione dopo la decompressione.

  • Un'operazione e una pipeline a più passaggi condividono la cifra. Suddividere il lavoro in 10 passaggi dentro una chiamata a /api/v1/pipeline non trasforma il tetto in 1 GB; il numero di passaggi incide solo sul tempo di esecuzione.
  • Il valore limite esatto passa: un corpo di 104,857,600 byte viene accettato, 104,857,601 byte no.
  • Il corpo porta con sé le intestazioni di ogni campo e i delimitatori oltre ai byte del file, quindi il margine che resta a un singolo file è strettamente inferiore a 100 MiB. Un file di esattamente 104,857,600 byte viene rifiutato.

È su quest'ultimo punto che la pratica scivola più facilmente: curl -F aggiunge i delimitatori al posto tuo, quindi confrontare la dimensione di un file con quella soglia non può mai tornare.

Oltre il limite ricevi 400 e bad_request

Una risposta oltre il limite non usa 413 e non contiene alcun riferimento alla dimensione. Riproducila con un corpo più grande di un byte:

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 cose da leggere insieme:

  • Lo stato è 400. Un corpo non letto viene classificato qui come bad_request e passa comunque attraverso problem+json, quindi un client scritto per "oltre il limite significa 413" prende il ramo sbagliato.
  • code è bad_request, una voce reale nella tabella dei codici di errore. Non ricade in un caso generico; condivide lo stesso codice con un nome di campo scritto male o una codifica multipart rotta, e nulla in quella tabella riguarda la dimensione.
  • Il hint ti dice di caricare un PDF valido. Il file che hai caricato è molto probabilmente un PDF valido, solo più grande di qualche centinaio di byte.

L'indizio, quindi, non è il codice di stato ma il conteggio dei byte del corpo: davanti a un 400 il cui detail contiene failed to read multipart field, misura la dimensione invece di ripercorrere il modulo.

413 su questo sito esiste, solo che non compare sugli endpoint di operazione. Ogni punto di ingresso che accetta un caricamento di file è stato portato a 100 MiB, mentre gli endpoint che non ricevono upload restano al valore predefinito del framework HTTP (axum, di Rust), 2 MiB, dove un corpo oltre il limite riceve 413 e una riga di testo semplice:

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

Uno stesso fatto, due codici di stato e due corpi di risposta a seconda dell'endpoint. Trasferire l'una o l'altra esperienza dove non vale ti porterà fuori strada.

L'altro guasto con lo stesso numero, al ritorno

Una richiesta con Idempotency-Key mette in cache la risposta perché un nuovo tentativo possa riprodurla. Mettere in cache significa leggere prima il corpo della risposta in memoria, e quella lettura è limitata dagli stessi 100 MiB, anche se servono condizioni molto più strette:

  1. La richiesta portava Idempotency-Key
  2. La cache ha mancato, quindi la chiave era nuova
  3. L'operazione a monte ha restituito 2xx

I fallimenti non finiscono in cache e passano diretti, quindi solo un output grande e riuscito raggiunge questo limite.

Quello che succede lì è il punto da ricordare: ricevi 500 e non viene scritto nulla nella cache. L'operazione è già stata eseguita, ma il chiamante vede un fallimento; poiché nulla è finito in cache, il nuovo tentativo rifà tutto il lavoro. La chiave di idempotenza esiste per eliminare lavoro duplicato e fallisce proprio quando serve di più, travestendo un lavoro concluso da guasto del server. La cache vive nella memoria del processo e non viene mai scritta su disco, quindi un riavvio la azzera; per la semantica vedi Idempotency-Key: nuovi tentativi sicuri per le attività PDF.

100 MiB non è un'impostazione configurabile della piattaforma

La cifra non si può cambiare. Nessuna variabile d'ambiente la alza o la abbassa, in nessuno dei due sensi; un numero diverso significa modificare il codice e ricostruire l'immagine. Cercala come impostazione di deployment e non la troverai.

È anche più di una quota. I byte di un corpo di richiesta vengono letti per intero in memoria prima dell'elaborazione, quindi ogni caricamento grande simultaneo ne occupa accanto una quantità comparabile. Alzare il tetto significa accettare insieme un picco di memoria più alto: il numero è anche ciò che impedisce a una singola richiesta di trascinare giù il processo.

Un deployment self-hosted predefinito non ha un proxy inverso, e il portale non controlla la dimensione prima dell'invio, quindi quel rifiuto arriva dal limite del server stesso. Metti nginx davanti e colpirai prima nginx: client_max_body_size consente solo 1 MiB per impostazione predefinita e risponde 413, una forma vicina a quella del confronto qui sopra e facile da scambiare per lo stesso limite.

Cosa fare quando lo incontri

Dal più economico al più costoso:

  1. Misura prima di inviare. Confronta il conteggio dei byte del corpo con 104,857,600 prima che la richiesta parta e lascia margine per i delimitatori. È meglio che leggere un codice di stato dopo.
  2. Comprimi il file sotto il limite. La dimensione di una scansione deriva soprattutto dal suo livello immagine, e ricomprimerla di solito ne toglie una parte visibile. Comprimere un PDF gira nel browser, senza script.
  3. Dividi il lavoro su più chiamate. Quando il contenuto è divisibile, suddividilo e invialo in poche richieste: meno complicazioni che alzare il tetto, e non aumenta la memoria che una singola richiesta trattiene.
  4. Cambia il limite solo per un requisito rigido di richiesta singola. Significa modificare il codice, ricostruire e accettare il costo in memoria della sezione precedente.

Questo limite obbliga il client a decidere in anticipo

I due fallimenti portano a un'unica conclusione: il client deve calcolare la cifra di 100 MiB prima di inviare. In entrata, la risposta è bad_request, un codice che non dice nulla sulla dimensione; in uscita, 500, che sembra un guasto del server. Contare i byte prima che la richiesta parta è l'unico giudizio che non dipende da ciò che dice un messaggio di errore.

Open tool
Process in the browser — no watermark, files removed after the job.
Open tool