change(fastapi): centralize HTTPException

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2021-12-02 23:22:31 -08:00
parent a747548254
commit abfd41f31e
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
6 changed files with 28 additions and 51 deletions

View file

@ -6,8 +6,8 @@ import typing
from urllib.parse import quote_plus from urllib.parse import quote_plus
from fastapi import FastAPI, HTTPException, Request from fastapi import FastAPI, HTTPException, Request, Response
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from prometheus_client import multiprocess from prometheus_client import multiprocess
from sqlalchemy import and_, or_ from sqlalchemy import and_, or_
@ -21,10 +21,11 @@ from aurweb.auth import BasicAuthBackend
from aurweb.db import get_engine, query from aurweb.db import get_engine, query
from aurweb.models import AcceptedTerm, Term from aurweb.models import AcceptedTerm, Term
from aurweb.prometheus import http_api_requests_total, http_requests_total, instrumentator from aurweb.prometheus import http_api_requests_total, http_requests_total, instrumentator
from aurweb.routers import accounts, auth, errors, html, packages, rpc, rss, sso, trusted_user from aurweb.routers import accounts, auth, html, packages, rpc, rss, sso, trusted_user
from aurweb.templates import make_context, render_template
# Setup the FastAPI app. # Setup the FastAPI app.
app = FastAPI(exception_handlers=errors.exceptions) app = FastAPI()
# Instrument routes with the prometheus-fastapi-instrumentator # Instrument routes with the prometheus-fastapi-instrumentator
# library with custom collectors and expose /metrics. # library with custom collectors and expose /metrics.
@ -93,14 +94,15 @@ def child_exit(server, worker): # pragma: no cover
@app.exception_handler(HTTPException) @app.exception_handler(HTTPException)
async def http_exception_handler(request, exc): async def http_exception_handler(request: Request, exc: HTTPException) \
""" -> Response:
Dirty HTML error page to replace the default JSON error responses. """ Handle an HTTPException thrown in a route. """
In the future this should use a proper Arch-themed HTML template.
"""
phrase = http.HTTPStatus(exc.status_code).phrase phrase = http.HTTPStatus(exc.status_code).phrase
return HTMLResponse(f"<h1>{exc.status_code} {phrase}</h1><p>{exc.detail}</p>", context = make_context(request, phrase)
status_code=exc.status_code) context["exc"] = exc
context["phrase"] = phrase
return render_template(request, "errors/detail.html", context,
exc.status_code)
@app.middleware("http") @app.middleware("http")

View file

@ -1,21 +0,0 @@
from http import HTTPStatus
from aurweb.templates import make_context, render_template
async def not_found(request, exc):
context = make_context(request, "Page Not Found")
return render_template(request, "errors/404.html", context,
HTTPStatus.NOT_FOUND)
async def service_unavailable(request, exc):
context = make_context(request, "Service Unavailable")
return render_template(request, "errors/503.html", context,
HTTPStatus.SERVICE_UNAVAILABLE)
# Maps HTTP errors to functions
exceptions = {
404: not_found,
503: service_unavailable
}

View file

@ -1,8 +0,0 @@
{% extends 'partials/layout.html' %}
{% block pageContent %}
<div id="error-page" class="box 404">
<h2>404 - {% trans %}Page Not Found{% endtrans %}</h2>
<p>{% trans %}Sorry, the page you've requested does not exist.{% endtrans %}</p>
</div>
{% endblock %}

View file

@ -1,8 +0,0 @@
{% extends 'partials/layout.html' %}
{% block pageContent %}
<div id="error-page" class="box 503">
<h2>503 - {% trans %}Service Unavailable{% endtrans %}</h2>
<p>{% trans %}Don't panic! This site is down due to maintenance. We will be back soon.{% endtrans %}</p>
</div>
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends 'partials/layout.html' %}
{% block pageContent %}
<div id="error-page" class="box">
<h2>{{ "%d" | format(exc.status_code) }} - {{ phrase }}</h2>
<p>{{ exc.detail }}</p>
</div>
{% endblock %}

View file

@ -11,6 +11,8 @@ import aurweb.asgi
import aurweb.config import aurweb.config
import aurweb.redis import aurweb.redis
from aurweb.testing.requests import Request
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_asgi_startup_session_secret_exception(monkeypatch): async def test_asgi_startup_session_secret_exception(monkeypatch):
@ -42,9 +44,11 @@ async def test_asgi_startup_exception(monkeypatch):
async def test_asgi_http_exception_handler(): async def test_asgi_http_exception_handler():
exc = HTTPException(status_code=422, detail="EXCEPTION!") exc = HTTPException(status_code=422, detail="EXCEPTION!")
phrase = http.HTTPStatus(exc.status_code).phrase phrase = http.HTTPStatus(exc.status_code).phrase
response = await aurweb.asgi.http_exception_handler(None, exc) response = await aurweb.asgi.http_exception_handler(Request(), exc)
assert response.body.decode() == \ assert response.status_code == 422
f"<h1>{exc.status_code} {phrase}</h1><p>{exc.detail}</p>" content = response.body.decode()
assert f"{exc.status_code} - {phrase}" in content
assert "EXCEPTION!" in content
@pytest.mark.asyncio @pytest.mark.asyncio