Fixed error where the RequestTimeout test wasn't actually testing the correct behaviour

Fixed error where KeepAliveTimeout wasn't being triggered in the test suite, when using uvloop
Fixed test cases when using other asyncio loops such as uvloop
Fixed Flake8 linting errors
This commit is contained in:
Ashley Sommer 2017-09-13 10:18:36 +10:00
parent 173f94216a
commit 8eb59ad4dc
4 changed files with 95 additions and 23 deletions

View File

@ -129,7 +129,7 @@ class Config(dict):
self.KEEP_ALIVE = keep_alive self.KEEP_ALIVE = keep_alive
# Apache httpd server default keepalive timeout = 5 seconds # Apache httpd server default keepalive timeout = 5 seconds
# Nginx server default keepalive timeout = 75 seconds # Nginx server default keepalive timeout = 75 seconds
# Nginx performance tuning guidelines uses keepalive timeout = 15 seconds # Nginx performance tuning guidelines uses keepalive = 15 seconds
# IE client hard keepalive limit = 60 seconds # IE client hard keepalive limit = 60 seconds
# Firefox client hard keepalive limit = 115 seconds # Firefox client hard keepalive limit = 115 seconds

View File

@ -193,7 +193,6 @@ class HttpProtocol(asyncio.Protocol):
log.info('KeepAlive Timeout. Closing connection.') log.info('KeepAlive Timeout. Closing connection.')
self.transport.close() self.transport.close()
# -------------------------------------------- # # -------------------------------------------- #
# Parsing # Parsing
# -------------------------------------------- # # -------------------------------------------- #

View File

