Browse Source

Add Cache-Control header to static files (#3865)

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

---------

Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Henry Zhu 7 months ago
parent
commit
5e39a4159d
4 changed files with 16 additions and 4 deletions
  1. 2 2
      nicegui/app/app.py
  2. 2 2
      nicegui/nicegui.py
  3. 11 0
      nicegui/staticfiles.py
  4. 1 0
      tests/test_serving_files.py

+ 2 - 2
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 import FastAPI, HTTPException, Request, Response
 from fastapi.responses import FileResponse
 from fastapi.responses import FileResponse
-from fastapi.staticfiles import StaticFiles
 
 
 from .. import background_tasks, helpers
 from .. import background_tasks, helpers
 from ..client import Client
 from ..client import Client
@@ -17,6 +16,7 @@ from ..logging import log
 from ..native import NativeConfig
 from ..native import NativeConfig
 from ..observables import ObservableSet
 from ..observables import ObservableSet
 from ..server import Server
 from ..server import Server
+from ..staticfiles import CacheControlledStaticFiles
 from ..storage import Storage
 from ..storage import Storage
 from .app_config import AppConfig
 from .app_config import AppConfig
 from .range_response import get_range_response
 from .range_response import get_range_response
@@ -157,7 +157,7 @@ class App(FastAPI):
         if url_path == '/':
         if url_path == '/':
             raise ValueError('''Path cannot be "/", because it would hide NiceGUI's internal "/_nicegui" route.''')
             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}')
         @self.get(url_path + '/{path:path}')
         async def static_file(request: Request, path: str = '') -> Response:
         async def static_file(request: Request, path: str = '') -> Response:

+ 2 - 2
nicegui/nicegui.py

@@ -9,7 +9,6 @@ import socketio
 from fastapi import HTTPException, Request
 from fastapi import HTTPException, Request
 from fastapi.middleware.gzip import GZipMiddleware
 from fastapi.middleware.gzip import GZipMiddleware
 from fastapi.responses import FileResponse, Response
 from fastapi.responses import FileResponse, Response
-from fastapi.staticfiles import StaticFiles
 
 
 from . import air, background_tasks, binding, core, favicon, helpers, json, run, welcome
 from . import air, background_tasks, binding, core, favicon, helpers, json, run, welcome
 from .app import App
 from .app import App
@@ -21,6 +20,7 @@ from .logging import log
 from .middlewares import RedirectWithPrefixMiddleware
 from .middlewares import RedirectWithPrefixMiddleware
 from .page import page
 from .page import page
 from .slot import Slot
 from .slot import Slot
+from .staticfiles import CacheControlledStaticFiles
 from .version import __version__
 from .version import __version__
 
 
 
 
@@ -56,7 +56,7 @@ mimetypes.add_type('text/css', '.css')
 
 
 app.add_middleware(GZipMiddleware)
 app.add_middleware(GZipMiddleware)
 app.add_middleware(RedirectWithPrefixMiddleware)
 app.add_middleware(RedirectWithPrefixMiddleware)
-static_files = StaticFiles(
+static_files = CacheControlledStaticFiles(
     directory=(Path(__file__).parent / 'static').resolve(),
     directory=(Path(__file__).parent / 'static').resolve(),
     follow_symlink=True,
     follow_symlink=True,
 )
 )

+ 11 - 0
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

+ 1 - 0
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:
     with httpx.Client() as http_client:
         r = http_client.get(f'http://localhost:{Screen.PORT}/static/examples/slideshow/slides/slide1.jpg')
         r = http_client.get(f'http://localhost:{Screen.PORT}/static/examples/slideshow/slides/slide1.jpg')
         assert r.status_code == 200
         assert r.status_code == 200
+        assert 'max-age=' in r.headers['Cache-Control']
 
 
 
 
 def test_404_for_non_existing_static_file(screen: Screen):
 def test_404_for_non_existing_static_file(screen: Screen):