Issue #21032. Fixed socket leak if HTTPConnection.getresponse() fails.

Original patch by Martin Panter.
This commit is contained in:
Serhiy Storchaka 2014-12-01 13:07:45 +02:00
parent 9cba989502
commit b491e0521f
3 changed files with 41 additions and 11 deletions

View File

@ -1169,18 +1169,22 @@ class HTTPConnection:
else: else:
response = self.response_class(self.sock, method=self._method) response = self.response_class(self.sock, method=self._method)
response.begin() try:
assert response.will_close != _UNKNOWN response.begin()
self.__state = _CS_IDLE assert response.will_close != _UNKNOWN
self.__state = _CS_IDLE
if response.will_close: if response.will_close:
# this effectively passes the connection to the response # this effectively passes the connection to the response
self.close() self.close()
else: else:
# remember this, so we can tell when it is complete # remember this, so we can tell when it is complete
self.__response = response self.__response = response
return response return response
except:
response.close()
raise
try: try:
import ssl import ssl

View File

@ -28,6 +28,7 @@ class FakeSocket:
self.fileclass = fileclass self.fileclass = fileclass
self.data = b'' self.data = b''
self.sendall_calls = 0 self.sendall_calls = 0
self.file_closed = False
self.host = host self.host = host
self.port = port self.port = port
@ -38,7 +39,13 @@ class FakeSocket:
def makefile(self, mode, bufsize=None): def makefile(self, mode, bufsize=None):
if mode != 'r' and mode != 'rb': if mode != 'r' and mode != 'rb':
raise client.UnimplementedFileMode() raise client.UnimplementedFileMode()
return self.fileclass(self.text) # keep the file around so we can check how much was read from it
self.file = self.fileclass(self.text)
self.file.close = self.file_close #nerf close ()
return self.file
def file_close(self):
self.file_closed = True
def close(self): def close(self):
pass pass
@ -675,6 +682,22 @@ class BasicTest(TestCase):
conn.request('POST', '/', body) conn.request('POST', '/', body)
self.assertGreater(sock.sendall_calls, 1) self.assertGreater(sock.sendall_calls, 1)
def test_error_leak(self):
# Test that the socket is not leaked if getresponse() fails
conn = client.HTTPConnection('example.com')
response = None
class Response(client.HTTPResponse):
def __init__(self, *pos, **kw):
nonlocal response
response = self # Avoid garbage collector closing the socket
client.HTTPResponse.__init__(self, *pos, **kw)
conn.response_class = Response
conn.sock = FakeSocket('') # Emulate server dropping connection
conn.request('GET', '/')
self.assertRaises(client.BadStatusLine, conn.getresponse)
self.assertTrue(response.closed)
self.assertTrue(conn.sock.file_closed)
class OfflineTest(TestCase): class OfflineTest(TestCase):
def test_responses(self): def test_responses(self):
self.assertEqual(client.responses[client.NOT_FOUND], "Not Found") self.assertEqual(client.responses[client.NOT_FOUND], "Not Found")

View File

@ -36,6 +36,9 @@ Core and Builtins
Library Library
------- -------
- Issue #21032. Fixed socket leak if HTTPConnection.getresponse() fails.
Original patch by Martin Panter.
- Issue #22960: Add a context argument to xmlrpclib.ServerProxy constructor. - Issue #22960: Add a context argument to xmlrpclib.ServerProxy constructor.
- Issue #22915: SAX parser now supports files opened with file descriptor or - Issue #22915: SAX parser now supports files opened with file descriptor or