Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions blurb_it/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,24 @@

@web.middleware
async def error_middleware(request, handler):
"""Middleware to render error message using the template renderer."""
"""Middleware to render error message using the template renderer.

Only client and server errors are rendered. Redirects and other
non-error responses raised as exceptions pass through untouched.
The rendered page keeps the original status code, so monitoring,
health probes and Sentry see the real error instead of a 200.
"""
try:
response = await handler(request)
except web.HTTPException as ex:
if ex.status < 400:
raise
if ex.text:
message = ex.text
else:
message = ex.reason
context = {"error_message": message, "status": ex.status}
response = aiohttp_jinja2.render_template(
"error.html", request, context=context
"error.html", request, context=context, status=ex.status
)
return response
59 changes: 59 additions & 0 deletions tests/test_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from __future__ import annotations

import pathlib

import aiohttp_jinja2
import jinja2
from aiohttp import web

from blurb_it import middleware


def create_app() -> web.Application:
app = web.Application(middlewares=[middleware.error_middleware])
aiohttp_jinja2.setup(
app, loader=jinja2.FileSystemLoader(pathlib.Path.cwd() / "templates")
)
return app


async def test_not_found_keeps_status_code(aiohttp_client) -> None:
app = create_app()
client = await aiohttp_client(app)

response = await client.get("/does-not-exist")

assert response.status == 404
text = await response.text()
assert "<h1>404</h1>" in text
assert "Not Found" in text


async def test_handler_error_renders_text_with_status(aiohttp_client) -> None:
async def forbidden(request):
raise web.HTTPForbidden(reason="Invalid CSRF token. Please retry.")

app = create_app()
app.router.add_get("/forbidden", forbidden)
client = await aiohttp_client(app)

response = await client.get("/forbidden")

assert response.status == 403
text = await response.text()
assert "<h1>403</h1>" in text
assert "Invalid CSRF token. Please retry." in text


async def test_redirect_passes_through(aiohttp_client) -> None:
async def redirect(request):
raise web.HTTPFound(location="/elsewhere")

app = create_app()
app.router.add_get("/redirect", redirect)
client = await aiohttp_client(app)

response = await client.get("/redirect", allow_redirects=False)

assert response.status == 302
assert response.headers["Location"] == "/elsewhere"
Loading