How-to2026-08-277 λεπ. ανάγνωση

Όταν απορρίπτεται μια μεγάλη μεταφόρτωση PDF: το όριο 100 MiB του σώματος αίτησης και το σφάλμα που σε οδηγεί σε λάθος κατεύθυνση

Το σώμα μιας αίτησης μπορεί να φτάσει τα 100 MiB, δηλαδή 104,857,600 bytes, μαζί με τη δομή multipart· πάνω από το όριο τα endpoints ενεργειών απαντούν 400 με code bad_request και λεπτομέρεια για αποτυχημένο πεδίο multipart, όχι 413, ώστε ένα πρόβλημα μεγέθους να διαβάζεται σαν πρόβλημα παραμέτρων, ενώ με Idempotency-Key υπάρχει δεύτερο όριο 100 MiB στην προσωρινή αποθήκευση απόκρισης και πάνω από αυτό παίρνετε 500 χωρίς τίποτε αποθηκευμένο.

PDF123 · Updated 2026-08-27

Ένα μεγάλο PDF που δεν ανεβαίνει συνήθως δεν είναι κατεστραμμένο αρχείο. Το σώμα της αίτησης έφτασε στο όριο ανά αίτηση: 100 MiB, ή 104,857,600 bytes. Μετριέται σε ολόκληρο το σώμα, μαζί με τα διαχωριστικά του multipart και τις κεφαλίδες των πεδίων, και αυτό ακριβώς το νούμερο περνά, ενώ ένα byte παραπάνω απορρίπτεται.

Το σφάλμα που επιστρέφεται δείχνει αλλού. Τα endpoints ενεργειών απαντούν 400 με code ίσο με bad_request και μια λεπτομέρεια που λέει ότι ένα πεδίο multipart δεν διαβάστηκε, χωρίς καμία αναφορά σε μέγεθος. Ένας πελάτης που διακλαδίζεται με βάση το code το καταχωρίζει ως σφάλμα παραμέτρου και πάει να ελέγξει ονόματα πεδίων, ενώ αυτό που πρέπει να αλλάξει είναι το μέγεθος του αρχείου.

Το ίδιο 100 MiB διέπει και την αντίθετη κατεύθυνση. Μια αίτηση που φέρει Idempotency-Key διαβάζει την απόκριση στη μνήμη πριν την αποθηκεύσει προσωρινά, με το ίδιο νούμερο ως όριο, και πάνω από αυτό ο καλών λαμβάνει 500 ενώ η ενέργεια έχει ήδη ολοκληρωθεί. Ένα νούμερο, δύο αντίθετοι τρόποι αποτυχίας.

                     100 MiB = 104,857,600 bytes (αυτό το νούμερο ακριβώς περνά)
                                   |
                +------------------+------------------+
                |                                     |
   εισερχόμενο (σώμα αίτησης)              εξερχόμενο (σώμα απόκρισης)
   ολόκληρο το σώμα, μαζί με τα όρια       μόνο με Idempotency-Key,
   multipart και τις κεφαλίδες πεδίων·     αστοχία στην προσωρινή αποθήκευση
   μία ενέργεια και ένας αγωγός            και ανάντη 2xx
   πάνω από: 400 + bad_request             πάνω από: 500, τίποτε δεν αποθηκεύεται
   (λεπτομέρεια: πεδίο multipart απέτυχε)

Σημείωση σχήματος: ένα όριο, και οι δύο πλευρές του, εισερχόμενη και εξερχόμενη, επιστρέφουν διαφορετικούς κωδικούς κατάστασης με αντίθετες συνέπειες.

Το όριο μετρά ολόκληρο το σώμα της αίτησης, μαζί με τα διαχωριστικά

Το όριο των 100 MiB αφορά το σώμα μιας μεμονωμένης αίτησης, όχι το μέγεθος ενός αρχείου ούτε το μέγεθος μετά την αποσυμπίεση.

  • Μία μεμονωμένη ενέργεια και ένας αγωγός πολλών βημάτων μοιράζονται το ίδιο νούμερο. Ο διαχωρισμός της δουλειάς σε 10 βήματα μέσα σε μία κλήση /api/v1/pipeline δεν μετατρέπει το ταβάνι σε 1 GB· ο αριθμός των βημάτων επηρεάζει μόνο τον χρόνο εκτέλεσης.
  • Η ίδια η οριακή τιμή περνά: ένα σώμα αίτησης 104,857,600 bytes περνά, 104,857,601 bytes δεν περνά.
  • Το σώμα φέρει και τις κεφαλίδες κάθε πεδίου και τους διαχωριστές ορίων μαζί με τα bytes του αρχείου, οπότε το περιθώριο για ένα μόνο αρχείο είναι αυστηρά κάτω από 100 MiB. Ένα αρχείο ακριβώς 104,857,600 bytes απορρίπτεται.

