Keep-Alive
"Anything Else?"
At an ice-cream van you don't walk away and join the back of the queue after every scoop. You stay at the window: "One vanilla, please... and a mint... oh, and a lemon -- that's all, thanks!" The seller keeps serving you until you say you're done, or until you stand there so long without ordering that they move on.
HTTP connections work the same way. Opening a new connection is like joining the queue -- it takes time. So an HTTP/1.1 browser keeps the connection open and sends its next request down the same one. That is keep-alive.
| At the van | In HTTP |
|---|---|
| Staying at the window | Keeping the connection open |
| "That's all, thanks!" | The header Connection: close |
| Standing there saying nothing | An idle connection |
| The seller moving on | keepalive_timeout running out |
Try It
Three scoops, one connection. The last request says Connection: close,
so the server answers and then hangs up:
from pynet.loop import run
from pynet.streams import open_connection
from pyweb.aserver import AsyncServer
from pyweb.app import App
app = App()
@app.get("/scoop/<flavour>")
def scoop(request, flavour):
return f"<p>One scoop of {flavour}</p>"
async def ask(reader, writer, request):
writer.write(request)
await writer.drain()
head = await reader.read_until(b"\r\n\r\n")
lines = head.decode().split("\r\n")
length = next(int(line.split(": ")[1]) for line in lines if line.startswith("Content-Length"))
body = await reader.read_exactly(length)
connection = [line for line in lines if line.startswith("Connection")]
print(lines[0], body.decode(), connection)
async def main():
async with AsyncServer(app, access_log=False) as server:
reader, writer = await open_connection("127.0.0.1", server.port)
await ask(reader, writer, b"GET /scoop/vanilla HTTP/1.1\r\n\r\n")
await ask(reader, writer, b"GET /scoop/mint HTTP/1.1\r\n\r\n")
print("connections open:", server.connection_count)
await ask(reader, writer, b"GET /scoop/lemon HTTP/1.1\r\nConnection: close\r\n\r\n")
print("anything more?", await reader.read())
writer.close()
run(main())
Output:
HTTP/1.1 200 OK <p>One scoop of vanilla</p> []
HTTP/1.1 200 OK <p>One scoop of mint</p> []
connections open: 1
HTTP/1.1 200 OK <p>One scoop of lemon</p> ['Connection: close']
anything more? b''
Notice how Content-Length matters here: on a kept-alive connection it
is the only way the client knows where one reply ends and the next
begins.
The Rules
- HTTP/1.1 keeps the connection open unless the request says
Connection: close(or the handler setsConnection: closeon its response). - HTTP/1.0 closes after one reply unless the request says
Connection: keep-alive(and then the reply says it too). - After an error about the request itself (a bad head, a body that is too big, a timeout) the server always closes: it can't be sure where the next request would start.
- While the server is shutting down, the next reply says
Connection: close. - An idle connection is closed quietly -- no reply at all -- after
keepalive_timeoutseconds (5 by default).
A client may even send two requests at once without waiting ("pipelining"); the server answers them one at a time, in order.
What We Test
- Two requests on one HTTP/1.1 connection both get answers.
- Two requests sent in one packet are answered in order.
Connection: closeis honoured: the reply says so and the socket closes.- A handler that sets
Connection: closegets the connection closed after its reply. - HTTP/1.0 closes by default and stays open with
Connection: keep-alive. - A
404does not close the connection; the next request still works. - An idle keep-alive connection closes after
keepalive_timeoutwithout sending anything.
Next Up
What stops someone from standing at the window forever, or ordering a million scoops? See Limits and Timeouts.