Repository: zauberzeug/nicegui
Pull request #3865 (2024-10-11 15:58:31)

 Add Cache-Control header to static files
As discussed in https://github.com/zauberzeug/nicegui/discussions/3858, this PR adds `Cache-Control` header to the framework and custom static files.


Patch:
diff --git a/nicegui/app/app.py b/nicegui/app/app.py
index 4cd7e29e..312a6402 100644
--- a/nicegui/app/app.py
+++ b/nicegui/app/app.py
@@ -9,7 +9,6 @@ from typing import Any, Awaitable, Callable, List, Optional, Union
 
 from fastapi import FastAPI, HTTPException, Request, Response
 from fastapi.responses import FileResponse
-from fastapi.staticfiles import StaticFiles
 
 from .. import background_tasks, helpers
 from ..client import Client
@@ -17,6 +16,7 @@ from ..logging import log
 from ..native import NativeConfig
 from ..observables import ObservableSet
 from ..server import Server
+from ..staticfiles import CacheControlledStaticFiles
 from ..storage import Storage
 from .app_config import AppConfig
 from .range_response import get_range_response
@@ -157,7 +157,7 @@ class App(FastAPI):
         if url_path == '/':
             raise ValueError('''Path cannot be "/", because it would hide NiceGUI's internal "/_nicegui" route.''')
 
-        handler = StaticFiles(directory=local_directory, follow_symlink=follow_symlink)
+        handler = CacheControlledStaticFiles(directory=local_directory, follow_symlink=follow_symlink)
 
         @self.get(url_path + '/{path:path}')
         async def static_file(request: Request, path: str = '') -> Response:
diff --git a/nicegui/nicegui.py b/nicegui/nicegui.py
index 72878a9d..456027eb 100644
--- a/nicegui/nicegui.py
+++ b/nicegui/nicegui.py
@@ -9,7 +9,6 @@ import socketio
 from fastapi import HTTPException, Request
 from fastapi.middleware.gzip import GZipMiddleware
 from fastapi.responses import FileResponse, Response
-from fastapi.staticfiles import StaticFiles
 
 from . import air, background_tasks, binding, core, favicon, helpers, json, run, welcome
 from .app import App
@@ -21,6 +20,7 @@ from .logging import log
 from .middlewares import RedirectWithPrefixMiddleware
 from .page import page
 from .slot import Slot
+from .staticfiles import CacheControlledStaticFiles
 from .version import __version__
 
 
@@ -56,7 +56,7 @@ mimetypes.add_type('text/css', '.css')
 
 app.add_middleware(GZipMiddleware)
 app.add_middleware(RedirectWithPrefixMiddleware)
-static_files = StaticFiles(
+static_files = CacheControlledStaticFiles(
     directory=(Path(__file__).parent / 'static').resolve(),
     follow_symlink=True,
 )
diff --git a/nicegui/staticfiles.py b/nicegui/staticfiles.py
new file mode 100644
index 00000000..731beaba
--- /dev/null
+++ b/nicegui/staticfiles.py
@@ -0,0 +1,11 @@
+from starlette.responses import Response
+from starlette.staticfiles import StaticFiles
+from starlette.types import Scope
+
+
+class CacheControlledStaticFiles(StaticFiles):
+
+    async def get_response(self, path: str, scope: Scope) -> Response:
+        response = await super().get_response(path, scope)
+        response.headers['Cache-Control'] = 'public, max-age=3600'
+        return response


Test patch:
diff --git a/tests/test_serving_files.py b/tests/test_serving_files.py
index 131dd2f0..ebca6e85 100644
--- a/tests/test_serving_files.py
+++ b/tests/test_serving_files.py
@@ -61,6 +61,7 @@ def test_get_from_static_files_dir(url_path: str, screen: Screen):
     with httpx.Client() as http_client:
         r = http_client.get(f'http://localhost:{Screen.PORT}/static/examples/slideshow/slides/slide1.jpg')
         assert r.status_code == 200
+        assert 'max-age=' in r.headers['Cache-Control']
 
 
 def test_404_for_non_existing_static_file(screen: Screen):