Skip to content

Commit

Permalink
Fixed error where the RequestTimeout test wasn't actually testing the…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
ashleysommer committed Sep 13, 2017
1 parent 173f942 commit 8eb59ad
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 23 deletions.
2 changes: 1 addition & 1 deletion sanic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def __init__(self, defaults=None, load_env=True, keep_alive=True):
self.KEEP_ALIVE = keep_alive
# Apache httpd server default keepalive timeout = 5 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
# Firefox client hard keepalive limit = 115 seconds

Expand Down
1 change: 0 additions & 1 deletion sanic/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ def keep_alive_timeout_callback(self):
log.info('KeepAlive Timeout. Closing connection.')
self.transport.close()


# -------------------------------------------- #
# Parsing
# -------------------------------------------- #
Expand Down
13 changes: 10 additions & 3 deletions tests/test_keep_alive_timeout.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ def connect(self, req):
new_conn = yield from super(ReuseableTCPConnector, self)\
.connect(req)
if self.old_proto is not None:
if self.old_proto != new_conn.protocol:
if self.old_proto != new_conn._protocol:
raise RuntimeError(
"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


Expand Down Expand Up @@ -64,6 +65,8 @@ async def _collect_response(loop):
**request_kwargs)
results[-1] = response
except Exception as e2:
import traceback
traceback.print_tb(e2.__traceback__)
exceptions.append(e2)
#Don't stop here! self.app.stop()

Expand All @@ -80,6 +83,8 @@ async def _collect_response(loop):
loop._stopping = False
http_server = loop.run_until_complete(_server_co)
except Exception as e1:
import traceback
traceback.print_tb(e1.__traceback__)
raise e1
self._server = _server = http_server
server.trigger_events(
Expand All @@ -93,7 +98,9 @@ async def _collect_response(loop):
loop.run_until_complete(_server.wait_closed())
self.app.stop()
except Exception as e3:
exceptions.append(e3)
import traceback
traceback.print_tb(e3.__traceback__)
exceptions.append(e3)
if exceptions:
raise ValueError(
"Exception during request: {}".format(exceptions))
Expand Down
102 changes: 84 additions & 18 deletions tests/test_request_timeout.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,77 @@
from json import JSONDecodeError

from sanic import Sanic
import asyncio
from sanic.response import text
from sanic.exceptions import RequestTimeout
from sanic.config import Config
import aiohttp
from aiohttp import TCPConnector
from sanic.testing import SanicTestClient, HOST, PORT


class DelayableTCPConnector(TCPConnector):
class DelayableHttpRequest(object):

class RequestContextManager(object):
def __new__(cls, req, delay):
cls = super(DelayableTCPConnector.DelayableHttpRequest, cls).\
cls = super(DelayableTCPConnector.RequestContextManager, cls).\
__new__(cls)
cls.req = req
cls.send_task = None
cls.resp = None
cls.orig_send = getattr(req, 'send')
cls.orig_start = None
cls.delay = delay
cls._acting_as = req
return cls

def __getattr__(self, item):
return getattr(self.req, item)

def send(self, *args, **kwargs):
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)
self.req.send(*args, **kwargs)
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):
gen = self.delayed_send(*args, **kwargs)
task = self.req.loop.create_task(gen)
self.send_task = task
self._acting_as = task
return self

def __init__(self, *args, **kwargs):
_post_connect_delay = kwargs.pop('post_connect_delay', 0)
Expand All @@ -35,31 +82,37 @@ def __init__(self, *args, **kwargs):

@asyncio.coroutine
def connect(self, req):
req = DelayableTCPConnector.\
DelayableHttpRequest(req, self._pre_request_delay)
d_req = DelayableTCPConnector.\
RequestContextManager(req, self._pre_request_delay)
conn = yield from super(DelayableTCPConnector, self).connect(req)
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


class DelayableSanicTestClient(SanicTestClient):
def __init__(self, app, request_delay=1):
def __init__(self, app, loop, request_delay=1):
super(DelayableSanicTestClient, self).__init__(app)
self._request_delay = request_delay
self._loop = None

async def _local_request(self, method, uri, cookies=None, *args,
**kwargs):
if self._loop is None:
self._loop = asyncio.get_event_loop()
if uri.startswith(('http:', 'https:', 'ftp:', 'ftps://' '//')):
url = uri
else:
url = 'http://{host}:{port}{uri}'.format(
host=HOST, port=PORT, uri=uri)

conn = DelayableTCPConnector(pre_request_delay=self._request_delay,
verify_ssl=False)
async with aiohttp.ClientSession(
cookies=cookies, connector=conn) as session:
verify_ssl=False, loop=self._loop)
async with aiohttp.ClientSession(cookies=cookies, connector=conn,
loop=self._loop) as session:
# Insert a delay after creating the connection
# But before sending the request.

Expand All @@ -81,17 +134,30 @@ async def _local_request(self, method, uri, cookies=None, *args,
return response


Config.REQUEST_TIMEOUT = 1
Config.REQUEST_TIMEOUT = 2
request_timeout_default_app = Sanic('test_request_timeout_default')
request_no_timeout_app = Sanic('test_request_no_timeout')


@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')


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')
assert response.status == 408
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'

0 comments on commit 8eb59ad

Please sign in to comment.