Bug description:
HTTPResponse.read() blocks when a 304 (or 204) response carries Transfer-Encoding: chunked. With no timeout set it waits forever.
RFC 9112 §6.1 explicitly allows this header on a 304: "A server MAY send a Transfer-Encoding header field ... in a 304 (Not Modified) response to a GET request, neither of which includes a message body". So a compliant server can trigger it. requests and urllib3 go through http.client, so they hang the same way.
import http.client, socket, threading
srv = socket.create_server(("127.0.0.1", 0))
def serve():
conn, _ = srv.accept()
conn.recv(65536)
conn.sendall(b"HTTP/1.1 304 Not Modified\r\n"
b"Transfer-Encoding: chunked\r\n\r\n")
conn.recv(1) # keep the connection open, as a keep-alive server would
threading.Thread(target=serve, daemon=True).start()
c = http.client.HTTPConnection(*srv.getsockname(), timeout=5)
c.request("GET", "/")
r = c.getresponse()
print(r.status) # 304
print(r.read()) # expected b'', but blocks until the timeout (forever without one)
Cause: HTTPResponse.begin() sets self.length = 0 for 1xx, 204, 304 and HEAD, but leaves self.chunked = True. read() short-circuits for HEAD only, so for 204/304 it goes into _read_chunked() and waits for a chunk that never comes. HEAD hit the same problem in bpo-6312 (gh-50561) and got the HEAD-specific check in read(). The other bodiless statuses never did.
Suggested fix: set self.chunked = False alongside self.length = 0 in that block of begin(). I have a patch with a test and I'm happy to open the PR.
CPython versions tested on:
3.14, CPython main branch
Operating systems tested on:
macOS
Linked PRs
Bug description:
HTTPResponse.read()blocks when a 304 (or 204) response carriesTransfer-Encoding: chunked. With no timeout set it waits forever.RFC 9112 §6.1 explicitly allows this header on a 304: "A server MAY send a Transfer-Encoding header field ... in a 304 (Not Modified) response to a GET request, neither of which includes a message body". So a compliant server can trigger it. requests and urllib3 go through
http.client, so they hang the same way.Cause:
HTTPResponse.begin()setsself.length = 0for 1xx, 204, 304 and HEAD, but leavesself.chunked = True.read()short-circuits for HEAD only, so for 204/304 it goes into_read_chunked()and waits for a chunk that never comes. HEAD hit the same problem in bpo-6312 (gh-50561) and got the HEAD-specific check inread(). The other bodiless statuses never did.Suggested fix: set
self.chunked = Falsealongsideself.length = 0in that block ofbegin(). I have a patch with a test and I'm happy to open the PR.CPython versions tested on:
3.14, CPython main branch
Operating systems tested on:
macOS
Linked PRs