bufferedhttp: Tolerate socket being None
Seen in prod: Traceback (most recent call last): File ".../swift/proxy/controllers/obj.py", line 416, in _get_conn_response self.app.node_timeout, not final_phase) File ".../swift/proxy/controllers/obj.py", line 1645, in await_response self.resp = self.conn.getexpect() File ".../swift/common/bufferedhttp.py", line 229, in getexpect response = BufferedHTTPResponse(self.sock, **kwargs) File ".../swift/common/bufferedhttp.py", line 64, in __init__ self._real_socket = sock.fd._sock AttributeError: 'NoneType' object has no attribute 'fd' Change-Id: I35b3660f954bcf91aee2698b2c9cc10d5447abc1 Co-Authored-By: Clay Gerrard <clay.gerrard@gmail.com>
This commit is contained in:
parent
977f1bcc61
commit
50ba8e6975
@ -33,7 +33,7 @@ import socket
|
|||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
from eventlet.green.httplib import CONTINUE, HTTPConnection, HTTPMessage, \
|
from eventlet.green.httplib import CONTINUE, HTTPConnection, HTTPMessage, \
|
||||||
HTTPResponse, HTTPSConnection, _UNKNOWN
|
HTTPResponse, HTTPSConnection, _UNKNOWN, ImproperConnectionState
|
||||||
from six.moves.urllib.parse import quote, parse_qsl, urlencode
|
from six.moves.urllib.parse import quote, parse_qsl, urlencode
|
||||||
import six
|
import six
|
||||||
|
|
||||||
@ -56,16 +56,24 @@ class BufferedHTTPResponse(HTTPResponse):
|
|||||||
|
|
||||||
def __init__(self, sock, debuglevel=0, strict=0,
|
def __init__(self, sock, debuglevel=0, strict=0,
|
||||||
method=None): # pragma: no cover
|
method=None): # pragma: no cover
|
||||||
|
# sock should be an eventlet.greenio.GreenSocket
|
||||||
self.sock = sock
|
self.sock = sock
|
||||||
# sock is an eventlet.greenio.GreenSocket
|
if sock is None:
|
||||||
if six.PY2:
|
# ...but it could be None if we close the connection as we're
|
||||||
|
# getting it wrapped up in a Response
|
||||||
|
self._real_socket = None
|
||||||
|
# No socket means no file-like -- set it to None like in
|
||||||
|
# HTTPResponse.close()
|
||||||
|
self.fp = None
|
||||||
|
elif six.PY2:
|
||||||
# sock.fd is a socket._socketobject
|
# sock.fd is a socket._socketobject
|
||||||
# sock.fd._sock is a _socket.socket object, which is what we want.
|
# sock.fd._sock is a _socket.socket object, which is what we want.
|
||||||
self._real_socket = sock.fd._sock
|
self._real_socket = sock.fd._sock
|
||||||
|
self.fp = sock.makefile('rb')
|
||||||
else:
|
else:
|
||||||
# sock.fd is a socket.socket, which should have a _real_close
|
# sock.fd is a socket.socket, which should have a _real_close
|
||||||
self._real_socket = sock.fd
|
self._real_socket = sock.fd
|
||||||
self.fp = sock.makefile('rb')
|
self.fp = sock.makefile('rb')
|
||||||
self.debuglevel = debuglevel
|
self.debuglevel = debuglevel
|
||||||
self.strict = strict
|
self.strict = strict
|
||||||
self._method = method
|
self._method = method
|
||||||
@ -106,6 +114,8 @@ class BufferedHTTPResponse(HTTPResponse):
|
|||||||
if self.fp:
|
if self.fp:
|
||||||
self.fp.close()
|
self.fp.close()
|
||||||
self.fp = None
|
self.fp = None
|
||||||
|
if not self.sock:
|
||||||
|
raise ImproperConnectionState('Socket already closed')
|
||||||
self.fp = self.sock.makefile('rb', 0)
|
self.fp = self.sock.makefile('rb', 0)
|
||||||
version, status, reason = self._read_status()
|
version, status, reason = self._read_status()
|
||||||
if status != CONTINUE:
|
if status != CONTINUE:
|
||||||
|
@ -99,6 +99,50 @@ class TestBufferedHTTP(unittest.TestCase):
|
|||||||
if err:
|
if err:
|
||||||
raise Exception(err)
|
raise Exception(err)
|
||||||
|
|
||||||
|
def test_get_expect(self):
|
||||||
|
bindsock = listen_zero()
|
||||||
|
request = []
|
||||||
|
|
||||||
|
def accept():
|
||||||
|
with Timeout(3):
|
||||||
|
sock, addr = bindsock.accept()
|
||||||
|
fp = sock.makefile('rwb')
|
||||||
|
request.append(fp.readline())
|
||||||
|
fp.write(b'HTTP/1.1 100 Continue\r\n\r\n')
|
||||||
|
fp.flush()
|
||||||
|
fp.write(b'HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\n'
|
||||||
|
b'RESPONSE')
|
||||||
|
fp.flush()
|
||||||
|
|
||||||
|
server = spawn(accept)
|
||||||
|
try:
|
||||||
|
address = '%s:%s' % ('127.0.0.1', bindsock.getsockname()[1])
|
||||||
|
conn = bufferedhttp.BufferedHTTPConnection(address)
|
||||||
|
conn.putrequest('GET', '/path')
|
||||||
|
conn.endheaders()
|
||||||
|
resp = conn.getexpect()
|
||||||
|
self.assertIsInstance(resp, bufferedhttp.BufferedHTTPResponse)
|
||||||
|
self.assertEqual(resp.status, 100)
|
||||||
|
self.assertEqual(resp.version, 11)
|
||||||
|
self.assertEqual(resp.reason, 'Continue')
|
||||||
|
# I don't think you're supposed to "read" a continue response
|
||||||
|
self.assertRaises(AssertionError, resp.read)
|
||||||
|
|
||||||
|
resp = conn.getresponse()
|
||||||
|
self.assertIsInstance(resp, bufferedhttp.BufferedHTTPResponse)
|
||||||
|
self.assertEqual(resp.read(), b'RESPONSE')
|
||||||
|
|
||||||
|
finally:
|
||||||
|
server.wait()
|
||||||
|
self.assertEqual(request[0], b'GET /path HTTP/1.1\r\n')
|
||||||
|
|
||||||
|
def test_closed_response(self):
|
||||||
|
resp = bufferedhttp.BufferedHTTPResponse(None)
|
||||||
|
self.assertEqual(resp.status, 'UNKNOWN')
|
||||||
|
self.assertEqual(resp.version, 'UNKNOWN')
|
||||||
|
self.assertEqual(resp.reason, 'UNKNOWN')
|
||||||
|
self.assertEqual(resp.read(), b'')
|
||||||
|
|
||||||
def test_nonstr_header_values(self):
|
def test_nonstr_header_values(self):
|
||||||
with mock.patch('swift.common.bufferedhttp.HTTPSConnection',
|
with mock.patch('swift.common.bufferedhttp.HTTPSConnection',
|
||||||
MockHTTPSConnection):
|
MockHTTPSConnection):
|
||||||
|
Loading…
Reference in New Issue
Block a user