diff options
author | Andrey Petrov <andrey.petrov@shazow.net> | 2014-06-25 15:12:19 -0700 |
---|---|---|
committer | Andrey Petrov <andrey.petrov@shazow.net> | 2014-06-25 15:12:19 -0700 |
commit | 24d60ad23cd03d05aa9faa9f91c735fd1dc8b720 (patch) | |
tree | 55d3647eee0b4037c76a13060674ee479800b2b5 | |
parent | 9b4670c861a2a78ae7feb5bd6a1794ab63857069 (diff) | |
download | urllib3-24d60ad23cd03d05aa9faa9f91c735fd1dc8b720.tar.gz |
Handle SSL read timeouts as ReadTimeoutError.
-rw-r--r-- | .coveragerc | 1 | ||||
-rw-r--r-- | test/with_dummyserver/test_socketlevel.py | 61 | ||||
-rw-r--r-- | urllib3/response.py | 11 |
3 files changed, 62 insertions, 11 deletions
diff --git a/.coveragerc b/.coveragerc index 17546433..b106d41d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -11,3 +11,4 @@ exclude_lines = pass .* # Abstract import + raise diff --git a/test/with_dummyserver/test_socketlevel.py b/test/with_dummyserver/test_socketlevel.py index f13d77cc..1273a3e7 100644 --- a/test/with_dummyserver/test_socketlevel.py +++ b/test/with_dummyserver/test_socketlevel.py @@ -125,9 +125,10 @@ class TestSocketClosing(SocketDummyServerTestCase): self._start_server(socket_handler) pool = HTTPConnectionPool(self.host, self.port, timeout=0.001) - self.assertRaises(ReadTimeoutError, pool.request, 'GET', '/', retries=0) - - timed_out.set() + try: + self.assertRaises(ReadTimeoutError, pool.request, 'GET', '/', retries=0) + finally: + timed_out.set() def test_timeout_errors_cause_retries(self): def socket_handler(listener): @@ -193,8 +194,10 @@ class TestSocketClosing(SocketDummyServerTestCase): response = pool.urlopen('GET', '/', retries=0, preload_content=False, timeout=util.Timeout(connect=1, read=0.001)) - self.assertRaises(ReadTimeoutError, response.read) - timed_out.set() + try: + self.assertRaises(ReadTimeoutError, response.read) + finally: + timed_out.set() def test_incomplete_response(self): body = 'Response' @@ -400,11 +403,12 @@ class TestSSL(SocketDummyServerTestCase): buf += ssl_sock.recv(65536) # Deliberately send from the non-SSL socket. - sock2.send(('HTTP/1.1 200 OK\r\n' - 'Content-Type: text/plain\r\n' - 'Content-Length: 2\r\n' - '\r\n' - 'Hi').encode('utf-8')) + sock2.send(( + 'HTTP/1.1 200 OK\r\n' + 'Content-Type: text/plain\r\n' + 'Content-Length: 2\r\n' + '\r\n' + 'Hi').encode('utf-8')) sock2.close() ssl_sock.close() @@ -412,3 +416,40 @@ class TestSSL(SocketDummyServerTestCase): pool = HTTPSConnectionPool(self.host, self.port) self.assertRaises(SSLError, pool.request, 'GET', '/', retries=0) + + def test_ssl_read_timeout(self): + timed_out = Event() + + def socket_handler(listener): + sock = listener.accept()[0] + ssl_sock = ssl.wrap_socket(sock, + server_side=True, + keyfile=DEFAULT_CERTS['keyfile'], + certfile=DEFAULT_CERTS['certfile'], + ca_certs=DEFAULT_CA) + + buf = b'' + while not buf.endswith(b'\r\n\r\n'): + buf += ssl_sock.recv(65536) + + # Send incomplete message (note Content-Length) + ssl_sock.send(( + 'HTTP/1.1 200 OK\r\n' + 'Content-Type: text/plain\r\n' + 'Content-Length: 10\r\n' + '\r\n' + 'Hi-').encode('utf-8')) + timed_out.wait() + + sock.close() + ssl_sock.close() + + self._start_server(socket_handler) + pool = HTTPSConnectionPool(self.host, self.port) + + response = pool.urlopen('GET', '/', retries=0, preload_content=False, + timeout=util.Timeout(connect=1, read=0.001)) + try: + self.assertRaises(ReadTimeoutError, response.read) + finally: + timed_out.set() diff --git a/urllib3/response.py b/urllib3/response.py index 82d55c96..f1f86cba 100644 --- a/urllib3/response.py +++ b/urllib3/response.py @@ -13,7 +13,7 @@ from ._collections import HTTPHeaderDict from .exceptions import ConnectionError, DecodeError, ReadTimeoutError from .packages.six import string_types as basestring, binary_type from .util import is_fp_closed -from .connection import HTTPException +from .connection import HTTPException, BaseSSLError class DeflateDecoder(object): @@ -203,6 +203,15 @@ class HTTPResponse(io.IOBase): # there is yet no clean way to get at it from this context. raise ReadTimeoutError(self._pool, None, 'Read timed out.') + except BaseSSLError as e: + # FIXME: Is there a better way to differentiate between SSLErrors? + if not 'read operation timed out' in e.message: + # This shouldn't happen but just in case we're missing an edge + # case, let's avoid swallowing SSL errors. + raise + + raise ReadTimeoutError(self._pool, None, 'Read timed out.') + except HTTPException as e: # This includes IncompleteRead. raise ConnectionError('Connection failed: %r' % e, e) |