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

Skip to content

Commit ce911c3

Browse files
committed
Issue #26499: Fixes to HTTPResponse.readline() and read1(), by Silent Ghost
1 parent 9528334 commit ce911c3

4 files changed

Lines changed: 86 additions & 9 deletions

File tree

Doc/library/http.client.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,10 @@ server. It provides access to the request headers and the entity
362362
body. The response is an iterable object and can be used in a with
363363
statement.
364364

365+
.. versionchanged:: 3.5
366+
The :class:`io.BufferedIOBase` interface is now implemented and
367+
all of its reader operations are supported.
368+
365369

366370
.. method:: HTTPResponse.read([amt])
367371

Lib/http/client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,8 @@ def read1(self, n=-1):
635635
return b""
636636
if self.chunked:
637637
return self._read1_chunked(n)
638+
if self.length is not None and (n < 0 or n > self.length):
639+
n = self.length
638640
try:
639641
result = self.fp.read1(n)
640642
except ValueError:
@@ -645,6 +647,8 @@ def read1(self, n=-1):
645647
result = self.fp.read1(16*1024)
646648
if not result and n:
647649
self._close_conn()
650+
elif self.length is not None:
651+
self.length -= len(result)
648652
return result
649653

650654
def peek(self, n=-1):
@@ -662,9 +666,13 @@ def readline(self, limit=-1):
662666
if self.chunked:
663667
# Fallback to IOBase readline which uses peek() and read()
664668
return super().readline(limit)
669+
if self.length is not None and (limit < 0 or limit > self.length):
670+
limit = self.length
665671
result = self.fp.readline(limit)
666672
if not result and limit:
667673
self._close_conn()
674+
elif self.length is not None:
675+
self.length -= len(result)
668676
return result
669677

670678
def _read1_chunked(self, n):

Lib/test/test_httplib.py

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,8 @@ def test_bad_status_repr(self):
341341
self.assertEqual(repr(exc), '''BadStatusLine("\'\'",)''')
342342

343343
def test_partial_reads(self):
344-
# if we have a length, the system knows when to close itself
345-
# same behaviour than when we read the whole thing with read()
344+
# if we have Content-Length, HTTPResponse knows when to close itself,
345+
# the same behaviour as when we read the whole thing with read()
346346
body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText"
347347
sock = FakeSocket(body)
348348
resp = client.HTTPResponse(sock)
@@ -355,9 +355,24 @@ def test_partial_reads(self):
355355
resp.close()
356356
self.assertTrue(resp.closed)
357357

358+
def test_mixed_reads(self):
359+
# readline() should update the remaining length, so that read() knows
360+
# how much data is left and does not raise IncompleteRead
361+
body = "HTTP/1.1 200 Ok\r\nContent-Length: 13\r\n\r\nText\r\nAnother"
362+
sock = FakeSocket(body)
363+
resp = client.HTTPResponse(sock)
364+
resp.begin()
365+
self.assertEqual(resp.readline(), b'Text\r\n')
366+
self.assertFalse(resp.isclosed())
367+
self.assertEqual(resp.read(), b'Another')
368+
self.assertTrue(resp.isclosed())
369+
self.assertFalse(resp.closed)
370+
resp.close()
371+
self.assertTrue(resp.closed)
372+
358373
def test_partial_readintos(self):
359-
# if we have a length, the system knows when to close itself
360-
# same behaviour than when we read the whole thing with read()
374+
# if we have Content-Length, HTTPResponse knows when to close itself,
375+
# the same behaviour as when we read the whole thing with read()
361376
body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText"
362377
sock = FakeSocket(body)
363378
resp = client.HTTPResponse(sock)
@@ -827,7 +842,7 @@ def test_chunked_trailers(self):
827842
resp.begin()
828843
self.assertEqual(resp.read(), expected)
829844
# we should have reached the end of the file
830-
self.assertEqual(sock.file.read(100), b"") #we read to the end
845+
self.assertEqual(sock.file.read(), b"") #we read to the end
831846
resp.close()
832847

833848
def test_chunked_sync(self):
@@ -839,19 +854,65 @@ def test_chunked_sync(self):
839854
resp.begin()
840855
self.assertEqual(resp.read(), expected)
841856
# the file should now have our extradata ready to be read
842-
self.assertEqual(sock.file.read(100), extradata.encode("ascii")) #we read to the end
857+
self.assertEqual(sock.file.read(), extradata.encode("ascii")) #we read to the end
843858
resp.close()
844859

845860
def test_content_length_sync(self):
846861
"""Check that we don't read past the end of the Content-Length stream"""
847-
extradata = "extradata"
862+
extradata = b"extradata"
863+
expected = b"Hello123\r\n"
864+
sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n' + expected + extradata)
865+
resp = client.HTTPResponse(sock, method="GET")
866+
resp.begin()
867+
self.assertEqual(resp.read(), expected)
868+
# the file should now have our extradata ready to be read
869+
self.assertEqual(sock.file.read(), extradata) #we read to the end
870+
resp.close()
871+
872+
def test_readlines_content_length(self):
873+
extradata = b"extradata"
874+
expected = b"Hello123\r\n"
875+
sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n' + expected + extradata)
876+
resp = client.HTTPResponse(sock, method="GET")
877+
resp.begin()
878+
self.assertEqual(resp.readlines(2000), [expected])
879+
# the file should now have our extradata ready to be read
880+
self.assertEqual(sock.file.read(), extradata) #we read to the end
881+
resp.close()
882+
883+
def test_read1_content_length(self):
884+
extradata = b"extradata"
885+
expected = b"Hello123\r\n"
886+
sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n' + expected + extradata)
887+
resp = client.HTTPResponse(sock, method="GET")
888+
resp.begin()
889+
self.assertEqual(resp.read1(2000), expected)
890+
# the file should now have our extradata ready to be read
891+
self.assertEqual(sock.file.read(), extradata) #we read to the end
892+
resp.close()
893+
894+
def test_readline_bound_content_length(self):
895+
extradata = b"extradata"
896+
expected = b"Hello123\r\n"
897+
sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n' + expected + extradata)
898+
resp = client.HTTPResponse(sock, method="GET")
899+
resp.begin()
900+
self.assertEqual(resp.readline(10), expected)
901+
self.assertEqual(resp.readline(10), b"")
902+
# the file should now have our extradata ready to be read
903+
self.assertEqual(sock.file.read(), extradata) #we read to the end
904+
resp.close()
905+
906+
def test_read1_bound_content_length(self):
907+
extradata = b"extradata"
848908
expected = b"Hello123\r\n"
849-
sock = FakeSocket('HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\nHello123\r\n' + extradata)
909+
sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 30\r\n\r\n' + expected*3 + extradata)
850910
resp = client.HTTPResponse(sock, method="GET")
851911
resp.begin()
912+
self.assertEqual(resp.read1(20), expected*2)
852913
self.assertEqual(resp.read(), expected)
853914
# the file should now have our extradata ready to be read
854-
self.assertEqual(sock.file.read(100), extradata.encode("ascii")) #we read to the end
915+
self.assertEqual(sock.file.read(), extradata) #we read to the end
855916
resp.close()
856917

857918
class ExtendedReadTest(TestCase):

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ Core and Builtins
9191
Library
9292
-------
9393

94+
- Issue #26499: Account for remaining Content-Length in
95+
HTTPResponse.readline() and read1(). Based on patch by Silent Ghost.
96+
Also document that HTTPResponse now supports these methods.
97+
9498
- Issue #25320: Handle sockets in directories unittest discovery is scanning.
9599
Patch from Victor van den Elzen.
96100

0 commit comments

Comments
 (0)