Originally posted by 8dazo August 17, 2026
Initial Checks
Discussion Link
#3071
Description
On the httptools HTTP implementation (what --http auto selects when httptools is installed), Uvicorn only honors a request Connection: close when the header value is exactly lowercase close.
Connection: Close and Connection: keep-alive, close leave the TCP connection open and do not echo Connection: close on the response.
This is a bug, not intentional:
docs/server-behavior.md says a Connection: Close header must close the connection, and that HTTP headers are case-insensitive.
- RFC 9112 §9.6: a server that receives a
close connection option MUST close after the final response, and SHOULD send Connection: close on that response. Tokens are case-insensitive (RFC 9110 §7.6.1).
- httptools already treats the response
Connection value as case-insensitive (value.lower() == b"close"). The request check is an exact tuple match on (b"connection", b"close"). Existing tests only send lowercase close.
As noted in the discussion, the exact-match is not local to httptools. CLOSE_HEADER in flow_control.py is used the same way in httptools_impl.py, h11_impl.py, and zttp_impl.py. h11 and zttp still close via their own state machines, but they also fail to echo Connection: close for mixed-case / multi-token values (RFC SHOULD). Patching only httptools would leave that echo gap.
Lowercasing the whole header value is not enough for keep-alive, close. Connection is a comma-separated token list. The same tokenisation already exists in _get_upgrade() (split, strip, lower, membership test). A small helper next to CLOSE_HEADER (split / strip / lowercase each token) should be used for the echo in all implementations, and for httptools' keep-alive decision.
Actual (0.52.3 / main):
h11 close | Close | keep-alive, close closing=True echo=True for lowercase only
httptools close closing=True echo=True
httptools Close closing=False echo=False
httptools keep-alive, close closing=False echo=False
zttp close closing=True echo=True
zttp Close / keep-alive, close closing=True echo=False
Expected: all three implementations close after the response and echo Connection: close whenever the request Connection header contains a close token, regardless of case, surrounding whitespace, or sibling tokens.
Example Code
import asyncio
from tests.protocols.test_http import get_connected_protocol
from tests.response import Response
from uvicorn.protocols.http.h11_impl import H11Protocol
from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
from uvicorn.protocols.http.zttp_impl import ZttpProtocol
REQUESTS = {
"lower": b"GET / HTTP/1.1\r\nHost: example.org\r\nConnection: close\r\n\r\n",
"upper": b"GET / HTTP/1.1\r\nHost: example.org\r\nConnection: Close\r\n\r\n",
"mixed": b"GET / HTTP/1.1\r\nHost: example.org\r\nConnection: keep-alive, close\r\n\r\n",
}
async def main():
for name, cls in [
("h11", H11Protocol),
("httptools", HttpToolsProtocol),
("zttp", ZttpProtocol),
]:
for key, req in REQUESTS.items():
protocol = get_connected_protocol(
Response("Hello, world", media_type="text/plain"),
cls,
access_log=False,
)
protocol.data_received(req)
await protocol.loop.run_one()
buf = protocol.transport.buffer.lower()
print(
name,
key,
"closing=",
protocol.transport.is_closing(),
"echo=",
b"connection: close" in buf,
)
asyncio.run(main())
Python, Uvicorn & OS Version
Running uvicorn 0.52.3 with CPython 3.12.13 on Darwin
Discussed in #3071
Originally posted by 8dazo August 17, 2026
Initial Checks
Discussion Link
#3071
Description
On the
httptoolsHTTP implementation (what--http autoselects when httptools is installed), Uvicorn only honors a requestConnection: closewhen the header value is exactly lowercaseclose.Connection: CloseandConnection: keep-alive, closeleave the TCP connection open and do not echoConnection: closeon the response.This is a bug, not intentional:
docs/server-behavior.mdsays aConnection: Closeheader must close the connection, and that HTTP headers are case-insensitive.closeconnection option MUST close after the final response, and SHOULD sendConnection: closeon that response. Tokens are case-insensitive (RFC 9110 §7.6.1).Connectionvalue as case-insensitive (value.lower() == b"close"). The request check is an exact tuple match on(b"connection", b"close"). Existing tests only send lowercaseclose.As noted in the discussion, the exact-match is not local to httptools.
CLOSE_HEADERinflow_control.pyis used the same way inhttptools_impl.py,h11_impl.py, andzttp_impl.py. h11 and zttp still close via their own state machines, but they also fail to echoConnection: closefor mixed-case / multi-token values (RFC SHOULD). Patching only httptools would leave that echo gap.Lowercasing the whole header value is not enough for
keep-alive, close. Connection is a comma-separated token list. The same tokenisation already exists in_get_upgrade()(split,strip,lower, membership test). A small helper next toCLOSE_HEADER(split / strip / lowercase each token) should be used for the echo in all implementations, and for httptools' keep-alive decision.Actual (0.52.3 /
main):Expected: all three implementations close after the response and echo
Connection: closewhenever the request Connection header contains aclosetoken, regardless of case, surrounding whitespace, or sibling tokens.Example Code
Python, Uvicorn & OS Version