From c01cbb3a8c93e90fd22a3a58ec93804ee369a5dc Mon Sep 17 00:00:00 2001 From: 38elements Date: Sat, 26 Nov 2016 13:55:45 +0900 Subject: [PATCH 1/6] Change Request timeout process This add a request timeout exception. It cancels task, when request is timeout. --- examples/request_timeout.py | 21 ++++++++++++++++++ sanic/exceptions.py | 4 ++++ sanic/sanic.py | 1 + sanic/server.py | 28 ++++++++++++++++++------ tests/test_request_timeout.py | 40 +++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 examples/request_timeout.py create mode 100644 tests/test_request_timeout.py diff --git a/examples/request_timeout.py b/examples/request_timeout.py new file mode 100644 index 00000000..496864cd --- /dev/null +++ b/examples/request_timeout.py @@ -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) diff --git a/sanic/exceptions.py b/sanic/exceptions.py index e21aca63..bc052fbd 100644 --- a/sanic/exceptions.py +++ b/sanic/exceptions.py @@ -30,6 +30,10 @@ class FileNotFound(NotFound): self.relative_url = relative_url +class RequestTimeout(SanicException): + status_code = 408 + + class Handler: handlers = None diff --git a/sanic/sanic.py b/sanic/sanic.py index af284c00..128e3d28 100644 --- a/sanic/sanic.py +++ b/sanic/sanic.py @@ -250,6 +250,7 @@ class Sanic: 'sock': sock, 'debug': debug, 'request_handler': self.handle_request, + 'error_handler': self.error_handler, 'request_timeout': self.config.REQUEST_TIMEOUT, 'request_max_size': self.config.REQUEST_MAX_SIZE, 'loop': loop diff --git a/sanic/server.py b/sanic/server.py index b6233031..4b804353 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -13,6 +13,8 @@ except ImportError: from .log import log from .request import Request +from .response import HTTPResponse +from .exceptions import RequestTimeout class Signal: @@ -33,8 +35,8 @@ class HttpProtocol(asyncio.Protocol): # connection management '_total_request_size', '_timeout_handler', '_last_communication_time') - def __init__(self, *, loop, request_handler, signal=Signal(), - connections={}, request_timeout=60, + def __init__(self, *, loop, request_handler, error_handler, + signal=Signal(), connections={}, request_timeout=60, request_max_size=None): self.loop = loop self.transport = None @@ -45,11 +47,13 @@ class HttpProtocol(asyncio.Protocol): self.signal = signal self.connections = connections self.request_handler = request_handler + self.error_handler = error_handler self.request_timeout = request_timeout self.request_max_size = request_max_size self._total_request_size = 0 self._timeout_handler = None self._last_request_time = None + self._request_handler_task = None # -------------------------------------------- # # Connection @@ -75,7 +79,17 @@ class HttpProtocol(asyncio.Protocol): self._timeout_handler = \ self.loop.call_later(time_left, self.connection_timeout) 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 @@ -132,7 +146,7 @@ class HttpProtocol(asyncio.Protocol): self.request.body = body 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)) # -------------------------------------------- # @@ -165,6 +179,7 @@ class HttpProtocol(asyncio.Protocol): self.request = None self.url = None self.headers = None + self._request_handler_task = None self._total_request_size = 0 def close_if_idle(self): @@ -204,8 +219,8 @@ def trigger_events(events, loop): loop.run_until_complete(result) -def serve(host, port, request_handler, before_start=None, after_start=None, - before_stop=None, after_stop=None, +def serve(host, port, request_handler, error_handler, before_start=None, + after_start=None, before_stop=None, after_stop=None, debug=False, request_timeout=60, sock=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, signal=signal, request_handler=request_handler, + error_handler=error_handler, request_timeout=request_timeout, request_max_size=request_max_size, ), host, port, reuse_port=reuse_port, sock=sock) diff --git a/tests/test_request_timeout.py b/tests/test_request_timeout.py new file mode 100644 index 00000000..8cf3a680 --- /dev/null +++ b/tests/test_request_timeout.py @@ -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' From 0bd61f6a57948c94cb9deff0e378b95fc8f0cb4f Mon Sep 17 00:00:00 2001 From: 38elements Date: Sat, 26 Nov 2016 14:14:30 +0900 Subject: [PATCH 2/6] Use write_response --- sanic/server.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sanic/server.py b/sanic/server.py index 4b804353..339b8132 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -86,10 +86,7 @@ class HttpProtocol(asyncio.Protocol): 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() + self.write_response(response) # -------------------------------------------- # # Parsing From d8e480ab4889891906807696f36180086f00aa70 Mon Sep 17 00:00:00 2001 From: 38elements Date: Sat, 26 Nov 2016 14:47:42 +0900 Subject: [PATCH 3/6] Change sleep time --- examples/request_timeout.py | 4 ++-- tests/test_request_timeout.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/request_timeout.py b/examples/request_timeout.py index 496864cd..ddae7688 100644 --- a/examples/request_timeout.py +++ b/examples/request_timeout.py @@ -8,7 +8,7 @@ Config.REQUEST_TIMEOUT = 1 app = Sanic(__name__) -@app.route("/") +@app.route('/') async def test(request): await asyncio.sleep(3) return text('Hello, world!') @@ -18,4 +18,4 @@ async def test(request): def timeout(request, exception): return text('RequestTimeout from error_handler.') -app.run(host="0.0.0.0", port=8000) +app.run(host='0.0.0.0', port=8000) diff --git a/tests/test_request_timeout.py b/tests/test_request_timeout.py index 8cf3a680..7b8cfb21 100644 --- a/tests/test_request_timeout.py +++ b/tests/test_request_timeout.py @@ -12,7 +12,7 @@ request_timeout_default_app = Sanic('test_request_timeout_default') @request_timeout_app.route('/1') async def handler_1(request): - await asyncio.sleep(2) + await asyncio.sleep(1) return text('OK') @@ -29,7 +29,7 @@ def test_server_error_request_timeout(): @request_timeout_default_app.route('/1') async def handler_2(request): - await asyncio.sleep(2) + await asyncio.sleep(1) return text('OK') From 9010a6573fea7f855b0597986248a2f3d79d1ba4 Mon Sep 17 00:00:00 2001 From: 38elements Date: Sat, 26 Nov 2016 15:21:57 +0900 Subject: [PATCH 4/6] Add status code --- examples/request_timeout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/request_timeout.py b/examples/request_timeout.py index ddae7688..261f423a 100644 --- a/examples/request_timeout.py +++ b/examples/request_timeout.py @@ -16,6 +16,6 @@ async def test(request): @app.exception(RequestTimeout) def timeout(request, exception): - return text('RequestTimeout from error_handler.') + return text('RequestTimeout from error_handler.', 408) app.run(host='0.0.0.0', port=8000) From a5e6d6d2e8a9e3f879fba5cd0d8e175c774e5ceb Mon Sep 17 00:00:00 2001 From: 38elements Date: Sat, 26 Nov 2016 16:02:44 +0900 Subject: [PATCH 5/6] Use default error process --- sanic/server.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/sanic/server.py b/sanic/server.py index 339b8132..b68524f8 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -13,7 +13,6 @@ except ImportError: from .log import log from .request import Request -from .response import HTTPResponse from .exceptions import RequestTimeout @@ -80,12 +79,8 @@ class HttpProtocol(asyncio.Protocol): self.loop.call_later(time_left, self.connection_timeout) else: 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) + response = self.error_handler.response( + self.request, RequestTimeout('Request Timeout')) self.write_response(response) # -------------------------------------------- # From ee89b6ad03839bbe526f7e84958f9720e9a30e3f Mon Sep 17 00:00:00 2001 From: 38elements Date: Sat, 26 Nov 2016 16:47:16 +0900 Subject: [PATCH 6/6] before process --- sanic/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sanic/server.py b/sanic/server.py index b68524f8..dd582325 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -78,7 +78,8 @@ class HttpProtocol(asyncio.Protocol): self._timeout_handler = \ self.loop.call_later(time_left, self.connection_timeout) else: - self._request_handler_task.cancel() + if self._request_handler_task: + self._request_handler_task.cancel() response = self.error_handler.response( self.request, RequestTimeout('Request Timeout')) self.write_response(response)