skip to content

Which limits should a server enforce while parsing a multipart upload, and at what moment must it answer 413?

level: seniorimportance: must knowfreq 58%

answer

  1. each limit guards a different resource
  2. many small parts cost work, not bytes
  3. count while streaming, not after
  4. 413 fires mid-body
  5. the smallest limit in the chain wins

basics

~10 s

At least four: a per-part cap, a whole-request cap, a maximum part count, and caps on header blocks and in-memory field values. Counters run as bytes arrive, so 413 goes out mid-body, not after.

solid answer

~50 s

One limit is not enough, because each guards a different resource. A per-part maximum bounds a single file; a total-request maximum stops many acceptable parts adding up to an unacceptable payload; a part-count cap stops a body made of thousands of tiny parts, which costs parser work and handle allocations rather than bytes; and caps on header-block size and on individual field values stop the cheap variants of the same attack. The enforcement rule is the part people get wrong: counters are checked as bytes stream in, so a 413 goes out mid-body, before the payload has been fully received. If the request declares a length that already exceeds a limit, reject before reading any body at all. After rejecting mid-stream the server must decide whether to drain the rest or close the connection, because a client still sending may never see the response.

go deeper

for a junior

Know that an upload that exceeds the server's limit is answered with 413, and that the limit is configured on the server rather than agreed with the client.

for a middle

Name the distinct limits — per part, per request, part count, header block, field value — and explain that counters are checked as bytes stream in.

for a senior

Cover the operational edges: early rejection from a declared length, drain-versus-close after a mid-body 413, a proxy cap that pre-empts yours, and a timeout for slow senders.

for a principal

Treat the numbers as contract and capacity policy: per-endpoint rather than global, documented for clients, consistent with the proxy tier, and derived from what the service can actually absorb concurrently.

## Why one limit is not enough Every multipart limit protects a different resource, and an attacker picks whichever one you left open. A single per-file cap of, say, a few hundred megabytes looks protective and permits a request of fifty such parts. A generous total cap looks protective and permits a body of a hundred thousand empty parts, which costs almost no bytes but a great deal of parser work and a handle per part. | Limit | What it bounds | What it protects | Typical response when exceeded | |---|---|---|---| | Per-part maximum bytes | one file part | memory or disk for a single upload | 413 | | Total request maximum bytes | the whole body | aggregate memory, disk and bandwidth | 413 | | Maximum number of parts | part count | parser work, handles, temporary files | 413 | | Per-part header block size | the small header block of each part | parser buffers before any content arrives | 413, sometimes 400 | | Maximum in-memory field value | a non-file part kept in memory | heap consumed by ordinary form values | 413 | | Read timeout and minimum rate | time, not bytes | connections and threads held by slow uploads | a timeout status, not 413 | The last row is the one most often missing. A body that stays under the cap while trickling in never trips a size limit, yet occupies the connection and its buffers for as long as the client likes; only a time or rate control ends it. ## When the counter is checked The mechanics are simple and the timing is what interviews probe: 1. **Before the body is read.** If the request declares a total length and that declared length already exceeds the total maximum, the server can answer immediately, having read nothing but headers. This is the cheapest rejection available and it is worth taking. 2. **As bytes arrive.** The parser increments a per-part counter and a per-request counter for every byte it consumes. The instant a counter crosses its limit, parsing stops. It does not wait for the closing boundary, and it does not finish writing the part. 3. **On part boundaries.** The part counter increments as each new part's header block is seen, so a body with too many parts is stopped at the offending part rather than at the end. A declared length is not always available, and it is never authoritative: what actually arrived may differ from what was announced. The counters are the control; the declared length is only an early-exit optimisation. ## Which status code, and why 413 specifically - **413** — the payload exceeded a limit the server imposes. This is the answer for every size and count limit above. - **400** — the body is not well-formed multipart: a boundary that never terminates, a part header block that cannot be parsed, a truncated body. - **415** — the request's media type is not one this endpoint accepts at all, which is a decision made from the request's own content type before multipart parsing begins. Where a limit is *policy* rather than a hard capacity bound, state it in the response body so a legitimate caller can adapt rather than retry blindly. ## The awkward part: rejecting mid-body A server that stops reading at byte 200 of a 900 MB upload has a problem that has nothing to do with parsing. The client is still sending. Two options exist and both have costs: - **Drain the remainder and then respond.** The client reliably receives the 413, and the server spends the bandwidth and the connection time it was trying to avoid. Draining must itself be bounded, or it reintroduces the exhaustion. - **Respond and close the connection.** No bandwidth is wasted, but a client that is mid-send may observe a connection reset before it reads the response, and will report a network error rather than the status you sent. Neither is wrong. The practical compromise is to respond, drain a bounded number of additional bytes to give the client a chance to see it, then close. ## Where the limits live Limits usually exist in more than one place, and they interact: - A reverse proxy or gateway in front of the service often has its own body-size cap, and the smaller of the two wins. If the proxy's cap is lower, the application's limit never fires and its error body never reaches the client — a frequent cause of an unexplained error page instead of the tidy response the service takes care to produce. - The parser's limits apply to what it parses, so a handler that copies a streamed part onward is bounded by the parser only if it respects the same counters. - Limits are part of the endpoint's contract. Two endpoints in one service can legitimately differ, and a single global value is usually either too small for the upload endpoint or too large for everything else. ## The answer in short Enforce per-part, total, part-count, header-block and field-value limits plus a time control; count as bytes arrive; send 413 the moment a counter trips; use the declared length only as an early exit; and decide deliberately what happens to the rest of the body once you have stopped reading it.

  • Why is a part-count limit needed when a total size limit already exists?
    Because the two cost different resources. A hundred thousand nearly empty parts sit far inside any byte cap while costing a header parse, a counter and often a handle or temporary file each. The bytes are trivial; the per-part work is the attack.
  • If the request declares a total length, why still count bytes as they arrive?
    Because the declared length is an announcement, not a guarantee — the body may be longer, shorter, or arrive without a declared length at all. Use it as a free early rejection when it already exceeds the limit, and treat the running counters as the actual control.
  • The service returns 413 correctly, yet clients report a network error instead. What is happening?
    The server stopped reading and closed while the client was still sending, so the client saw the connection fail before it read the response. Draining a bounded number of further bytes before closing usually lets the status through. A proxy with a smaller cap in front produces the same symptom.
  • Why do size limits alone not stop a slow upload?
    Because a body that stays under the cap never crosses a byte threshold, however long it takes to arrive. It occupies a connection, any buffers already allocated, and under a thread-per-request model a thread, for as long as the client chooses. Only a read timeout or a minimum-throughput rule ends it.

saying these in an interview costs you the question

  • Sets one total size limit and calls the endpoint protected
  • Validates the size only after the whole body has been buffered
  • Trusts the declared content length as the actual body size
  • Ignores part count because tiny parts use few bytes
  • Forgets that a proxy's smaller body cap fires first