From e126d431d7202c20bdcb208c3583d63cf28c5900 Mon Sep 17 00:00:00 2001 From: Steven Guikal Date: Mon, 13 Dec 2021 19:08:33 -0500 Subject: [PATCH] fix(FastAPI): add custom error templates for certain exceptions Signed-off-by: Steven Guikal --- aurweb/asgi.py | 27 ++++++++++++++++++++++++--- aurweb/packages/util.py | 5 ++++- templates/errors/404.html | 34 ++++++++++++++++++++++++++++++++++ templates/errors/503.html | 8 ++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 templates/errors/404.html create mode 100644 templates/errors/503.html diff --git a/aurweb/asgi.py b/aurweb/asgi.py index 161f6f26..b55eada1 100644 --- a/aurweb/asgi.py +++ b/aurweb/asgi.py @@ -1,6 +1,7 @@ import asyncio import http import os +import re import sys import typing @@ -9,18 +10,22 @@ from urllib.parse import quote_plus from fastapi import FastAPI, HTTPException, Request, Response from fastapi.responses import RedirectResponse from fastapi.staticfiles import StaticFiles +from jinja2 import TemplateNotFound from prometheus_client import multiprocess from sqlalchemy import and_, or_ +from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.middleware.authentication import AuthenticationMiddleware from starlette.middleware.sessions import SessionMiddleware import aurweb.config import aurweb.logging +import aurweb.pkgbase.util as pkgbaseutil from aurweb import prometheus, util from aurweb.auth import BasicAuthBackend from aurweb.db import get_engine, query from aurweb.models import AcceptedTerm, Term +from aurweb.packages.util import get_pkg_or_base from aurweb.prometheus import instrumentator from aurweb.routers import APP_ROUTES from aurweb.templates import make_context, render_template @@ -89,7 +94,7 @@ def child_exit(server, worker): # pragma: no cover multiprocess.mark_process_dead(worker.pid) -@app.exception_handler(HTTPException) +@app.exception_handler(StarletteHTTPException) async def http_exception_handler(request: Request, exc: HTTPException) \ -> Response: """ Handle an HTTPException thrown in a route. """ @@ -97,8 +102,24 @@ async def http_exception_handler(request: Request, exc: HTTPException) \ context = make_context(request, phrase) context["exc"] = exc context["phrase"] = phrase - return render_template(request, "errors/detail.html", context, - exc.status_code) + + # Additional context for some exceptions. + if exc.status_code == http.HTTPStatus.NOT_FOUND: + tokens = request.url.path.split("/") + matches = re.match("^([a-z0-9][a-z0-9.+_-]*?)(\\.git)?$", tokens[1]) + if matches: + try: + pkgbase = get_pkg_or_base(matches.group(1)) + context = pkgbaseutil.make_context(request, pkgbase) + except HTTPException: + pass + + try: + return render_template(request, f"errors/{exc.status_code}.html", + context, exc.status_code) + except TemplateNotFound: + return render_template(request, "errors/detail.html", + context, exc.status_code) @app.middleware("http") diff --git a/aurweb/packages/util.py b/aurweb/packages/util.py index 4210f51e..fd8fdf85 100644 --- a/aurweb/packages/util.py +++ b/aurweb/packages/util.py @@ -87,11 +87,14 @@ def provides_markup(provides: Providers) -> str: def get_pkg_or_base( name: str, - cls: Union[models.Package, models.PackageBase] = models.PackageBase): + cls: Union[models.Package, models.PackageBase] = models.PackageBase) \ + -> Union[models.Package, models.PackageBase]: """ Get a PackageBase instance by its name or raise a 404 if it can't be found in the database. :param name: {Package,PackageBase}.Name + :param exception: Whether to raise an HTTPException or simply return None if + the package can't be found. :raises HTTPException: With status code 404 if record doesn't exist :return: {Package,PackageBase} instance """ diff --git a/templates/errors/404.html b/templates/errors/404.html new file mode 100644 index 00000000..d232c656 --- /dev/null +++ b/templates/errors/404.html @@ -0,0 +1,34 @@ +{% extends 'partials/layout.html' %} + +{% block pageContent %} +
+

404 - {% trans %}Page Not Found{% endtrans %}

+

{% trans %}Sorry, the page you've requested does not exist.{% endtrans %}

+ {% if pkgbase %} +
    + {% set pkgname_strong="%s" | format(pkgbase.Name) %} +
  • + {% trans %}Note{% endtrans %}: + {% trans %}Git clone URLs are not meant to be opened in a browser.{% endtrans %} +
  • +
  • + {% set gitcmd="git clone %s" | format(git_clone_uri_anon | format(pkgbase.Name)) %} + {% if is_maintainer %} + {% set gitcmd="git clone %s" | format(git_clone_uri_priv | format(pkgbase.Name)) %} + {% endif %} + {{ + "To clone the Git repository of %s, run %s." + | tr | format(pkgname_strong, gitcmd) | safe + }} +
  • +
  • + {% set pkglink='' | format(pkgbase.Name) %} + {{ + "Click %shere%s to return to the %s details page." + | tr | format(pkglink, "", pkgname_strong) | safe + }} +
  • +
+ {% endif %} +
+{% endblock %} diff --git a/templates/errors/503.html b/templates/errors/503.html new file mode 100644 index 00000000..08f737bf --- /dev/null +++ b/templates/errors/503.html @@ -0,0 +1,8 @@ +{% extends 'partials/layout.html' %} + +{% block pageContent %} +
+

503 - {% trans %}Service Unavailable{% endtrans %}

+

{% trans %}Don't panic! This site is down due to maintenance. We will be back soon.{% endtrans %}

+
+{% endblock %}