From e50902b8690907f1a7e26f0b18ea307f76019a4e Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Mon, 23 Jun 2025 12:10:36 +0100 Subject: [PATCH] Allow 404 pages to be cached --- cloudflare/workers.js | 5 +++-- tbx/core/tests/test_views.py | 18 ++++++++++++++++++ tbx/core/utils/views.py | 9 ++++++++- tbx/settings/base.py | 7 ++++--- tbx/settings/test.py | 2 ++ tbx/urls.py | 20 +++++++------------- 6 files changed, 42 insertions(+), 19 deletions(-) diff --git a/cloudflare/workers.js b/cloudflare/workers.js index 158d6ed78..03300eb45 100644 --- a/cloudflare/workers.js +++ b/cloudflare/workers.js @@ -93,9 +93,10 @@ const REPLACE_STRIPPED_QUERYSTRING_ON_REDIRECT_LOCATION = false; // Disabled by default, but highly recommended const STRIP_VALUELESS_QUERYSTRING_KEYS = false; -// Only these status codes should be considered cacheable +// Only these status codes should be considered cacheable. // (from https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.4) -const CACHABLE_HTTP_STATUS_CODES = [200, 203, 206, 300, 301, 410]; +// 404 is added to reduce server load on spammed requests. +const CACHABLE_HTTP_STATUS_CODES = [200, 203, 206, 300, 301, 404, 410]; addEventListener('fetch', (event) => { event.respondWith(main(event)); diff --git a/tbx/core/tests/test_views.py b/tbx/core/tests/test_views.py index d10de9b3b..17e50d8e8 100644 --- a/tbx/core/tests/test_views.py +++ b/tbx/core/tests/test_views.py @@ -107,3 +107,21 @@ def test_setting_theme_on_one_site_sets_it_on_multiple_sites(self): # check theme is set on new site resp = self.client.get(f"http://{new_site.hostname}/") self.assertEqual(resp.context["MODE"], mode) + + +class PageNotFoundTestCase(TestCase): + def test_page_not_found(self) -> None: + response = self.client.get("/does-not-exist/") + self.assertEqual(response.status_code, 404) + self.assertEqual( + response.headers["Cache-Control"], + "s-maxage=600, stale-while-revalidate=60, public", + ) + + def test_test_404(self) -> None: + response = self.client.get("/test404/") + self.assertEqual(response.status_code, 404) + self.assertEqual( + response.headers["Cache-Control"], + "s-maxage=600, stale-while-revalidate=60, public", + ) diff --git a/tbx/core/utils/views.py b/tbx/core/utils/views.py index cf53d4585..73a8b4f9c 100644 --- a/tbx/core/utils/views.py +++ b/tbx/core/utils/views.py @@ -1,9 +1,16 @@ from django.views import defaults +from django.views.decorators.cache import cache_control, never_cache +from tbx.core.utils.cache import get_default_cache_control_kwargs -def page_not_found(request, exception, template_name="patterns/pages/errors/404.html"): + +@cache_control(**(get_default_cache_control_kwargs() | {"s_maxage": 600})) +def page_not_found( + request, exception=None, template_name="patterns/pages/errors/404.html" +): return defaults.page_not_found(request, exception, template_name) +@never_cache def server_error(request, template_name="patterns/pages/errors/500.html"): return defaults.server_error(request, template_name) diff --git a/tbx/settings/base.py b/tbx/settings/base.py index 1e6c317eb..86c51da9b 100644 --- a/tbx/settings/base.py +++ b/tbx/settings/base.py @@ -22,6 +22,7 @@ # Basic settings DEBUG = False +TEST = False APP_NAME = env.get("APP_NAME", "torchbox") @@ -549,13 +550,13 @@ # Set s-max-age header that is used by reverse proxy/front end cache. See # urls.py with contextlib.suppress(ValueError): - CACHE_CONTROL_S_MAXAGE = int(env.get("CACHE_CONTROL_S_MAXAGE", 600)) + CACHE_CONTROL_S_MAXAGE = int(env.get("CACHE_CONTROL_S_MAXAGE", 14400)) -# Give front-end cache 30 second to revalidate the cache to avoid hitting the +# Give front-end cache 60 second to revalidate the cache to avoid hitting the # backend. See urls.py CACHE_CONTROL_STALE_WHILE_REVALIDATE = int( - env.get("CACHE_CONTROL_STALE_WHILE_REVALIDATE", 30) + env.get("CACHE_CONTROL_STALE_WHILE_REVALIDATE", 60) ) # Embeds diff --git a/tbx/settings/test.py b/tbx/settings/test.py index 7e6489e6e..2368a92ce 100644 --- a/tbx/settings/test.py +++ b/tbx/settings/test.py @@ -1,6 +1,8 @@ from .base import * # noqa: F403 +TEST = True + # ############# # General diff --git a/tbx/urls.py b/tbx/urls.py index c75bedc01..05a821896 100644 --- a/tbx/urls.py +++ b/tbx/urls.py @@ -17,6 +17,7 @@ get_default_cache_control_decorator, get_default_cache_control_method_decorator, ) +from tbx.core.utils.views import page_not_found, server_error from tbx.core.views import robots, switch_mode @@ -32,26 +33,19 @@ ] -if settings.DEBUG: +if settings.DEBUG or settings.TEST: from django.conf.urls.static import static from django.contrib.staticfiles.urls import staticfiles_urlpatterns - from django.views.generic import TemplateView # Serve static and media files from development server urlpatterns += staticfiles_urlpatterns() urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # Add views for testing 404 and 500 templates - urlpatterns += [ + private_urlpatterns += [ # Add views for testing 404 and 500 templates - path( - "test404/", - TemplateView.as_view(template_name="patterns/pages/errors/404.html"), - ), - path( - "test500/", - TemplateView.as_view(template_name="patterns/pages/errors/500.html"), - ), + path("test404/", page_not_found), + path("test500/", server_error), ] # Django Debug Toolbar @@ -104,5 +98,5 @@ ) # Error handlers -handler404 = "tbx.core.utils.views.page_not_found" -handler500 = "tbx.core.utils.views.server_error" +handler404 = page_not_found +handler500 = server_error