Originally posted by kenballus January 28, 2024
From RFC 9112, section 9.6:
A server that receives a "close" connection option MUST initiate closure of the connection (see below) after it sends the final response to the request that contained the "close" connection option. The server SHOULD send a "close" connection option in its final response on that connection. The server MUST NOT process any further requests received on that connection.
When uvicorn receives a pipeline with a request containing Connection: close
, followed by an invalid request, uvicorn responds only to the second (invalid) request, even though the standard requires that uvicorn respond only to the first one.
Connection: close
set, followed by an invalid request:printf 'GET / HTTP/1.1\r\nConnection: close\r\n\r\nInvalid\r\n\r\n' | nc localhost 8080
HTTP/1.1 400 Bad Request
content-type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: close
1e
Invalid HTTP request received.
0
The server should respond only to the first request, and then close the connection.
INFO: 127.0.0.1:51922 - "GET / HTTP/1.1" 200 OK
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/uvicorn/protocols/http/h11_impl.py", line 404, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/example.py", line 4, in app
await send({
File "/usr/local/lib/python3.11/dist-packages/uvicorn/protocols/http/h11_impl.py", line 486, in send
output = self.conn.send(event=response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/h11/_connection.py", line 512, in send
data_list = self.send_with_data_passthrough(event)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/h11/_connection.py", line 537, in send_with_data_passthrough
self._process_event(self.our_role, event)
File "/usr/local/lib/python3.11/dist-packages/h11/_connection.py", line 272, in _process_event
self._cstate.process_event(role, type(event), server_switch_event)
File "/usr/local/lib/python3.11/dist-packages/h11/_state.py", line 293, in process_event
self._fire_event_triggered_transitions(role, _event_type)
File "/usr/local/lib/python3.11/dist-packages/h11/_state.py", line 311, in _fire_event_triggered_transitions
raise LocalProtocolError(
h11._util.LocalProtocolError: can't handle event type Response when role=SERVER and state=MUST_CLOSE
$ python --version
Python 3.11.2
$ python -m pip show uvicorn
Name: uvicorn
Version: 0.27.0
Summary: The lightning-fast ASGI server.
Home-page:
Author:
Author-email: Tom Christie <tom@tomchristie.com>
License:
Location: /usr/local/lib/python3.11/dist-packages
Requires: click, h11
Required-by:
$ python -m pip show h11
Name: h11
Version: 0.14.0
Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1
Home-page: https://github.com/python-hyper/h11
Author: Nathaniel J. Smith
Author-email: njs@pobox.com
License: MIT
Location: /usr/local/lib/python3.11/dist-packages
Requires:
Required-by: uvicorn
Debian 12 (running in Docker on Arch Linux)
Linux 6.7.2
Some other HTTP implementations that handle this correctly:
Apache httpd, Boost::Beast, Daphne, H2O, Lighttpd, Nginx, Tornado, OpenWrt uhttpd, Waitress
Some other HTTP implementations that also have this bug:
Mongoose, aiohttp
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too