|
| 1 | +From 8864ac407bba8607950025e0979c4c69bc7abc7b Mon Sep 17 00:00:00 2001 |
| 2 | +From: Illia Volochii <illia.volochii@gmail.com> |
| 3 | +Date: Wed, 7 Jan 2026 18:07:30 +0200 |
| 4 | +Subject: [PATCH] Merge commit from fork |
| 5 | + |
| 6 | +* Stop decoding response content during redirects needlessly |
| 7 | + |
| 8 | +* Rename the new query parameter |
| 9 | + |
| 10 | +* Add a changelog entry |
| 11 | + |
| 12 | +Upstream Patch reference: https://github.com/urllib3/urllib3/commit/8864ac407bba8607950025e0979c4c69bc7abc7b.patch |
| 13 | +--- |
| 14 | + dummyserver/handlers.py | 9 ++++++++- |
| 15 | + src/urllib3/response.py | 4 +++- |
| 16 | + test/with_dummyserver/test_connectionpool.py | 19 +++++++++++++++++++ |
| 17 | + 3 files changed, 30 insertions(+), 2 deletions(-) |
| 18 | + |
| 19 | +diff --git a/dummyserver/handlers.py b/dummyserver/handlers.py |
| 20 | +index acd181d..db38a39 100644 |
| 21 | +--- a/dummyserver/handlers.py |
| 22 | ++++ b/dummyserver/handlers.py |
| 23 | +@@ -190,7 +190,14 @@ class TestingApp(RequestHandler): |
| 24 | + status = status.decode("latin-1") |
| 25 | + |
| 26 | + headers = [("Location", target)] |
| 27 | +- return Response(status=status, headers=headers) |
| 28 | ++ compressed = request.params.get("compressed") == b"true" |
| 29 | ++ if compressed: |
| 30 | ++ headers.append(("Content-Encoding", "gzip")) |
| 31 | ++ data = gzip.compress(b"foo") |
| 32 | ++ else: |
| 33 | ++ data = b"" |
| 34 | ++ |
| 35 | ++ return Response(data, status=status, headers=headers) |
| 36 | + |
| 37 | + def not_found(self, request): |
| 38 | + return Response("Not found", status="404 Not Found") |
| 39 | +diff --git a/src/urllib3/response.py b/src/urllib3/response.py |
| 40 | +index 0f8adbd..428b8ec 100644 |
| 41 | +--- a/src/urllib3/response.py |
| 42 | ++++ b/src/urllib3/response.py |
| 43 | +@@ -303,7 +303,9 @@ class HTTPResponse(io.IOBase): |
| 44 | + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. |
| 45 | + """ |
| 46 | + try: |
| 47 | +- self.read() |
| 48 | ++ # Do not spend resources decoding the content unless decoding has already been initiated. |
| 49 | ++ # In this backport we don't track that state, so we avoid decoding here. |
| 50 | ++ self.read(decode_content=False) |
| 51 | + except (HTTPError, SocketError, BaseSSLError, HTTPException): |
| 52 | + pass |
| 53 | + |
| 54 | +diff --git a/test/with_dummyserver/test_connectionpool.py b/test/with_dummyserver/test_connectionpool.py |
| 55 | +index cde027b..c80a16d 100644 |
| 56 | +--- a/test/with_dummyserver/test_connectionpool.py |
| 57 | ++++ b/test/with_dummyserver/test_connectionpool.py |
| 58 | +@@ -464,6 +464,25 @@ class TestConnectionPool(HTTPDummyServerTestCase): |
| 59 | + assert r.status == 200 |
| 60 | + assert r.data == b"Dummy server!" |
| 61 | + |
| 62 | ++ @mock.patch("urllib3.response.GzipDecoder.decompress") |
| 63 | ++ def test_no_decoding_with_redirect_when_preload_disabled( |
| 64 | ++ self, gzip_decompress: mock.MagicMock |
| 65 | ++ ) -> None: |
| 66 | ++ """ |
| 67 | ++ Test that urllib3 does not attempt to decode a gzipped redirect |
| 68 | ++ response when `preload_content` is set to `False`. |
| 69 | ++ """ |
| 70 | ++ with HTTPConnectionPool(self.host, self.port) as pool: |
| 71 | ++ # Three requests are expected: two redirects and one final / 200 OK. |
| 72 | ++ response = pool.request( |
| 73 | ++ "GET", |
| 74 | ++ "/redirect", |
| 75 | ++ fields={"target": "/redirect?compressed=true", "compressed": "true"}, |
| 76 | ++ preload_content=False, |
| 77 | ++ ) |
| 78 | ++ assert response.status == 200 |
| 79 | ++ gzip_decompress.assert_not_called() |
| 80 | ++ |
| 81 | + def test_303_redirect_makes_request_lose_body(self): |
| 82 | + with HTTPConnectionPool(self.host, self.port) as pool: |
| 83 | + response = pool.request( |
| 84 | +-- |
| 85 | +2.43.0 |
| 86 | + |
0 commit comments