Το τελευταίο σημείο είναι εκεί που η πράξη ξεφεύγει πιο εύκολα: το curl -F προσθέτει τα διαχωριστικά για εσάς, οπότε η σύγκριση του μεγέθους ενός αρχείου με αυτή τη γραμμή δεν μπορεί ποτέ να βγει σωστή.

Πάνω από το όριο παίρνετε 400 και bad_request

Μια απόκριση υπέρβασης δεν χρησιμοποιεί 413 και δεν περιέχει κανένα κείμενο για μέγεθος. Αναπαράγετέ το με σώμα ένα 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" }

Τρία πράγματα να διαβάσετε μαζί:

  • Η κατάσταση είναι 400. Μια αποτυχημένη ανάγνωση σώματος χαρακτηρίζεται εδώ ως bad_request και περνά κανονικά από problem+json, οπότε ένας πελάτης γραμμένος για «πάνω από το όριο σημαίνει 413» παίρνει λάθος διακλάδωση.
  • Το code είναι bad_request, πραγματική εγγραφή στον πίνακα κωδικών σφάλματος. Δεν πέφτει σε κάποια γενική περίπτωση· μοιράζεται έναν κωδικό με ένα λανθασμένο όνομα πεδίου ή μια χαλασμένη κωδικοποίηση multipart, και τίποτε σε εκείνον τον πίνακα δεν αφορά το μέγεθος.
  • Το hint σας λέει να ανεβάσετε ένα έγκυρο PDF. Το αρχείο που ανεβάσατε είναι πολύ πιθανόν ένα έγκυρο PDF που τυχαίνει να είναι μερικές εκατοντάδες bytes μεγαλύτερο.

Το σημάδι, λοιπόν, δεν είναι ο κωδικός κατάστασης αλλά το πλήθος bytes του σώματος: σε ένα 400 του οποίου το detail περιέχει failed to read multipart field, μετρήστε το μέγεθος στη συνέχεια, αντί να ξαναπεράσετε τη φόρμα.

Το 413 εμφανίζεται σε αυτόν τον ιστότοπο, απλώς όχι στα endpoints ενεργειών. Κάθε σημείο εισόδου που δέχεται μεταφόρτωση αρχείου έχει ανέβει στα 100 MiB, ενώ τα endpoints που δεν δέχονται μεταφόρτωση τρέχουν ακόμη στο προεπιλεγμένο όριο του πλαισίου HTTP (του axum της Rust), 2 MiB, όπου ένα σώμα υπέρβασης παίρνει 413 και μία γραμμή απλού κειμένου:

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

Ένα γεγονός, δύο κωδικοί κατάστασης και δύο σώματα απόκρισης ανάλογα με το endpoint. Η μεταφορά οποιασδήποτε από τις δύο εμπειρίες στην άλλη θα σας παραπλανήσει.

Η άλλη αποτυχία με το ίδιο νούμερο, στην επιστροφή

Μια αίτηση με Idempotency-Key αποθηκεύει προσωρινά την απόκρισή της, ώστε μια επανάληψη να μπορεί να την ξαναπαίξει. Η προσωρινή αποθήκευση σημαίνει πρώτα ανάγνωση του σώματος της απόκρισης στη μνήμη, και αυτή η ανάγνωση έχει το ίδιο όριο των 100 MiB, αν και απαιτεί ένα πολύ στενότερο σύνολο συνθηκών:

  1. Η αίτηση έφερε Idempotency-Key
  2. Δεν βρέθηκε στην προσωρινή αποθήκευση, άρα αυτό το κλειδί είναι καινούριο
  3. Η ανάντη ενέργεια επέστρεψε 2xx

Οι αποτυχίες δεν αποθηκεύονται προσωρινά και περνούν κατευθείαν, οπότε μόνο μια επιτυχημένη μεγάλη έξοδος φτάνει σε αυτό το όριο.

