diff --git a/sanic/app.py b/sanic/app.py index f0da45b2..c48a8d20 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -938,7 +938,7 @@ class Sanic: """ pass - async def handle_exception(self, request, exception, name=""): + async def handle_exception(self, request, exception): try: response = self.error_handler.response(request, exception) if isawaitable(response): @@ -959,7 +959,7 @@ class Sanic: if response is not None: try: response = await self._run_response_middleware( - request, response, request_name=name + request, response, request_name=request.name ) except CancelledError: # FIXME: Ensure exiting in a clean manner instead of this @@ -992,6 +992,7 @@ class Sanic: try: # Fetch handler from router handler, args, kwargs, uri, name = self.router.get(request) + request.name = name if request.stream.request_body: if self.router.is_stream_handler(request): @@ -1036,22 +1037,11 @@ class Sanic: response = handler(request, *args, **kwargs) if isawaitable(response): response = await response - # Run response middleware - if response is not None: - try: - response = await self._run_response_middleware( - request, response, request_name=name - ) - except CancelledError: - raise - except Exception: - error_logger.exception( - "Exception occurred in one of response " - "middleware handlers" - ) + if response: + response = await request.respond(response) # Make sure that response is finished / run StreamingHTTP callback if isinstance(response, BaseHTTPResponse): - await request.respond(response).send(end_stream=True) + await response.send(end_stream=True) else: raise ServerError( f"Invalid response type {response!r} (need HTTPResponse)" @@ -1063,8 +1053,9 @@ class Sanic: # -------------------------------------------- # # Response Generation Failed # -------------------------------------------- # - response = await self.handle_exception(request, e, name) - await request.respond(response).send(end_stream=True) + response = await self.handle_exception(request, e) + response = await request.respond(response) + await response.send(end_stream=True) # -------------------------------------------------------------------- # # Testing @@ -1345,6 +1336,8 @@ class Sanic: _response = await _response if _response: response = _response + if isinstance(response, BaseHTTPResponse): + response = request.stream.respond(response) break return response diff --git a/sanic/compat.py b/sanic/compat.py index ebf25bb0..e6d0bc31 100644 --- a/sanic/compat.py +++ b/sanic/compat.py @@ -1,7 +1,13 @@ from sys import argv from multidict import CIMultiDict # type: ignore +from asyncio import CancelledError +try: + from trio import Cancelled + CancelledErrors = CancelledError, Cancelled +except ImportError: + CancelledErrors = CancelledError, class Header(CIMultiDict): def get_all(self, key): diff --git a/sanic/request.py b/sanic/request.py index fb98cbae..ce9e1b51 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -9,6 +9,7 @@ from urllib.parse import parse_qs, parse_qsl, unquote, urlunparse from httptools import parse_url # type: ignore from sanic.exceptions import InvalidUsage +from sanic.compat import CancelledErrors from sanic.headers import ( parse_content_header, parse_forwarded, @@ -16,7 +17,7 @@ from sanic.headers import ( parse_xforwarded, ) from sanic.log import error_logger, logger -from sanic.response import HTTPResponse +from sanic.response import HTTPResponse, BaseHTTPResponse try: @@ -62,6 +63,7 @@ class Request: "endpoint", "headers", "method", + "name", "parsed_args", "parsed_not_grouped_args", "parsed_files", @@ -89,6 +91,7 @@ class Request: # Init but do not inhale self.body = b"" self.ctx = SimpleNamespace() + self.name = None self.parsed_forwarded = None self.parsed_json = None self.parsed_form = None @@ -105,7 +108,7 @@ class Request: self.__class__.__name__, self.method, self.path ) - def respond( + async def respond( self, response=None, *, status=200, headers=None, content_type=None ): # This logic of determining which response to use is subject to change @@ -113,8 +116,22 @@ class Request: response = self.stream.response or HTTPResponse( status=status, headers=headers, content_type=content_type, ) - # Connect the response and return it - return self.stream.respond(response) + # Connect the response + if isinstance(response, BaseHTTPResponse): + response = self.stream.respond(response) + # Run response middleware + try: + response = await self.app._run_response_middleware( + self, response, request_name=self.name + ) + except CancelledErrors: + raise + except Exception: + error_logger.exception( + "Exception occurred in one of response " + "middleware handlers" + ) + return response async def receive_body(self): self.body = b"".join([data async for data in self.stream])