python 44 lines · 8 steps

Serving cacheable static assets in Flask

A Flask blueprint that streams files safely while using ETags and cache headers to skip redundant transfers.

Explained by highlit
1import hashlib
2import os
3from datetime import timedelta
4 
5from flask import Blueprint, abort, current_app, request, send_file
6from werkzeug.utils import safe_join
7 
8assets = Blueprint("assets", __name__)
9 
10 
11def _compute_etag(path, stat):
12 digest = hashlib.blake2b(digest_size=16)
13 digest.update(str(stat.st_mtime_ns).encode())
14 digest.update(str(stat.st_size).encode())
15 digest.update(os.fsencode(path))
16 return f'"{digest.hexdigest()}"'
17 
18 
19@assets.route("/assets/<path:filename>")
20def serve_asset(filename):
21 root = current_app.config["ASSET_ROOT"]
22 full_path = safe_join(root, filename)
23 if full_path is None or not os.path.isfile(full_path):
24 abort(404)
25 
26 stat = os.stat(full_path)
27 etag = _compute_etag(full_path, stat)
28 
29 if etag in request.if_none_match:
30 response = current_app.response_class(status=304)
31 response.set_etag(etag.strip('"'))
32 return response
33 
34 response = send_file(
35 full_path,
36 conditional=True,
37 last_modified=stat.st_mtime,
38 max_age=timedelta(days=30),
39 )
40 response.set_etag(etag.strip('"'))
41 response.cache_control.public = True
42 response.cache_control.immutable = True
43 response.cache_control.max_age = int(timedelta(days=30).total_seconds())
44 return response
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1An ETag derived from file metadata lets clients revalidate and receive a cheap 304 instead of the full body.
  2. 2safe_join prevents path traversal by rejecting filenames that escape the configured root directory.
  3. 3Setting cache-control to public and immutable tells browsers they can reuse the asset without ever revalidating.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Serving cacheable static assets in Flask — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code