finished the keepalive_timeout tests

This commit is contained in:
Ashley Sommer 2017-09-12 13:09:42 +10:00
parent 2979e03148
commit 1a74accd65

View File

@ -4,6 +4,7 @@ from time import sleep as sync_sleep
import asyncio import asyncio
from sanic.response import text from sanic.response import text
from sanic.config import Config from sanic.config import Config
from sanic import server
import aiohttp import aiohttp
from aiohttp import TCPConnector from aiohttp import TCPConnector
from sanic.testing import SanicTestClient, HOST, PORT from sanic.testing import SanicTestClient, HOST, PORT
@ -12,33 +13,40 @@ from sanic.testing import SanicTestClient, HOST, PORT
class ReuseableTCPConnector(TCPConnector): class ReuseableTCPConnector(TCPConnector):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ReuseableTCPConnector, self).__init__(*args, **kwargs) super(ReuseableTCPConnector, self).__init__(*args, **kwargs)
self.conn = None self.old_proto = None
@asyncio.coroutine @asyncio.coroutine
def connect(self, req): def connect(self, req):
if self.conn: new_conn = yield from super(ReuseableTCPConnector, self)\
return self.conn .connect(req)
conn = yield from super(ReuseableTCPConnector, self).connect(req) if self.old_proto is not None:
self.conn = conn if self.old_proto != new_conn.protocol:
return conn raise RuntimeError(
"We got a new connection, wanted the same one!")
def close(self): self.old_proto = new_conn.protocol
return super(ReuseableTCPConnector, self).close() return new_conn
class ReuseableSanicTestClient(SanicTestClient): class ReuseableSanicTestClient(SanicTestClient):
def __init__(self, app): def __init__(self, app, loop=None):
super(ReuseableSanicTestClient, self).__init__(app) super(ReuseableSanicTestClient, self).__init__(app)
if loop is None:
loop = asyncio.get_event_loop()
self._loop = loop
self._server = None
self._tcp_connector = None self._tcp_connector = None
self._session = None self._session = None
# Copied from SanicTestClient, but with some changes to reuse the
# same loop for the same app.
def _sanic_endpoint_test( def _sanic_endpoint_test(
self, method='get', uri='/', gather_request=True, self, method='get', uri='/', gather_request=True,
debug=False, server_kwargs={}, debug=False, server_kwargs={},
*request_args, **request_kwargs): *request_args, **request_kwargs):
loop = self._loop
results = [None, None] results = [None, None]
exceptions = [] exceptions = []
do_kill_server = request_kwargs.pop('end_server', False)
if gather_request: if gather_request:
def _collect_request(request): def _collect_request(request):
if results[0] is None: if results[0] is None:
@ -47,26 +55,53 @@ class ReuseableSanicTestClient(SanicTestClient):
self.app.request_middleware.appendleft(_collect_request) self.app.request_middleware.appendleft(_collect_request)
@self.app.listener('after_server_start') @self.app.listener('after_server_start')
async def _collect_response(sanic, loop): async def _collect_response(loop):
try: try:
if do_kill_server:
request_kwargs['end_session'] = True
response = await self._local_request( response = await self._local_request(
method, uri, *request_args, method, uri, *request_args,
**request_kwargs) **request_kwargs)
results[-1] = response results[-1] = response
except Exception as e: except Exception as e:
log.error( import traceback
'Exception:\n{}'.format(traceback.format_exc())) traceback.print_tb(e.__traceback__)
exceptions.append(e) exceptions.append(e)
self.app.stop() #Don't stop here! self.app.stop()
server = self.app.create_server(host=HOST, debug=debug, port=PORT, **server_kwargs) if self._server is not None:
_server = self._server
else:
_server_co = self.app.create_server(host=HOST, debug=debug,
port=PORT, **server_kwargs)
server.trigger_events(
self.app.listeners['before_server_start'], loop)
try:
loop._stopping = False
http_server = loop.run_until_complete(_server_co)
except Exception as e:
raise e
self._server = _server = http_server
server.trigger_events(
self.app.listeners['after_server_start'], loop)
self.app.listeners['after_server_start'].pop() self.app.listeners['after_server_start'].pop()
if do_kill_server:
try:
_server.close()
self._server = None
loop.run_until_complete(_server.wait_closed())
self.app.stop()
except Exception as e:
exceptions.append(e)
if exceptions: if exceptions:
raise ValueError( raise ValueError(
"Exception during request: {}".format(exceptions)) "Exception during request: {}".format(exceptions))
if gather_request: if gather_request:
self.app.request_middleware.pop()
try: try:
request, response = results request, response = results
return request, response return request, response
@ -81,20 +116,29 @@ class ReuseableSanicTestClient(SanicTestClient):
raise ValueError( raise ValueError(
"Request object expected, got ({})".format(results)) "Request object expected, got ({})".format(results))
# Copied from SanicTestClient, but with some changes to reuse the
# same TCPConnection and the sane ClientSession more than once.
# Note, you cannot use the same session if you are in a _different_
# loop, so the changes above are required too.
async def _local_request(self, method, uri, cookies=None, *args, async def _local_request(self, method, uri, cookies=None, *args,
**kwargs): **kwargs):
request_keepalive = kwargs.pop('request_keepalive',
Config.KEEP_ALIVE_TIMEOUT)
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)
do_kill_session = kwargs.pop('end_session', False)
if self._session: if self._session:
session = self._session session = self._session
else: else:
if self._tcp_connector: if self._tcp_connector:
conn = self._tcp_connector conn = self._tcp_connector
else: else:
conn = ReuseableTCPConnector(verify_ssl=False) conn = ReuseableTCPConnector(verify_ssl=False,
keepalive_timeout=
request_keepalive)
self._tcp_connector = conn self._tcp_connector = conn
session = aiohttp.ClientSession(cookies=cookies, session = aiohttp.ClientSession(cookies=cookies,
connector=conn) connector=conn)
@ -115,28 +159,96 @@ class ReuseableSanicTestClient(SanicTestClient):
response.json = None response.json = None
response.body = await response.read() response.body = await response.read()
if do_kill_session:
session.close()
self._session = None
return response return response
Config.KEEP_ALIVE_TIMEOUT = 30 Config.KEEP_ALIVE_TIMEOUT = 2
Config.KEEP_ALIVE = True Config.KEEP_ALIVE = True
keep_alive_timeout_app = Sanic('test_request_timeout') keep_alive_timeout_app_reuse = Sanic('test_ka_timeout_reuse')
keep_alive_app_client_timeout = Sanic('test_ka_client_timeout')
keep_alive_app_server_timeout = Sanic('test_ka_server_timeout')
@keep_alive_timeout_app.route('/1') @keep_alive_timeout_app_reuse.route('/1')
async def handler(request): async def handler1(request):
return text('OK') return text('OK')
def test_keep_alive_timeout(): @keep_alive_app_client_timeout.route('/1')
client = ReuseableSanicTestClient(keep_alive_timeout_app) async def handler2(request):
return text('OK')
@keep_alive_app_server_timeout.route('/1')
async def handler3(request):
return text('OK')
def test_keep_alive_timeout_reuse():
"""If the server keep-alive timeout and client keep-alive timeout are
both longer than the delay, the client _and_ server will successfully
reuse the existing connection."""
loop = asyncio.get_event_loop()
client = ReuseableSanicTestClient(keep_alive_timeout_app_reuse, loop)
headers = { headers = {
'Connection': 'keep-alive' 'Connection': 'keep-alive'
} }
request, response = client.get('/1', headers=headers) request, response = client.get('/1', headers=headers)
assert response.status == 200 assert response.status == 200
#sync_sleep(2) assert response.text == 'OK'
request, response = client.get('/1') sync_sleep(1)
request, response = client.get('/1', end_server=True)
assert response.status == 200 assert response.status == 200
assert response.text == 'OK'
def test_keep_alive_client_timeout():
"""If the server keep-alive timeout is longer than the client
keep-alive timeout, client will try to create a new connection here."""
loop = asyncio.get_event_loop()
client = ReuseableSanicTestClient(keep_alive_app_client_timeout,
loop)
headers = {
'Connection': 'keep-alive'
}
request, response = client.get('/1', headers=headers,
request_keepalive=1)
assert response.status == 200
assert response.text == 'OK'
sync_sleep(3)
exception = None
try:
request, response = client.get('/1', end_server=True)
except ValueError as e:
exception = e
assert exception is not None
assert isinstance(exception, ValueError)
assert "got a new connection" in exception.args[0]
def test_keep_alive_server_timeout():
"""If the client keep-alive timeout is longer than the server
keep-alive timeout, the client will get a 'Connection reset' error."""
loop = asyncio.get_event_loop()
client = ReuseableSanicTestClient(keep_alive_app_server_timeout,
loop)
headers = {
'Connection': 'keep-alive'
}
request, response = client.get('/1', headers=headers,
request_keepalive=5)
assert response.status == 200
assert response.text == 'OK'
sync_sleep(3)
exception = None
try:
request, response = client.get('/1', end_server=True)
except ValueError as e:
exception = e
assert exception is not None
assert isinstance(exception, ValueError)
assert "Connection reset" in exception.args[0]