Commit b5b9c8cd authored by Serhiy Storchaka's avatar Serhiy Storchaka

Issue #16723: httplib.HTTPResponse no longer marked closed when the connection

is automatically closed.
parent f581b372
...@@ -324,7 +324,7 @@ class HTTPResponse(io.RawIOBase): ...@@ -324,7 +324,7 @@ class HTTPResponse(io.RawIOBase):
# empty version will cause next test to fail. # empty version will cause next test to fail.
version = "" version = ""
if not version.startswith("HTTP/"): if not version.startswith("HTTP/"):
self.close() self._close_conn()
raise BadStatusLine(line) raise BadStatusLine(line)
# The status code is a three-digit number # The status code is a three-digit number
...@@ -446,21 +446,24 @@ class HTTPResponse(io.RawIOBase): ...@@ -446,21 +446,24 @@ class HTTPResponse(io.RawIOBase):
# otherwise, assume it will close # otherwise, assume it will close
return True return True
def _close_conn(self):
fp = self.fp
self.fp = None
fp.close()
def close(self): def close(self):
super().close() # set "closed" flag
if self.fp: if self.fp:
self.fp.close() self._close_conn()
self.fp = None
# These implementations are for the benefit of io.BufferedReader. # These implementations are for the benefit of io.BufferedReader.
# XXX This class should probably be revised to act more like # XXX This class should probably be revised to act more like
# the "raw stream" that BufferedReader expects. # the "raw stream" that BufferedReader expects.
@property
def closed(self):
return self.isclosed()
def flush(self): def flush(self):
super().flush()
if self.fp:
self.fp.flush() self.fp.flush()
def readable(self): def readable(self):
...@@ -469,6 +472,7 @@ class HTTPResponse(io.RawIOBase): ...@@ -469,6 +472,7 @@ class HTTPResponse(io.RawIOBase):
# End of "raw stream" methods # End of "raw stream" methods
def isclosed(self): def isclosed(self):
"""True if the connection is closed."""
# NOTE: it is possible that we will not ever call self.close(). This # NOTE: it is possible that we will not ever call self.close(). This
# case occurs when will_close is TRUE, length is None, and we # case occurs when will_close is TRUE, length is None, and we
# read up to the last byte, but NOT past it. # read up to the last byte, but NOT past it.
...@@ -482,7 +486,7 @@ class HTTPResponse(io.RawIOBase): ...@@ -482,7 +486,7 @@ class HTTPResponse(io.RawIOBase):
return b"" return b""
if self._method == "HEAD": if self._method == "HEAD":
self.close() self._close_conn()
return b"" return b""
if self.chunked: if self.chunked:
...@@ -496,10 +500,10 @@ class HTTPResponse(io.RawIOBase): ...@@ -496,10 +500,10 @@ class HTTPResponse(io.RawIOBase):
try: try:
s = self._safe_read(self.length) s = self._safe_read(self.length)
except IncompleteRead: except IncompleteRead:
self.close() self._close_conn()
raise raise
self.length = 0 self.length = 0
self.close() # we read everything self._close_conn() # we read everything
return s return s
if self.length is not None: if self.length is not None:
...@@ -514,11 +518,11 @@ class HTTPResponse(io.RawIOBase): ...@@ -514,11 +518,11 @@ class HTTPResponse(io.RawIOBase):
if not s: if not s:
# Ideally, we would raise IncompleteRead if the content-length # Ideally, we would raise IncompleteRead if the content-length
# wasn't satisfied, but it might break compatibility. # wasn't satisfied, but it might break compatibility.
self.close() self._close_conn()
elif self.length is not None: elif self.length is not None:
self.length -= len(s) self.length -= len(s)
if not self.length: if not self.length:
self.close() self._close_conn()
return s return s
...@@ -539,7 +543,7 @@ class HTTPResponse(io.RawIOBase): ...@@ -539,7 +543,7 @@ class HTTPResponse(io.RawIOBase):
except ValueError: except ValueError:
# close the connection as protocol synchronisation is # close the connection as protocol synchronisation is
# probably lost # probably lost
self.close() self._close_conn()
raise IncompleteRead(b''.join(value)) raise IncompleteRead(b''.join(value))
if chunk_left == 0: if chunk_left == 0:
break break
...@@ -576,7 +580,7 @@ class HTTPResponse(io.RawIOBase): ...@@ -576,7 +580,7 @@ class HTTPResponse(io.RawIOBase):
break break
# we read everything; close the "file" # we read everything; close the "file"
self.close() self._close_conn()
return b''.join(value) return b''.join(value)
......
...@@ -164,6 +164,9 @@ class BasicTest(TestCase): ...@@ -164,6 +164,9 @@ class BasicTest(TestCase):
resp.begin() resp.begin()
self.assertEqual(resp.read(), b"Text") self.assertEqual(resp.read(), b"Text")
self.assertTrue(resp.isclosed()) self.assertTrue(resp.isclosed())
self.assertFalse(resp.closed)
resp.close()
self.assertTrue(resp.closed)
body = "HTTP/1.1 400.100 Not Ok\r\n\r\nText" body = "HTTP/1.1 400.100 Not Ok\r\n\r\nText"
sock = FakeSocket(body) sock = FakeSocket(body)
...@@ -185,6 +188,9 @@ class BasicTest(TestCase): ...@@ -185,6 +188,9 @@ class BasicTest(TestCase):
self.assertFalse(resp.isclosed()) self.assertFalse(resp.isclosed())
self.assertEqual(resp.read(2), b'xt') self.assertEqual(resp.read(2), b'xt')
self.assertTrue(resp.isclosed()) self.assertTrue(resp.isclosed())
self.assertFalse(resp.closed)
resp.close()
self.assertTrue(resp.closed)
def test_partial_reads_no_content_length(self): def test_partial_reads_no_content_length(self):
# when no length is present, the socket should be gracefully closed when # when no length is present, the socket should be gracefully closed when
...@@ -198,6 +204,9 @@ class BasicTest(TestCase): ...@@ -198,6 +204,9 @@ class BasicTest(TestCase):
self.assertEqual(resp.read(2), b'xt') self.assertEqual(resp.read(2), b'xt')
self.assertEqual(resp.read(1), b'') self.assertEqual(resp.read(1), b'')
self.assertTrue(resp.isclosed()) self.assertTrue(resp.isclosed())
self.assertFalse(resp.closed)
resp.close()
self.assertTrue(resp.closed)
def test_partial_reads_incomplete_body(self): def test_partial_reads_incomplete_body(self):
# if the server shuts down the connection before the whole # if the server shuts down the connection before the whole
...@@ -211,6 +220,9 @@ class BasicTest(TestCase): ...@@ -211,6 +220,9 @@ class BasicTest(TestCase):
self.assertEqual(resp.read(2), b'xt') self.assertEqual(resp.read(2), b'xt')
self.assertEqual(resp.read(1), b'') self.assertEqual(resp.read(1), b'')
self.assertTrue(resp.isclosed()) self.assertTrue(resp.isclosed())
self.assertFalse(resp.closed)
resp.close()
self.assertTrue(resp.closed)
def test_host_port(self): def test_host_port(self):
# Check invalid host_port # Check invalid host_port
...@@ -355,6 +367,9 @@ class BasicTest(TestCase): ...@@ -355,6 +367,9 @@ class BasicTest(TestCase):
self.assertEqual(resp.status, 200) self.assertEqual(resp.status, 200)
self.assertEqual(resp.reason, 'OK') self.assertEqual(resp.reason, 'OK')
self.assertTrue(resp.isclosed()) self.assertTrue(resp.isclosed())
self.assertFalse(resp.closed)
resp.close()
self.assertTrue(resp.closed)
def test_negative_content_length(self): def test_negative_content_length(self):
sock = FakeSocket( sock = FakeSocket(
...@@ -430,6 +445,9 @@ class BasicTest(TestCase): ...@@ -430,6 +445,9 @@ class BasicTest(TestCase):
resp.begin() resp.begin()
self.assertEqual(resp.read(), b'') self.assertEqual(resp.read(), b'')
self.assertTrue(resp.isclosed()) self.assertTrue(resp.isclosed())
self.assertFalse(resp.closed)
resp.close()
self.assertTrue(resp.closed)
class OfflineTest(TestCase): class OfflineTest(TestCase):
def test_responses(self): def test_responses(self):
......
...@@ -212,6 +212,8 @@ Core and Builtins ...@@ -212,6 +212,8 @@ Core and Builtins
Library Library
------- -------
- Issue #16723: httplib.HTTPResponse no longer marked closed when the connection
is automatically closed.
- Issue #16948: Fix quoted printable body encoding for non-latin1 character - Issue #16948: Fix quoted printable body encoding for non-latin1 character
sets in the email package. sets in the email package.
......
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