Commit ce911c3f authored by Martin Panter's avatar Martin Panter

Issue #26499: Fixes to HTTPResponse.readline() and read1(), by Silent Ghost

parent 9528334e
...@@ -362,6 +362,10 @@ server. It provides access to the request headers and the entity ...@@ -362,6 +362,10 @@ server. It provides access to the request headers and the entity
body. The response is an iterable object and can be used in a with body. The response is an iterable object and can be used in a with
statement. statement.
.. versionchanged:: 3.5
The :class:`io.BufferedIOBase` interface is now implemented and
all of its reader operations are supported.
.. method:: HTTPResponse.read([amt]) .. method:: HTTPResponse.read([amt])
......
...@@ -635,6 +635,8 @@ class HTTPResponse(io.BufferedIOBase): ...@@ -635,6 +635,8 @@ class HTTPResponse(io.BufferedIOBase):
return b"" return b""
if self.chunked: if self.chunked:
return self._read1_chunked(n) return self._read1_chunked(n)
if self.length is not None and (n < 0 or n > self.length):
n = self.length
try: try:
result = self.fp.read1(n) result = self.fp.read1(n)
except ValueError: except ValueError:
...@@ -645,6 +647,8 @@ class HTTPResponse(io.BufferedIOBase): ...@@ -645,6 +647,8 @@ class HTTPResponse(io.BufferedIOBase):
result = self.fp.read1(16*1024) result = self.fp.read1(16*1024)
if not result and n: if not result and n:
self._close_conn() self._close_conn()
elif self.length is not None:
self.length -= len(result)
return result return result
def peek(self, n=-1): def peek(self, n=-1):
...@@ -662,9 +666,13 @@ class HTTPResponse(io.BufferedIOBase): ...@@ -662,9 +666,13 @@ class HTTPResponse(io.BufferedIOBase):
if self.chunked: if self.chunked:
# Fallback to IOBase readline which uses peek() and read() # Fallback to IOBase readline which uses peek() and read()
return super().readline(limit) return super().readline(limit)
if self.length is not None and (limit < 0 or limit > self.length):
limit = self.length
result = self.fp.readline(limit) result = self.fp.readline(limit)
if not result and limit: if not result and limit:
self._close_conn() self._close_conn()
elif self.length is not None:
self.length -= len(result)
return result return result
def _read1_chunked(self, n): def _read1_chunked(self, n):
......
...@@ -341,8 +341,8 @@ class BasicTest(TestCase): ...@@ -341,8 +341,8 @@ class BasicTest(TestCase):
self.assertEqual(repr(exc), '''BadStatusLine("\'\'",)''') self.assertEqual(repr(exc), '''BadStatusLine("\'\'",)''')
def test_partial_reads(self): def test_partial_reads(self):
# if we have a length, the system knows when to close itself # if we have Content-Length, HTTPResponse knows when to close itself,
# same behaviour than when we read the whole thing with read() # the same behaviour as when we read the whole thing with read()
body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText" body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText"
sock = FakeSocket(body) sock = FakeSocket(body)
resp = client.HTTPResponse(sock) resp = client.HTTPResponse(sock)
...@@ -355,9 +355,24 @@ class BasicTest(TestCase): ...@@ -355,9 +355,24 @@ class BasicTest(TestCase):
resp.close() resp.close()
self.assertTrue(resp.closed) self.assertTrue(resp.closed)
def test_mixed_reads(self):
# readline() should update the remaining length, so that read() knows
# how much data is left and does not raise IncompleteRead
body = "HTTP/1.1 200 Ok\r\nContent-Length: 13\r\n\r\nText\r\nAnother"
sock = FakeSocket(body)
resp = client.HTTPResponse(sock)
resp.begin()
self.assertEqual(resp.readline(), b'Text\r\n')
self.assertFalse(resp.isclosed())
self.assertEqual(resp.read(), b'Another')
self.assertTrue(resp.isclosed())
self.assertFalse(resp.closed)
resp.close()
self.assertTrue(resp.closed)
def test_partial_readintos(self): def test_partial_readintos(self):
# if we have a length, the system knows when to close itself # if we have Content-Length, HTTPResponse knows when to close itself,
# same behaviour than when we read the whole thing with read() # the same behaviour as when we read the whole thing with read()
body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText" body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText"
sock = FakeSocket(body) sock = FakeSocket(body)
resp = client.HTTPResponse(sock) resp = client.HTTPResponse(sock)
...@@ -827,7 +842,7 @@ class BasicTest(TestCase): ...@@ -827,7 +842,7 @@ class BasicTest(TestCase):
resp.begin() resp.begin()
self.assertEqual(resp.read(), expected) self.assertEqual(resp.read(), expected)
# we should have reached the end of the file # we should have reached the end of the file
self.assertEqual(sock.file.read(100), b"") #we read to the end self.assertEqual(sock.file.read(), b"") #we read to the end
resp.close() resp.close()
def test_chunked_sync(self): def test_chunked_sync(self):
...@@ -839,19 +854,65 @@ class BasicTest(TestCase): ...@@ -839,19 +854,65 @@ class BasicTest(TestCase):
resp.begin() resp.begin()
self.assertEqual(resp.read(), expected) self.assertEqual(resp.read(), expected)
# the file should now have our extradata ready to be read # the file should now have our extradata ready to be read
self.assertEqual(sock.file.read(100), extradata.encode("ascii")) #we read to the end self.assertEqual(sock.file.read(), extradata.encode("ascii")) #we read to the end
resp.close() resp.close()
def test_content_length_sync(self): def test_content_length_sync(self):
"""Check that we don't read past the end of the Content-Length stream""" """Check that we don't read past the end of the Content-Length stream"""
extradata = "extradata" extradata = b"extradata"
expected = b"Hello123\r\n"
sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n' + expected + extradata)
resp = client.HTTPResponse(sock, method="GET")
resp.begin()
self.assertEqual(resp.read(), expected)
# the file should now have our extradata ready to be read
self.assertEqual(sock.file.read(), extradata) #we read to the end
resp.close()
def test_readlines_content_length(self):
extradata = b"extradata"
expected = b"Hello123\r\n"
sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n' + expected + extradata)
resp = client.HTTPResponse(sock, method="GET")
resp.begin()
self.assertEqual(resp.readlines(2000), [expected])
# the file should now have our extradata ready to be read
self.assertEqual(sock.file.read(), extradata) #we read to the end
resp.close()
def test_read1_content_length(self):
extradata = b"extradata"
expected = b"Hello123\r\n"
sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n' + expected + extradata)
resp = client.HTTPResponse(sock, method="GET")
resp.begin()
self.assertEqual(resp.read1(2000), expected)
# the file should now have our extradata ready to be read
self.assertEqual(sock.file.read(), extradata) #we read to the end
resp.close()
def test_readline_bound_content_length(self):
extradata = b"extradata"
expected = b"Hello123\r\n"
sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n' + expected + extradata)
resp = client.HTTPResponse(sock, method="GET")
resp.begin()
self.assertEqual(resp.readline(10), expected)
self.assertEqual(resp.readline(10), b"")
# the file should now have our extradata ready to be read
self.assertEqual(sock.file.read(), extradata) #we read to the end
resp.close()
def test_read1_bound_content_length(self):
extradata = b"extradata"
expected = b"Hello123\r\n" expected = b"Hello123\r\n"
sock = FakeSocket('HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\nHello123\r\n' + extradata) sock = FakeSocket(b'HTTP/1.1 200 OK\r\nContent-Length: 30\r\n\r\n' + expected*3 + extradata)
resp = client.HTTPResponse(sock, method="GET") resp = client.HTTPResponse(sock, method="GET")
resp.begin() resp.begin()
self.assertEqual(resp.read1(20), expected*2)
self.assertEqual(resp.read(), expected) self.assertEqual(resp.read(), expected)
# the file should now have our extradata ready to be read # the file should now have our extradata ready to be read
self.assertEqual(sock.file.read(100), extradata.encode("ascii")) #we read to the end self.assertEqual(sock.file.read(), extradata) #we read to the end
resp.close() resp.close()
class ExtendedReadTest(TestCase): class ExtendedReadTest(TestCase):
......
...@@ -91,6 +91,10 @@ Core and Builtins ...@@ -91,6 +91,10 @@ Core and Builtins
Library Library
------- -------
- Issue #26499: Account for remaining Content-Length in
HTTPResponse.readline() and read1(). Based on patch by Silent Ghost.
Also document that HTTPResponse now supports these methods.
- Issue #25320: Handle sockets in directories unittest discovery is scanning. - Issue #25320: Handle sockets in directories unittest discovery is scanning.
Patch from Victor van den Elzen. Patch from Victor van den Elzen.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment