From ee27c689e12e1074cbd7ae235f0b0c076130422a Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 23 Mar 2017 20:06:39 +0800 Subject: [PATCH 01/11] commented aiohttp load response body in testing --- sanic/testing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sanic/testing.py b/sanic/testing.py index 4fde428c..611f709d 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -22,8 +22,8 @@ class SanicTestClient: cookies=cookies, connector=conn) as session: async with getattr( session, method.lower())(url, *args, **kwargs) as response: - response.text = await response.text() - response.body = await response.read() + # response.text = await response.text() + # response.body = await response.read() return response def _sanic_endpoint_test( From 1562b81522d827539545a28e592aed1966ce0099 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 23 Mar 2017 20:48:57 +0800 Subject: [PATCH 02/11] add arg load_body in testing --- sanic/testing.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sanic/testing.py b/sanic/testing.py index 611f709d..d7e73e56 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -20,10 +20,12 @@ class SanicTestClient: conn = aiohttp.TCPConnector(verify_ssl=False) async with aiohttp.ClientSession( cookies=cookies, connector=conn) as session: + load_body = 'load_body' not in kwargs or kwargs.pop('load_body') async with getattr( session, method.lower())(url, *args, **kwargs) as response: - # response.text = await response.text() - # response.body = await response.read() + if load_body: + response.text = await response.text() + response.body = await response.read() return response def _sanic_endpoint_test( From 46dbaf95a6348482ab58d7f518fd77c1b1abb971 Mon Sep 17 00:00:00 2001 From: Dan Kruchinin Date: Tue, 4 Apr 2017 15:55:38 +0100 Subject: [PATCH 03/11] Response middleware should be called even if server replies with error --- sanic/app.py | 59 +++++++++++++++++++++++----------------- tests/test_middleware.py | 22 +++++++++++++++ 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index ef9ac57c..75e97889 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -444,17 +444,7 @@ class Sanic: # -------------------------------------------- # request.app = self - - response = False - # The if improves speed. I don't know why - if self.request_middleware: - for middleware in self.request_middleware: - response = middleware(request) - if isawaitable(response): - response = await response - if response: - break - + response = await self._run_request_middleware(request) # No middleware results if not response: # -------------------------------------------- # @@ -472,20 +462,6 @@ class Sanic: response = handler(request, *args, **kwargs) if isawaitable(response): response = await response - - # -------------------------------------------- # - # Response Middleware - # -------------------------------------------- # - - if self.response_middleware: - for middleware in self.response_middleware: - _response = middleware(request, response) - if isawaitable(_response): - _response = await _response - if _response: - response = _response - break - except Exception as e: # -------------------------------------------- # # Response Generation Failed @@ -503,6 +479,17 @@ class Sanic: else: response = HTTPResponse( "An error occurred while handling an error") + finally: + # -------------------------------------------- # + # Response Middleware + # -------------------------------------------- # + try: + response = await self._run_response_middleware(request, response) + except: + log.exception( + 'Exception occured in one of response middleware handlers' + ) + # pass the response to the correct callback if isinstance(response, StreamingHTTPResponse): @@ -615,6 +602,28 @@ class Sanic: return await serve(**server_settings) + async def _run_request_middleware(self, request): + # The if improves speed. I don't know why + if self.request_middleware: + for middleware in self.request_middleware: + response = middleware(request) + if isawaitable(response): + response = await response + if response: + return response + return None + + async def _run_response_middleware(self, request, response): + if self.response_middleware: + for middleware in self.response_middleware: + _response = middleware(request, response) + if isawaitable(_response): + _response = await _response + if _response: + response = _response + break + return response + def _helper(self, host="127.0.0.1", port=8000, debug=False, before_start=None, after_start=None, before_stop=None, after_stop=None, ssl=None, sock=None, workers=1, loop=None, diff --git a/tests/test_middleware.py b/tests/test_middleware.py index f84d7151..bc1a7eb8 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -2,6 +2,7 @@ from json import loads as json_loads, dumps as json_dumps from sanic import Sanic from sanic.request import Request from sanic.response import json, text, HTTPResponse +from sanic.exceptions import NotFound # ------------------------------------------------------------ # @@ -53,6 +54,27 @@ def test_middleware_response(): assert isinstance(results[2], HTTPResponse) +def test_middleware_response_exception(): + app = Sanic('test_middleware_response_exception') + result = {'status_code': None} + + @app.middleware('response') + async def process_response(reqest, response): + result['status_code'] = response.status + return response + + @app.exception(NotFound) + async def error_handler(request, exception): + return text('OK', exception.status_code) + + @app.route('/') + async def handler(request): + return text('FAIL') + + request, response = app.test_client.get('/page_not_found') + assert response.text == 'OK' + assert result['status_code'] == 404 + def test_middleware_override_request(): app = Sanic('test_middleware_override_request') From f0a59fccf8abbef6bd83dec4bcfb5d43e66a000d Mon Sep 17 00:00:00 2001 From: Dan Kruchinin Date: Tue, 4 Apr 2017 17:19:45 +0100 Subject: [PATCH 04/11] flake8-related fixes --- sanic/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index 75e97889..1d9a4987 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -484,13 +484,13 @@ class Sanic: # Response Middleware # -------------------------------------------- # try: - response = await self._run_response_middleware(request, response) + response = await self._run_response_middleware(request, + response) except: log.exception( 'Exception occured in one of response middleware handlers' ) - # pass the response to the correct callback if isinstance(response, StreamingHTTPResponse): await stream_callback(response) From 8cf7dce33f4b7513cdd9e753a8db8555eb252110 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Sat, 8 Apr 2017 13:31:11 -0700 Subject: [PATCH 05/11] fix python -m method of running --- sanic/__main__.py | 7 +++++-- sanic/app.py | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/sanic/__main__.py b/sanic/__main__.py index 322d735d..b6a66b8e 100644 --- a/sanic/__main__.py +++ b/sanic/__main__.py @@ -28,10 +28,13 @@ if __name__ == "__main__": raise ValueError("Module is not a Sanic app, it is a {}. " "Perhaps you meant {}.app?" .format(type(app).__name__, args.module)) + if args.cert is not None or args.key is not None: + ssl = {'cert': args.cert, 'key': args.key} + else: + ssl = None app.run(host=args.host, port=args.port, - workers=args.workers, debug=args.debug, - cert=args.cert, key=args.key) + workers=args.workers, debug=args.debug, ssl=ssl) except ImportError: log.error("No module named {} found.\n" " Example File: project/sanic_server.py -> app\n" diff --git a/sanic/app.py b/sanic/app.py index 1d9a4987..ad4d7923 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -7,7 +7,7 @@ from functools import partial from inspect import isawaitable, stack, getmodulename from traceback import format_exc from urllib.parse import urlencode, urlunparse -from ssl import create_default_context +from ssl import create_default_context, Purpose from sanic.config import Config from sanic.constants import HTTP_METHODS @@ -635,9 +635,9 @@ class Sanic: # try common aliaseses cert = ssl.get('cert') or ssl.get('certificate') key = ssl.get('key') or ssl.get('keyfile') - if not cert and key: + if cert is None or key is None: raise ValueError("SSLContext or certificate and key required.") - context = create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) + context = create_default_context(purpose=Purpose.CLIENT_AUTH) context.load_cert_chain(cert, keyfile=key) ssl = context if stop_event is not None: From 2ef8120073d97aef30da51d65e08e1f0069fe21b Mon Sep 17 00:00:00 2001 From: aryeh Date: Sun, 9 Apr 2017 13:29:21 -0400 Subject: [PATCH 06/11] Allow a custom Request class to be passed in to Sonic Allowing a custom Request class to be defined would enable either a different Request class or a subclass of Request to be used, providing more flexibility. --- sanic/app.py | 4 +++- sanic/server.py | 11 +++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index ad4d7923..d2894ff2 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -26,7 +26,7 @@ from sanic.websocket import WebSocketProtocol, ConnectionClosed class Sanic: def __init__(self, name=None, router=None, error_handler=None, - load_env=True): + load_env=True, request_class=None): # Only set up a default log handler if the # end-user application didn't set anything up. if not logging.root.handlers and log.level == logging.NOTSET: @@ -44,6 +44,7 @@ class Sanic: self.name = name self.router = router or Router() + self.request_class = request_class self.error_handler = error_handler or ErrorHandler() self.config = Config(load_env=load_env) self.request_middleware = deque() @@ -668,6 +669,7 @@ class Sanic: server_settings = { 'protocol': protocol, + 'request_class': self.request_class, 'host': host, 'port': port, 'sock': sock, diff --git a/sanic/server.py b/sanic/server.py index 976503a6..04dffefd 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -63,13 +63,13 @@ class HttpProtocol(asyncio.Protocol): # request params 'parser', 'request', 'url', 'headers', # request config - 'request_handler', 'request_timeout', 'request_max_size', + 'request_handler', 'request_timeout', 'request_max_size', 'request_class', # connection management '_total_request_size', '_timeout_handler', '_last_communication_time') def __init__(self, *, loop, request_handler, error_handler, signal=Signal(), connections=set(), request_timeout=60, - request_max_size=None): + request_max_size=None, request_class=None): self.loop = loop self.transport = None self.request = None @@ -82,6 +82,7 @@ class HttpProtocol(asyncio.Protocol): self.error_handler = error_handler self.request_timeout = request_timeout self.request_max_size = request_max_size + self.request_class = request_class or Request self._total_request_size = 0 self._timeout_handler = None self._last_request_time = None @@ -151,7 +152,7 @@ class HttpProtocol(asyncio.Protocol): self.headers.append((name.decode().casefold(), value.decode())) def on_headers_complete(self): - self.request = Request( + self.request = self.request_class( url_bytes=self.url, headers=CIDict(self.headers), version=self.parser.get_http_version(), @@ -320,7 +321,7 @@ def serve(host, port, request_handler, error_handler, before_start=None, request_timeout=60, ssl=None, sock=None, request_max_size=None, reuse_port=False, loop=None, protocol=HttpProtocol, backlog=100, register_sys_signals=True, run_async=False, connections=None, - signal=Signal()): + signal=Signal(), request_class=None): """Start asynchronous HTTP Server on an individual process. :param host: Address to host on @@ -345,6 +346,7 @@ def serve(host, port, request_handler, error_handler, before_start=None, :param reuse_port: `True` for multiple workers :param loop: asyncio compatible event loop :param protocol: subclass of asyncio protocol class + :param request_class: Request class to use :return: Nothing """ if not run_async: @@ -366,6 +368,7 @@ def serve(host, port, request_handler, error_handler, before_start=None, error_handler=error_handler, request_timeout=request_timeout, request_max_size=request_max_size, + request_class=request_class, ) server_coroutine = loop.create_server( From b9dfec38c2c77fd98ce6dffb909de85b4c423514 Mon Sep 17 00:00:00 2001 From: aryeh Date: Sun, 9 Apr 2017 13:38:36 -0400 Subject: [PATCH 07/11] Break long line (> 80 chars) into 2 lines --- sanic/server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sanic/server.py b/sanic/server.py index 04dffefd..7868836c 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -63,7 +63,8 @@ class HttpProtocol(asyncio.Protocol): # request params 'parser', 'request', 'url', 'headers', # request config - 'request_handler', 'request_timeout', 'request_max_size', 'request_class', + 'request_handler', 'request_timeout', 'request_max_size', + 'request_class', # connection management '_total_request_size', '_timeout_handler', '_last_communication_time') From 09885534c686d490733f9cc6c59f4a66859bc32d Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 10 Apr 2017 18:31:28 +0800 Subject: [PATCH 08/11] fixed #615 --- sanic/response.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic/response.py b/sanic/response.py index 4eecaf79..bebb8071 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -129,7 +129,7 @@ class StreamingHTTPResponse(BaseHTTPResponse): data = self._encode_body(data) self.transport.write( - b"%b\r\n%b\r\n" % (str(len(data)).encode(), data)) + b"%x\r\n%b\r\n" % (len(data), data)) async def stream( self, version="1.1", keep_alive=False, keep_alive_timeout=None): From 084f0d27a3dbb5b93f0e544b9f0c762fba31acc3 Mon Sep 17 00:00:00 2001 From: Eli Uriegas Date: Tue, 11 Apr 2017 15:19:00 -0500 Subject: [PATCH 09/11] Increment to 0.5.0 --- sanic/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sanic/__init__.py b/sanic/__init__.py index b67a3a6a..7bf0994a 100644 --- a/sanic/__init__.py +++ b/sanic/__init__.py @@ -1,6 +1,6 @@ from sanic.app import Sanic from sanic.blueprints import Blueprint -__version__ = '0.4.1' +__version__ = '0.5.0' __all__ = ['Sanic', 'Blueprint'] From d20a49e5006a0ee3c6e64a11feb253fadcd840df Mon Sep 17 00:00:00 2001 From: Eli Uriegas Date: Tue, 11 Apr 2017 16:02:49 -0500 Subject: [PATCH 10/11] Lock chardet for now... --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 28014eb6..163df025 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,6 @@ aiofiles aiohttp==1.3.5 +chardet<=2.3.0 beautifulsoup4 coverage httptools From 015c87b5e1c3023a3fea229d235273093b17496d Mon Sep 17 00:00:00 2001 From: Eli Uriegas Date: Tue, 11 Apr 2017 16:02:57 -0500 Subject: [PATCH 11/11] Add traceback for better debugging --- sanic/testing.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sanic/testing.py b/sanic/testing.py index 1cad6a7b..787106a6 100644 --- a/sanic/testing.py +++ b/sanic/testing.py @@ -1,3 +1,5 @@ +import traceback + from sanic.log import log HOST = '127.0.0.1' @@ -50,6 +52,8 @@ class SanicTestClient: **request_kwargs) results[-1] = response except Exception as e: + log.error( + 'Exception:\n{}'.format(traceback.format_exc())) exceptions.append(e) self.app.stop()