Which request-size limits does an HTTP server enforce before handing a request to the framework, and what does the client see when one trips?
answer
- parsing needs buffers, buffers need caps
- 414 line, 431 headers, 413 body
- refused below the seam, so no handler runs
- declared length refuses early, streamed refuses late
- lowest cap across the layers wins
basics
~20 sServers cap the request line, the header block and the body before hand-off. An over-long target usually returns 414, an over-large header block 431, an over-large body 413 - produced below the seam, so the application never sees the request.
solid answer
~50 sA server has to allocate buffers before it understands a request, so it applies caps while parsing, and refuses on its own authority. Typical caps: the **request line / target length**, answered with `414 URI Too Long`; the **header block** - total size and often per-field size and field count - answered with `431 Request Header Fields Too Large`, though some servers answer `400` or simply close; and the **body size**, answered with `413 Content Too Large`. Malformed framing gets `400 Bad Request`. The crucial consequence is that these responses come from below the seam: the framework's entry point is never invoked, so its error format, its logging and its error handling contribute nothing. A body cap can also trip *late*, while the body is being streamed after the handler has already started, and then the response may already be partly written.
go deeper
Learn the three caps and their codes: over-long target 414, over-large header block 431, over-large body 413. They come from the server, not from your handler.
Explain why parsing forces caps to exist and why a refusal before hand-off means no middleware, no handler and no application log entry for that request.
Diagnose from the symptom: a terse default error body, a gap in application logs, or a reset connection mid-upload each point at a specific layer and a specific cap. Know why a streamed body can only be refused late.
Own the numbers across edge, server and framework as one set. Disagreement between layers produces errors in a format nobody wrote and, for duplicated or ambiguous input, the conditions for requests being read two different ways.
## Why the server, not the application, enforces them Parsing is allocation. To read a request line the server must buffer it; to read headers it must hold them; to accept a body it must either buffer or stream it somewhere. An unbounded parser is a denial-of-service primitive: one connection sending an endless header block can exhaust memory. So a server refuses oversized input **while parsing**, before it has anything to hand across the adapter boundary and before the application exists as far as that request is concerned. ## The caps and what the client sees | what is capped | typical response | when it is detected | |---|---|---| | request line / target length | `414 URI Too Long` | while reading the first line | | single header field, or the whole header block, or the field count | `431 Request Header Fields Too Large`; some servers answer `400` or close the connection | while reading headers | | declared body length | `413 Content Too Large` | as soon as the announced length is read | | actual streamed body length | `413`, or an abrupt close, or a stream reset | mid-body, possibly after the handler started | | malformed start line, headers or framing | `400 Bad Request` | while parsing | The status codes are the protocol's, not any product's, but the exact choice for an oversized header block genuinely varies: `431` is the purpose-built code, and plenty of servers answer `400` or close instead, on the reasoning that a client which cannot be parsed cannot be trusted to read an explanation. ## Declared length versus streamed length These are two different rejections and they behave differently. 1. **Declared length.** The request announces how long the body is. A server can compare that with its cap the moment the head is parsed and refuse immediately - cleanly, before hand-off, without reading a byte of payload. When the client asks for permission to send first, the server can refuse at that point too, so the payload never travels. 2. **Streamed length.** The body arrives without a declared length, or the declared length was a lie. The cap is then enforced as bytes accumulate - and by then the framework may already be running, may already have set a status, and may already have flushed the first bytes of a response. Once a response is committed, the status cannot be changed to `413`; the only remaining moves are truncating the response or resetting the exchange, which is why a client sometimes sees a broken connection rather than a clean error. ## Why your error page does not appear This is the question behind the question in interviews. When a request is refused before hand-off: - the framework's entry point is **not** invoked, so no middleware, no handler and no error handling runs; - the response body is whatever the server produces - typically a terse default page, not the application's error format; - the application's request log has **no entry** for the request, because nothing above the seam ever knew about it; - application metrics show a gap, while the server's own metrics or access log show the rejection. "Clients report errors we cannot find in our logs" is the classic symptom, and the fix is to look one layer down. ## Layers stacked in front A reverse proxy or gateway in front of the server enforces its own caps, and so does the framework, often with a second body limit applied above the seam for routes that accept uploads. Three layers with three independent numbers means the **lowest** number wins, and the client's error comes from whichever layer trips first - in that layer's format. When the numbers disagree in the other direction, a request that the edge allowed is rejected at the origin, which is harder to diagnose because the edge's logs show success being forwarded. ## What to check when diagnosing - Read the failing response body: a terse default page is a strong hint the rejection came from a server or proxy, not the application. - Compare the caps at every layer, including the ones defaults set for you. - Distinguish a clean status from a reset connection: a reset usually means the limit tripped after the response had already begun. - Check whether the client announces the body length or streams it, because that decides whether rejection can be clean at all. ## The takeaway Limits are part of the contract at the seam even though no application code implements them. They define a whole class of requests your application will never see, answered in a format you did not write, absent from logs you do own - and knowing that is what turns an unexplainable client report into a two-minute diagnosis.
- Why can a rejection for an oversized body arrive as a reset connection rather than 413?Because a body without a trustworthy declared length is only measured as it streams. If the cap trips after the handler has begun writing, the response is already committed and its status cannot be changed, so the server can only truncate or reset the exchange. Clients then see a broken connection rather than a clean error.
- A client reports errors that appear nowhere in the application logs. What does that suggest?That the request was refused before hand-off, by the server or by a proxy in front of it. Nothing above the seam ran, so no application log line exists. Check the server or proxy access log, compare the caps at each layer, and look at the response body - a terse default page points below the application.
- Should the framework also cap body size if the server already does?Usually yes, because the two caps serve different purposes: the server's protects the process from arbitrary traffic, while a limit above the seam can be per-route and produce the application's own error format. The rule is that the caps must be deliberately related, not accidentally different.
saying these in an interview costs you the question
- Expects the application error page for a request refused before hand-off
- Believes the framework produces 413 and 431 responses
- Thinks an oversized request always yields a clean status code
- Assumes the application log records every request the client sent
- Believes one cap at the edge makes origin caps unnecessary