Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows immutable cache for static files in a directory #1268

Merged
merged 3 commits into from
May 10, 2023
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
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"]