Αυτό που συμβαίνει εκεί είναι το σημείο που αξίζει να θυμάστε: παίρνετε 500 και τίποτε δεν γράφεται στην προσωρινή αποθήκευση. Η ενέργεια έχει ήδη εκτελεστεί, αλλά ο καλών βλέπει αποτυχία· επειδή δεν αποθηκεύτηκε τίποτε, η επανάληψη τρέχει ξανά ολόκληρη τη διαδικασία. Το κλειδί ιδιομορφίας υπάρχει για να εξαλείφει διπλή δουλειά και αποτυγχάνει ακριβώς όταν το χρειάζεστε περισσότερο, μεταμφιέζοντας μια ολοκληρωμένη εργασία σε σφάλμα διακομιστή. Η προσωρινή αποθήκευση ζει στη μνήμη της διεργασίας και δεν γράφεται ποτέ στον δίσκο, οπότε μια επανεκκίνηση την καθαρίζει· για τη σημασιολογία δείτε Idempotency-Key: ασφαλείς επαναλήψεις για εργασίες PDF.

Τα 100 MiB δεν είναι ρυθμιζόμενη παράμετρος της πλατφόρμας

Ο αριθμός δεν μπορεί να αλλάξει. Καμία μεταβλητή περιβάλλοντος δεν τον ανεβάζει ούτε τον κατεβάζει, προς καμία κατεύθυνση· ένα διαφορετικό νούμερο σημαίνει αλλαγή κώδικα και επαναδόμηση της εικόνας. Ψάξτε το ως ρύθμιση ανάπτυξης και δεν θα βρείτε καμία.

Είναι επίσης κάτι παραπάνω από ποσόστωση. Τα bytes ενός σώματος αίτησης διαβάζονται ολόκληρα στη μνήμη πριν από την επεξεργασία, οπότε κάθε ταυτόχρονη μεγάλη μεταφόρτωση κρατά δίπλα της ένα συγκρίσιμο ποσό. Το ανέβασμα του ταβανιού σημαίνει αποδοχή υψηλότερης κορύφωσης μνήμης· το νούμερο είναι και αυτό που εμποδίζει μία μόνο αίτηση να σύρει τη διεργασία προς τα κάτω.

Μια προεπιλεγμένη αυτοφιλοξενούμενη ανάπτυξη δεν έχει αντίστροφο διακομιστή μεσολάβησης και η πύλη δεν ελέγχει το μέγεθος πριν από την υποβολή, οπότε αυτή η απόρριψη προέρχεται από το όριο του ίδιου του διακομιστή. Βάλτε nginx μπροστά και θα χτυπήσετε πρώτα το nginx: το client_max_body_size επιτρέπει μόνο 1 MiB από προεπιλογή και απαντά 413, ένα σχήμα κοντά σε αυτό της παραπάνω σύγκρισης και εύκολο να το μπερδέψετε με το ίδιο όριο.

Τι να κάνετε όταν το χτυπήσετε

Από το φθηνότερο προς το ακριβότερο:

  1. Μετρήστε πριν στείλετε. Συγκρίνετε το πλήθος bytes του σώματος της αίτησης με το 104,857,600 πριν φύγει η αίτηση και αφήστε περιθώριο για τα διαχωριστικά. Αυτό είναι καλύτερο από το να διαβάσετε έναν κωδικό κατάστασης εκ των υστέρων.
  2. Συμπιέστε το αρχείο κάτω από το όριο. Το μέγεθος μιας σάρωσης προέρχεται κυρίως από το στρώμα εικόνας της, και η επανασυμπίεση αφαιρεί συνήθως ένα ορατό μερίδιό του. Η Συμπίεση PDF τρέχει στον φυλλομετρητή, χωρίς ανάγκη για σενάριο.
  3. Χωρίστε τη δουλειά σε πολλές κλήσεις. Όταν το περιεχόμενο διαιρείται, χωρίστε το και στείλτε το σε λίγες αιτήσεις: λιγότερος μπελάς από το ανέβασμα του ορίου και δεν αυξάνει τη μνήμη που κρατά μία αίτηση.
  4. Αλλάξτε το όριο μόνο για σκληρή απαίτηση μίας αίτησης. Αυτό σημαίνει αλλαγή κώδικα και επαναδόμηση, και αποδοχή του κόστους μνήμης της προηγούμενης ενότητας.

Αυτό το όριο ζητά από τον πελάτη να αποφασίσει εκ των προτέρων

Και οι δύο αποτυχίες οδηγούν σε ένα συμπέρασμα: το νούμερο των 100 MiB πρέπει να το έχει υπολογίσει ο πελάτης πριν στείλει. Στην εισερχόμενη κατεύθυνση σας απαντούν με bad_request, έναν κωδικό που δεν λέει τίποτε για μέγεθος· στην εξερχόμενη, με 500, που μοιάζει με σφάλμα διακομιστή. Η μέτρηση των bytes πριν φύγει η αίτηση είναι ο μόνος τρόπος κρίσης που δεν εξαρτάται από το τι λέει ένα μήνυμα σφάλματος.

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