Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 152f0b8

Browse files
authored
bpo-41002: Optimize HTTPResponse.read with a given amount (GH-20943)
I've done the implementation for both non-chunked and chunked reads. I haven't benchmarked chunked reads because I don't currently have a convenient way to generate a high-bandwidth chunked stream, but I don't see any reason that it shouldn't enjoy the same benefits that the non-chunked case does. I've used the benchmark attached to the bpo bug to verify that performance now matches the unsized read case. Automerge-Triggered-By: @methane
1 parent cf18c9e commit 152f0b8

3 files changed

Lines changed: 53 additions & 10 deletions

File tree

Lib/http/client.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -448,18 +448,25 @@ def read(self, amt=None):
448448
self._close_conn()
449449
return b""
450450

451+
if self.chunked:
452+
return self._read_chunked(amt)
453+
451454
if amt is not None:
452-
# Amount is given, implement using readinto
453-
b = bytearray(amt)
454-
n = self.readinto(b)
455-
return memoryview(b)[:n].tobytes()
455+
if self.length is not None and amt > self.length:
456+
# clip the read to the "end of response"
457+
amt = self.length
458+
s = self.fp.read(amt)
459+
if not s and amt:
460+
# Ideally, we would raise IncompleteRead if the content-length
461+
# wasn't satisfied, but it might break compatibility.
462+
self._close_conn()
463+
elif self.length is not None:
464+
self.length -= len(s)
465+
if not self.length:
466+
self._close_conn()
467+
return s
456468
else:
457469
# Amount is not given (unbounded read) so we must check self.length
458-
# and self.chunked
459-
460-
if self.chunked:
461-
return self._readall_chunked()
462-
463470
if self.length is None:
464471
s = self.fp.read()
465472
else:
@@ -560,15 +567,23 @@ def _get_chunk_left(self):
560567
self.chunk_left = chunk_left
561568
return chunk_left
562569

563-
def _readall_chunked(self):
570+
def _read_chunked(self, amt=None):
564571
assert self.chunked != _UNKNOWN
565572
value = []
566573
try:
567574
while True:
568575
chunk_left = self._get_chunk_left()
569576
if chunk_left is None:
570577
break
578+
579+
if amt is not None and amt <= chunk_left:
580+
value.append(self._safe_read(amt))
581+
self.chunk_left = chunk_left - amt
582+
break
583+
571584
value.append(self._safe_read(chunk_left))
585+
if amt is not None:
586+
amt -= chunk_left
572587
self.chunk_left = 0
573588
return b''.join(value)
574589
except IncompleteRead:

Lib/test/test_httplib.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,33 @@ def test_partial_readintos(self):
569569
resp.close()
570570
self.assertTrue(resp.closed)
571571

572+
def test_partial_reads_past_end(self):
573+
# if we have Content-Length, clip reads to the end
574+
body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText"
575+
sock = FakeSocket(body)
576+
resp = client.HTTPResponse(sock)
577+
resp.begin()
578+
self.assertEqual(resp.read(10), b'Text')
579+
self.assertTrue(resp.isclosed())
580+
self.assertFalse(resp.closed)
581+
resp.close()
582+
self.assertTrue(resp.closed)
583+
584+
def test_partial_readintos_past_end(self):
585+
# if we have Content-Length, clip readintos to the end
586+
body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText"
587+
sock = FakeSocket(body)
588+
resp = client.HTTPResponse(sock)
589+
resp.begin()
590+
b = bytearray(10)
591+
n = resp.readinto(b)
592+
self.assertEqual(n, 4)
593+
self.assertEqual(bytes(b)[:4], b'Text')
594+
self.assertTrue(resp.isclosed())
595+
self.assertFalse(resp.closed)
596+
resp.close()
597+
self.assertTrue(resp.closed)
598+
572599
def test_partial_reads_no_content_length(self):
573600
# when no length is present, the socket should be gracefully closed when
574601
# all data was read
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve performance of HTTPResponse.read with a given amount. Patch by Bruce Merry.

0 commit comments

Comments
 (0)