Skip to content

Commit

Permalink
Allows immutable cache for static files in a directory (#1268)
Browse files Browse the repository at this point in the history
  • Loading branch information
brichet authored May 10, 2023
1 parent ecd2822 commit 54d7292
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 2 deletions.
17 changes: 15 additions & 2 deletions jupyter_server/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,13 @@ def wrapper(self, *args, **kwargs):


class FileFindHandler(JupyterHandler, web.StaticFileHandler):
"""subclass of StaticFileHandler for serving files from a search path"""
"""subclass of StaticFileHandler for serving files from a search path
The setting "static_immutable_cache" can be set up to serve some static
file as immutable (e.g. file name containing a hash). The setting is a
list of base URL, every static file URL starting with one of those will
be immutable.
"""

# cache search results, don't search for files more than once
_static_paths: dict = {}
Expand All @@ -951,8 +957,15 @@ class FileFindHandler(JupyterHandler, web.StaticFileHandler):
def set_headers(self):
"""Set the headers."""
super().set_headers()

immutable_paths = self.settings.get("static_immutable_cache", [])

# allow immutable cache for files
if any(self.request.path.startswith(path) for path in immutable_paths):
self.set_header("Cache-Control", "public, max-age=31536000, immutable")

# disable browser caching, rely on 304 replies for savings
if "v" not in self.request.arguments or any(
elif "v" not in self.request.arguments or any(
self.request.path.startswith(path) for path in self.no_cache_paths
):
self.set_header("Cache-Control", "no-cache")
Expand Down
14 changes: 14 additions & 0 deletions jupyter_server/serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,17 @@ def _default_terminals_enabled(self):
config=True,
)

static_immutable_cache = List(
Unicode(),
help="""
Paths to set up static files as immutable.
This allow setting up the cache control of static files as immutable.
It should be used for static file named with a hash for instance.
""",
config=True,
)

_starter_app = Instance(
default_value=None,
allow_none=True,
Expand Down Expand Up @@ -1990,6 +2001,9 @@ def init_webapp(self):
] = self.identity_provider.get_secure_cookie_kwargs
self.tornado_settings["token"] = self.identity_provider.token

if self.static_immutable_cache:
self.tornado_settings["static_immutable_cache"] = self.static_immutable_cache

# ensure default_url starts with base_url
if not self.default_url.startswith(self.base_url):
self.default_url = url_path_join(self.base_url, self.default_url)
Expand Down
25 changes: 25 additions & 0 deletions tests/base/test_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
APIVersionHandler,
AuthenticatedFileHandler,
AuthenticatedHandler,
FileFindHandler,
FilesRedirectHandler,
JupyterHandler,
RedirectWithParams,
Expand Down Expand Up @@ -126,3 +127,27 @@ def test_redirect_with_params(jp_serverapp):
handler._transforms = []
handler.get()
assert handler.get_status() == 301


async def test_static_handler(jp_serverapp, tmpdir):
async def async_magic():
pass

MagicMock.__await__ = lambda x: async_magic().__await__()

test_file = tmpdir / "foo"
with open(test_file, "w") as fid:
fid.write("hello")

app: ServerApp = jp_serverapp
request = HTTPRequest("GET", str(test_file))
request.connection = MagicMock()

handler = FileFindHandler(app.web_app, request, path=str(tmpdir))
handler._transforms = []
await handler.get("foo")
assert handler._headers["Cache-Control"] == "no-cache"

handler.settings["static_immutable_cache"] = [str(tmpdir)]
await handler.get("foo")
assert handler._headers["Cache-Control"] == "public, max-age=31536000, immutable"
10 changes: 10 additions & 0 deletions tests/test_serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,3 +561,13 @@ def test_deprecated_notebook_dir_priority(jp_configurable_serverapp, tmp_path):
cfg.ServerApp.notebook_dir = str(notebook_dir)
app.update_config(cfg)
assert app.root_dir == str(cli_dir)


def test_immutable_cache_trait():
# Verify we're working with a clean instance.
ServerApp.clear_instance()
kwargs = {"static_immutable_cache": "/test/immutable"}
serverapp = ServerApp.instance(**kwargs)
serverapp.init_configurables()
serverapp.init_webapp()
assert serverapp.web_app.settings["static_immutable_cache"] == ["/test/immutable"]

0 comments on commit 54d7292

Please sign in to comment.