Change Request timeout process

This add a request timeout exception.
It cancels task, when request is timeout.
This commit is contained in:
38elements 2016-11-26 13:55:45 +09:00
parent fc19f2ea34
commit c01cbb3a8c
5 changed files with 88 additions and 6 deletions

View File

@ -0,0 +1,21 @@
from sanic import Sanic
import asyncio
from sanic.response import text
from sanic.config import Config
from sanic.exceptions import RequestTimeout
Config.REQUEST_TIMEOUT = 1
app = Sanic(__name__)
@app.route("/")
async def test(request):
await asyncio.sleep(3)
return text('Hello, world!')
@app.exception(RequestTimeout)
def timeout(request, exception):
return text('RequestTimeout from error_handler.')
app.run(host="0.0.0.0", port=8000)

View File

@ -30,6 +30,10 @@ class FileNotFound(NotFound):
self.relative_url = relative_url self.relative_url = relative_url
class RequestTimeout(SanicException):
status_code = 408
class Handler: class Handler:
handlers = None handlers = None

View File

@ -250,6 +250,7 @@ class Sanic:
'sock': sock, 'sock': sock,
'debug': debug, 'debug': debug,
'request_handler': self.handle_request, 'request_handler': self.handle_request,
'error_handler': self.error_handler,
'request_timeout': self.config.REQUEST_TIMEOUT, 'request_timeout': self.config.REQUEST_TIMEOUT,
'request_max_size': self.config.REQUEST_MAX_SIZE, 'request_max_size': self.config.REQUEST_MAX_SIZE,
'loop': loop 'loop': loop

View File

@ -13,6 +13,8 @@ except ImportError:
from .log import log from .log import log
from .request import Request from .request import Request
from .response import HTTPResponse
from .exceptions import RequestTimeout
class Signal: class Signal:
@ -33,8 +35,8 @@ class HttpProtocol(asyncio.Protocol):
# connection management # connection management
'_total_request_size', '_timeout_handler', '_last_communication_time') '_total_request_size', '_timeout_handler', '_last_communication_time')
def __init__(self, *, loop, request_handler, signal=Signal(), def __init__(self, *, loop, request_handler, error_handler,
connections={}, request_timeout=60, signal=Signal(), connections={}, request_timeout=60,
request_max_size=None): request_max_size=None):
self.loop = loop self.loop = loop
self.transport = None self.transport = None
@ -45,11 +47,13 @@ class HttpProtocol(asyncio.Protocol):
self.signal = signal self.signal = signal
self.connections = connections self.connections = connections
self.request_handler = request_handler self.request_handler = request_handler
self.error_handler = error_handler
self.request_timeout = request_timeout self.request_timeout = request_timeout
self.request_max_size = request_max_size self.request_max_size = request_max_size
self._total_request_size = 0 self._total_request_size = 0
self._timeout_handler = None self._timeout_handler = None
self._last_request_time = None self._last_request_time = None
self._request_handler_task = None
# -------------------------------------------- # # -------------------------------------------- #
# Connection # Connection
@ -75,7 +79,17 @@ class HttpProtocol(asyncio.Protocol):
self._timeout_handler = \ self._timeout_handler = \
self.loop.call_later(time_left, self.connection_timeout) self.loop.call_later(time_left, self.connection_timeout)
else: else:
self.bail_out("Request timed out, connection closed") self._request_handler_task.cancel()
try:
response = self.error_handler.response(
self.request, RequestTimeout('Request Timeout'))
except Exception as e:
response = HTTPResponse(
'Request Timeout', RequestTimeout.status_code)
self.transport.write(
response.output(
self.request.version, False, self.request_timeout))
self.transport.close()
# -------------------------------------------- # # -------------------------------------------- #
# Parsing # Parsing
@ -132,7 +146,7 @@ class HttpProtocol(asyncio.Protocol):
self.request.body = body self.request.body = body
def on_message_complete(self): def on_message_complete(self):
self.loop.create_task( self._request_handler_task = self.loop.create_task(
self.request_handler(self.request, self.write_response)) self.request_handler(self.request, self.write_response))
# -------------------------------------------- # # -------------------------------------------- #
@ -165,6 +179,7 @@ class HttpProtocol(asyncio.Protocol):
self.request = None self.request = None
self.url = None self.url = None
self.headers = None self.headers = None
self._request_handler_task = None
self._total_request_size = 0 self._total_request_size = 0
def close_if_idle(self): def close_if_idle(self):
@ -204,8 +219,8 @@ def trigger_events(events, loop):
loop.run_until_complete(result) loop.run_until_complete(result)
def serve(host, port, request_handler, before_start=None, after_start=None, def serve(host, port, request_handler, error_handler, before_start=None,
before_stop=None, after_stop=None, after_start=None, before_stop=None, after_stop=None,
debug=False, request_timeout=60, sock=None, debug=False, request_timeout=60, sock=None,
request_max_size=None, reuse_port=False, loop=None): request_max_size=None, reuse_port=False, loop=None):
""" """
@ -240,6 +255,7 @@ def serve(host, port, request_handler, before_start=None, after_start=None,
connections=connections, connections=connections,
signal=signal, signal=signal,
request_handler=request_handler, request_handler=request_handler,
error_handler=error_handler,
request_timeout=request_timeout, request_timeout=request_timeout,
request_max_size=request_max_size, request_max_size=request_max_size,
), host, port, reuse_port=reuse_port, sock=sock) ), host, port, reuse_port=reuse_port, sock=sock)

View File

@ -0,0 +1,40 @@
from sanic import Sanic
import asyncio
from sanic.response import text
from sanic.exceptions import RequestTimeout
from sanic.utils import sanic_endpoint_test
from sanic.config import Config
Config.REQUEST_TIMEOUT = 1
request_timeout_app = Sanic('test_request_timeout')
request_timeout_default_app = Sanic('test_request_timeout_default')
@request_timeout_app.route('/1')
async def handler_1(request):
await asyncio.sleep(2)
return text('OK')
@request_timeout_app.exception(RequestTimeout)
def handler_exception(request, exception):
return text('Request Timeout from error_handler.', 408)
def test_server_error_request_timeout():
request, response = sanic_endpoint_test(request_timeout_app, uri='/1')
assert response.status == 408
assert response.text == 'Request Timeout from error_handler.'
@request_timeout_default_app.route('/1')
async def handler_2(request):
await asyncio.sleep(2)
return text('OK')
def test_default_server_error_request_timeout():
request, response = sanic_endpoint_test(
request_timeout_default_app, uri='/1')
assert response.status == 408
assert response.text == 'Error: Request Timeout'