From 17d100400fd09f808078e7044aa200427327ab7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=2E=20K=C3=A4rkk=C3=A4inen?= Date: Tue, 10 Mar 2020 16:45:26 +0200 Subject: [PATCH] Middleware and error handling refactoring. --- sanic/app.py | 34 +++++++++++++++++----------------- sanic/http.py | 17 +++++++++++++++++ sanic/server.py | 9 ++++++--- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/sanic/app.py b/sanic/app.py index c48a8d20..e42c1210 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -955,22 +955,22 @@ class Sanic: response = HTTPResponse( "An error occurred while handling an error", status=500 ) - # Run response middleware if response is not None: try: - response = await self._run_response_middleware( - request, response, request_name=request.name - ) - except CancelledError: - # FIXME: Ensure exiting in a clean manner instead of this - # and verify py37 and py38 test_middleware.py separately - request.stream.keep_alive = False - except Exception: - error_logger.exception( - "Exception occurred in one of response " - "middleware handlers" - ) - return response + response = await request.respond(response) + except: + # Skip response middleware + request.stream.respond(response) + await response.send(end_stream=True) + raise + else: + response = request.stream.response + if isinstance(response, BaseHTTPResponse): + await response.send(end_stream=True) + else: + raise ServerError( + f"Invalid response type {response!r} (need HTTPResponse)" + ) async def handle_request(self, request): """Take a request from the HTTP Server and return a response object @@ -1039,6 +1039,8 @@ class Sanic: response = await response if response: response = await request.respond(response) + else: + response = request.stream.response # Make sure that response is finished / run StreamingHTTP callback if isinstance(response, BaseHTTPResponse): await response.send(end_stream=True) @@ -1053,9 +1055,7 @@ class Sanic: # -------------------------------------------- # # Response Generation Failed # -------------------------------------------- # - response = await self.handle_exception(request, e) - response = await request.respond(response) - await response.send(end_stream=True) + await self.handle_exception(request, e) # -------------------------------------------------------------------- # # Testing diff --git a/sanic/http.py b/sanic/http.py index 5be70957..16a68685 100644 --- a/sanic/http.py +++ b/sanic/http.py @@ -274,9 +274,26 @@ class Http: # From request and handler states we can respond, otherwise be silent if self.stage is Stage.HANDLER: app = self.protocol.app + if self.request is None: + self.create_empty_request() response = await app.handle_exception(self.request, exception) await self.respond(response).send(end_stream=True) + def create_empty_request(self): + """Current error handling code needs a request object that won't exist + if an error occurred during before a request was received. Create a + bogus response for error handling use.""" + # FIXME: Avoid this by refactoring error handling and response code + self.request = self.protocol.request_class( + url_bytes=self.url.encode() if self.url else b"*", + headers=Header({}), + version="1.1", + method="NONE", + transport=self.protocol.transport, + app=self.protocol.app, + ) + self.request.stream = self + def log_response(self): """ Helper method provided to enable the logging of responses in case if diff --git a/sanic/server.py b/sanic/server.py index cdea56dd..03820844 100644 --- a/sanic/server.py +++ b/sanic/server.py @@ -137,6 +137,12 @@ class HttpProtocol(asyncio.Protocol): except Exception: logger.exception("protocol.connection_task uncaught") finally: + if self._debug and self._http and self._http.request: + ip = self.transport.get_extra_info("peername") + logger.error( + "Connection lost before response written" + f" @ {ip} {self._http.request}" + ) self._http = None self._task = None try: @@ -229,9 +235,6 @@ class HttpProtocol(asyncio.Protocol): self.resume_writing() if self._task: self._task.cancel() - if self._debug and self._http and self._http.response: - ip = self.transport.get_extra_info("peername") - logger.error(f"Connection lost before response written @ {ip}") except Exception: logger.exception("protocol.connection_lost")