@ -20,10 +20,11 @@ class ReuseableTCPConnector(TCPConnector):
new_conn = yield from super(ReuseableTCPConnector, self)\ new_conn = yield from super(ReuseableTCPConnector, self)\
.connect(req) .connect(req)
if self.old_proto is not None: if self.old_proto is not None:
if self.old_proto != new_conn.protocol: if self.old_proto != new_conn._protocol:
raise RuntimeError( raise RuntimeError(
"We got a new connection, wanted the same one!") "We got a new connection, wanted the same one!")
self.old_proto = new_conn.protocol print(new_conn.__dict__)
self.old_proto = new_conn._protocol
return new_conn return new_conn
@ -64,6 +65,8 @@ class ReuseableSanicTestClient(SanicTestClient):
**request_kwargs) **request_kwargs)
results[-1] = response results[-1] = response
except Exception as e2: except Exception as e2:
import traceback
traceback.print_tb(e2.__traceback__)
exceptions.append(e2) exceptions.append(e2)
#Don't stop here! self.app.stop() #Don't stop here! self.app.stop()
@ -80,6 +83,8 @@ class ReuseableSanicTestClient(SanicTestClient):
loop._stopping = False loop._stopping = False
http_server = loop.run_until_complete(_server_co) http_server = loop.run_until_complete(_server_co)
except Exception as e1: except Exception as e1:
import traceback
traceback.print_tb(e1.__traceback__)
raise e1 raise e1
self._server = _server = http_server self._server = _server = http_server
server.trigger_events( server.trigger_events(
@ -93,6 +98,8 @@ class ReuseableSanicTestClient(SanicTestClient):
loop.run_until_complete(_server.wait_closed()) loop.run_until_complete(_server.wait_closed())
self.app.stop() self.app.stop()
except Exception as e3: except Exception as e3:
import traceback
traceback.print_tb(e3.__traceback__)
exceptions.append(e3) exceptions.append(e3)
if exceptions: if exceptions:
raise ValueError( raise ValueError(

View File

@ -1,8 +1,8 @@
from json import JSONDecodeError from json import JSONDecodeError
from sanic import Sanic from sanic import Sanic
import asyncio import asyncio
from sanic.response import text from sanic.response import text
from sanic.exceptions import RequestTimeout
from sanic.config import Config from sanic.config import Config
import aiohttp import aiohttp
from aiohttp import TCPConnector from aiohttp import TCPConnector
@ -10,21 +10,68 @@ from sanic.testing import SanicTestClient, HOST, PORT
class DelayableTCPConnector(TCPConnector): class DelayableTCPConnector(TCPConnector):
class DelayableHttpRequest(object):
class RequestContextManager(object):
def __new__(cls, req, delay): def __new__(cls, req, delay):
cls = super(DelayableTCPConnector.DelayableHttpRequest, cls).\ cls = super(DelayableTCPConnector.RequestContextManager, cls).\
__new__(cls) __new__(cls)
cls.req = req cls.req = req
cls.send_task = None
cls.resp = None
cls.orig_send = getattr(req, 'send')
cls.orig_start = None
cls.delay = delay cls.delay = delay
cls._acting_as = req
return cls return cls
def __getattr__(self, item): def __getattr__(self, item):
return getattr(self.req, item) acting_as = self._acting_as
return getattr(acting_as, item)
@asyncio.coroutine
def start(self, connection, read_until_eof=False):
if self.send_task is None:
raise RuntimeError("do a send() before you do a start()")
resp = yield from self.send_task
self.send_task = None
self.resp = resp
self._acting_as = self.resp
self.orig_start = getattr(resp, 'start')
try:
ret = yield from self.orig_start(connection,
read_until_eof)
except Exception as e:
raise e
return ret
def close(self):
if self.resp is not None:
self.resp.close()
if self.send_task is not None:
self.send_task.cancel()
@asyncio.coroutine
def delayed_send(self, *args, **kwargs):
req = self.req
if self.delay and self.delay > 0:
#sync_sleep(self.delay)
_ = yield from asyncio.sleep(self.delay)
t = req.loop.time()
print("sending at {}".format(t), flush=True)
conn = next(iter(args)) # first arg is connection
try:
delayed_resp = self.orig_send(*args, **kwargs)
except Exception as e:
return aiohttp.ClientResponse(req.method, req.url)
return delayed_resp
def send(self, *args, **kwargs): def send(self, *args, **kwargs):
if self.delay and self.delay > 0: gen = self.delayed_send(*args, **kwargs)
_ = yield from asyncio.sleep(self.delay) task = self.req.loop.create_task(gen)
self.req.send(*args, **kwargs) self.send_task = task
self._acting_as = task
return self
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
_post_connect_delay = kwargs.pop('post_connect_delay', 0) _post_connect_delay = kwargs.pop('post_connect_delay', 0)
@ -35,31 +82,37 @@ class DelayableTCPConnector(TCPConnector):
@asyncio.coroutine @asyncio.coroutine
def connect(self, req): def connect(self, req):
req = DelayableTCPConnector.\ d_req = DelayableTCPConnector.\
DelayableHttpRequest(req, self._pre_request_delay) RequestContextManager(req, self._pre_request_delay)
conn = yield from super(DelayableTCPConnector, self).connect(req) conn = yield from super(DelayableTCPConnector, self).connect(req)
if self._post_connect_delay and self._post_connect_delay > 0: if self._post_connect_delay and self._post_connect_delay > 0:
_ = yield from asyncio.sleep(self._post_connect_delay) _ = yield from asyncio.sleep(self._post_connect_delay,
loop=self._loop)
req.send = d_req.send
t = req.loop.time()
print("Connected at {}".format(t), flush=True)
return conn return conn
class DelayableSanicTestClient(SanicTestClient): class DelayableSanicTestClient(SanicTestClient):
def __init__(self, app, request_delay=1): def __init__(self, app, loop, request_delay=1):
super(DelayableSanicTestClient, self).__init__(app) super(DelayableSanicTestClient, self).__init__(app)
self._request_delay = request_delay self._request_delay = request_delay
self._loop = None
async def _local_request(self, method, uri, cookies=None, *args, async def _local_request(self, method, uri, cookies=None, *args,
**kwargs): **kwargs):
if self._loop is None:
self._loop = asyncio.get_event_loop()
if uri.startswith(('http:', 'https:', 'ftp:', 'ftps://' '//')): if uri.startswith(('http:', 'https:', 'ftp:', 'ftps://' '//')):
url = uri url = uri
else: else:
url = 'http://{host}:{port}{uri}'.format( url = 'http://{host}:{port}{uri}'.format(
host=HOST, port=PORT, uri=uri) host=HOST, port=PORT, uri=uri)
conn = DelayableTCPConnector(pre_request_delay=self._request_delay, conn = DelayableTCPConnector(pre_request_delay=self._request_delay,
verify_ssl=False) verify_ssl=False, loop=self._loop)
async with aiohttp.ClientSession( async with aiohttp.ClientSession(cookies=cookies, connector=conn,
cookies=cookies, connector=conn) as session: loop=self._loop) as session:
# Insert a delay after creating the connection # Insert a delay after creating the connection
# But before sending the request. # But before sending the request.
@ -81,17 +134,30 @@ class DelayableSanicTestClient(SanicTestClient):
return response return response
Config.REQUEST_TIMEOUT = 1 Config.REQUEST_TIMEOUT = 2
request_timeout_default_app = Sanic('test_request_timeout_default') request_timeout_default_app = Sanic('test_request_timeout_default')
request_no_timeout_app = Sanic('test_request_no_timeout')
@request_timeout_default_app.route('/1') @request_timeout_default_app.route('/1')
async def handler(request): async def handler1(request):
return text('OK')
@request_no_timeout_app.route('/1')
async def handler2(request):
return text('OK') return text('OK')
def test_default_server_error_request_timeout(): def test_default_server_error_request_timeout():
client = DelayableSanicTestClient(request_timeout_default_app, 2) client = DelayableSanicTestClient(request_timeout_default_app, None, 3)
request, response = client.get('/1') request, response = client.get('/1')
assert response.status == 408 assert response.status == 408
assert response.text == 'Error: Request Timeout' assert response.text == 'Error: Request Timeout'
def test_default_server_error_request_dont_timeout():
client = DelayableSanicTestClient(request_no_timeout_app, None, 1)
request, response = client.get('/1')
assert response.status == 200
assert response.text == 'OK'