From ea09906e0a554eb8b0de0acc898f7a4f73a3e99a Mon Sep 17 00:00:00 2001 From: "L. Karkkainen" Date: Fri, 27 Jan 2023 18:50:35 +0000 Subject: [PATCH] Refactored to sanic.pages.error module. Traceback and style tuning. Print also headers, and for other than 500 errors as well. 500 error message text UX workaround. --- sanic/errorpages.py | 10 ++++++++-- sanic/pages/autoindex.py | 1 + sanic/pages/base.py | 28 +++++----------------------- sanic/pages/error.py | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 25 deletions(-) create mode 100644 sanic/pages/error.py diff --git a/sanic/errorpages.py b/sanic/errorpages.py index dc15739a..a336a78d 100644 --- a/sanic/errorpages.py +++ b/sanic/errorpages.py @@ -23,7 +23,7 @@ from traceback import extract_tb from sanic.exceptions import BadRequest, SanicException from sanic.helpers import STATUS_CODES from sanic.response import html, json, text -from sanic.pages.base import ErrorPage +from sanic.pages.error import ErrorPage dumps: t.Callable[..., str] try: @@ -160,7 +160,13 @@ class HTMLRenderer(BaseRenderer): ) def _page(self, full: bool) -> HTTPResponse: - page = ErrorPage(super().title, super().text, sys.exc_info()[1], full=full) + page = ErrorPage( + title=super().title, + text=super().text, + request=self.request, + exc=self.exception, + full=full, + ) return html(page.render(), status=self.status, headers=self.headers) def full(self) -> HTTPResponse: diff --git a/sanic/pages/autoindex.py b/sanic/pages/autoindex.py index 2054b784..47141707 100644 --- a/sanic/pages/autoindex.py +++ b/sanic/pages/autoindex.py @@ -24,6 +24,7 @@ class AutoIndex(BasePage): table.autoindex td:first-child { flex: 1; } table.autoindex td:nth-child(2) { text-align: right; } table.autoindex td:last-child { text-align: right; } + span.icon { margin-right: 1rem; } """ ) TITLE = "File browser" diff --git a/sanic/pages/base.py b/sanic/pages/base.py index f3809e93..6c813056 100644 --- a/sanic/pages/base.py +++ b/sanic/pages/base.py @@ -16,12 +16,15 @@ class BasePage(ABC): display: flex; align-items: center; justify-content: space-between; background: #555; color: #e1e1e1; } + main { padding-bottom: 3rem; } + h2 { margin: 2rem 0 1rem 0; } a:visited { color: inherit; } a { text-decoration: none; color: #88f; } a:hover, a:focus { text-decoration: underline; outline: none; } #logo { height: 2.5rem; } - table { width: 100%; max-width: 1200px; } - span.icon { margin-right: 1rem; } + .smalltext { font-size: 1rem; } + .nobr { white-space: nowrap; } + table { width: 100%; max-width: 1200px; word-break: break-all; } @media (prefers-color-scheme: dark) { html { background: #111; color: #ccc; } } @@ -50,24 +53,3 @@ class BasePage(ABC): @abstractmethod def _body(self) -> None: ... - - -class ErrorPage(BasePage): - TITLE = "Error while handling your request" - - def __init__(self, title: str, text: str, exc: Exception, full: bool) -> None: - super().__init__() - self.title = title - self.text = text - self.exc = exc - self.full = full - - def _body(self) -> None: - with self.doc.main: - self.doc.h1(f"⚠️ {self.title}") - if self.full and self.exc: - from niceback import html_traceback - self.doc(html_traceback(self.exc)) - else: - self.doc.p(self.text) - diff --git a/sanic/pages/error.py b/sanic/pages/error.py new file mode 100644 index 00000000..88be299e --- /dev/null +++ b/sanic/pages/error.py @@ -0,0 +1,40 @@ +from .base import BasePage +from sanic.request import Request + +from html5tagger import E +from niceback import html_traceback, inspector + +# Avoid showing the request in the traceback variable inspectors +inspector.blacklist_types += Request, + +class ErrorPage(BasePage): + def __init__(self, title: str, text: str, request: Request, exc: Exception, full: bool) -> None: + super().__init__() + # Internal server errors come with the text of the exception, which we don't want to show to the user. + # FIXME: This needs to be done some place else but I am not digging into that now. + if "Internal Server Error" in title: + text = "The application encountered an unexpected error and could not continue." + self.TITLE = E(f"App {request.app.name} cannot handle your request") + self.title = title + self.text = text + self.request = request + self.exc = exc + self.full = full + + def _body(self) -> None: + with self.doc.main: + self.doc.h1(f"⚠️ {self.title}").p(self.text) + if not self.request.app.debug: + return + # Show additional details in debug mode, open by default for 500 errors + with self.doc.details(open=self.full, class_="smalltext"): + self.doc.summary("Details for developers (Sanic debug mode only)") + if self.exc: + self.doc.h2(f"Exception in {self.request.route.name}:") + # skip_outmost=1 to hide Sanic.handle_request + self.doc(html_traceback(self.exc, skip_outmost=1)) + + self.doc.h2(f"{self.request.method} {self.request.path}") + with self.doc.table(id="request-headers"): + for k, v in self.request.headers.items(): + self.doc.tr.td(f"{k}:", class_="nobr").td(v)