Limits and Timeouts
The Post Office Rules
A post office has rules on the wall: parcels no heavier than 20 kg, labels must fit on the box, and if you get to the front of the queue and just stand there, the clerk calls the next person. Without rules, one awkward customer could jam up the whole post office.
A web server is open to anyone on the network, including people who
are careless or naughty. So PyWeb has the same kind of rules, kept in a
Limits object:
| Rule | Default | What happens if you break it |
|---|---|---|
max_head_bytes |
16 KiB | 431 -- your label is too long |
max_headers |
100 | 431 -- too many stickers |
max_body_bytes |
1 MiB | 413 -- parcel too heavy (refused before reading it) |
header_timeout |
10 s | 408 if you start a request and stall |
body_timeout |
30 s | 408 if the body stops arriving |
keepalive_timeout |
5 s | an idle connection is closed quietly |
write_timeout |
30 s | a client that stops reading is cut off |
max_connections |
1 000 | 503 -- "we're full, come back later" |
After any of these the server closes the connection.
Try It
Here the rules are made tiny so we can break them on purpose:
from pynet.loop import run
from pynet.streams import open_connection
from pyweb.aserver import AsyncServer
from pyweb.app import App
from pyweb.http1 import Limits
app = App()
@app.post("/letters")
def post_letter(request):
return {"got": len(request.body)}
async def send(server, raw):
reader, writer = await open_connection("127.0.0.1", server.port)
writer.write(raw)
await writer.drain()
reply = await reader.read() # every one of these closes afterwards
writer.close()
head, _, body = reply.partition(b"\r\n\r\n")
print(head.split(b"\r\n")[0].decode(), body.decode())
async def main():
limits = Limits(max_body_bytes=10, max_head_bytes=200, header_timeout=0.2)
async with AsyncServer(app, limits=limits, access_log=False) as server:
print("1. a small letter")
await send(server, b"POST /letters HTTP/1.1\r\nContent-Length: 5\r\nConnection: close\r\n\r\nhello")
print("2. a parcel that is too big (we never even send it)")
await send(server, b"POST /letters HTTP/1.1\r\nContent-Length: 5000\r\n\r\n")
print("3. a label that is far too long")
await send(server, b"POST /letters HTTP/1.1\r\nX-Note: " + b"blah" * 100 + b"\r\n\r\n")
print("4. someone who starts talking, then stops")
await send(server, b"POST /letters HTTP/1.1\r\n")
run(main())
Output:
1. a small letter
HTTP/1.1 200 OK {"got": 5}
2. a parcel that is too big (we never even send it)
HTTP/1.1 413 Content Too Large {"error": "Content Too Large", "message": "the request body is too large"}
3. a label that is far too long
HTTP/1.1 431 Request Header Fields Too Large {"error": "Request Header Fields Too Large", "message": "the request head is too large"}
4. someone who starts talking, then stops
HTTP/1.1 408 Request Timeout {"error": "Request Timeout", "message": "the request head took too long"}
Error replies are JSON, or a small HTML page if the request said
Accept: text/html -- just like HTTPError from a handler.
Quiet or 408?
There are two kinds of silence:
- A connection that has sent nothing at all (a brand-new one, or a kept-alive one between requests) is closed quietly. Browsers often open a spare connection "just in case", and shouldn't get a telling-off.
- A connection that started a request and then stalled gets
408 Request Timeout, then is closed.
Tricky Requests
Some requests are refused because two programs might read them differently -- the trick behind "request smuggling":
- Two different
Content-Lengthvalues, orContent-Lengthtogether withTransfer-Encoding->400. Transfer-Encodingon its own ->411(PyWeb only accepts bodies with aContent-Length).- A lone carriage return, line feed or NUL byte inside a line ->
400. - An unknown method such as
BREW->501;HTTP/2.0->505.
What We Test
413for a too-bigContent-Length, sent without the body.431for a head longer thanmax_head_bytes, while a head of exactlymax_head_bytesis fine.400for conflicting framing and bare CR/LF/NUL,411forTransfer-Encoding,501for unknown methods,505for unknown versions -- and the connection closes after each.- A stalled head gets
408within half a second whenheader_timeout=0.2; a stalled body gets408too. - A silent new connection and an idle keep-alive connection close without any reply.
- A client that never reads a huge reply is cut off by
write_timeout. - With
max_connections=2, a third connection gets503. - A client hanging up mid-request is forgotten quietly.