bigywater.tech
Django · 90

静态文件、媒体文件与缓存策略

静态文件放哪里、怎么缓存;用户上传的媒体文件又该怎么办?这篇分别在 runserver、没有 WhiteNoise 的 gunicorn、有 WhiteNoise 的 gunicorn 三种环境里请求同一批文件,直接对比响应头。

immutable
WhiteNoise 给带哈希的文件 max-age=315360000, public, immutable;不带哈希的只有 max-age=60
31600 → 219 / 75
同一 CSS:未压缩 → gzip / br 的字节数(重复内容的合成文件,压缩比被夸大)
404
DEBUG=False 时 Django 不提供静态文件;WhiteNoise 也不提供 MEDIA

开发环境

实测 · 本机执行输出
  /static/css/site.css                         status 200  last-modified=Wed, 30 Sep 2026 12:56:19 GMT  content-length=31600  content-type=text/css  (响应体 31600 字节)
  带 If-Modified-Since                          status 304  content-length=0  (响应体 0 字节)
  /media/upload.txt(runserver 不服务媒体文件)         status 404  content-type=text/html; charset=utf-8  (响应体 2268 字节)

开发服务器提供静态文件只是为了方便,它没有缓存策略、也不是为生产设计的。

DEBUG=False 且无 WhiteNoise

实测 · 本机执行输出
  /static/css/site.css                         status 404  content-type=text/html; charset=utf-8  (响应体 179 字节)

这是「本地好好的,上线静态文件全 404」的经典原因。

WhiteNoise

实测 · 本机执行输出
  collected 下的文件: ['css/site.css', 'css/site.css.br', 'css/site.css.gz', 'css/site.e52e430ea85d.css', 'css/site.e52e430ea85d.css.br', 'css/site.e52e430ea85d.css.gz', 'img/tiny.251afaa41707.png', 'img/tiny.png', 'js/app.3942f0ee209d.js', 'js/app.3942f0ee209d.js.br', 'js/app.3942f0ee209d.js.gz', 'js/app.js', 'js/app.js.br', 'js/app.js.gz']
  带哈希的文件(不带 Accept-Encoding)                   status 200  cache-control=max-age=315360000, public, immutable  etag="6abd4d47-7b70"  last-modified=Wed, 30 Sep 2026 12:56:23 GMT  content-length=31600  vary=Accept-Encoding  content-type=text/css; charset="utf-8"  (响应体 31600 字节)
  带哈希 + Accept-Encoding: gzip                  status 200  cache-control=max-age=315360000, public, immutable  etag="6abd4d47-7b70"  last-modified=Wed, 30 Sep 2026 12:56:23 GMT  content-encoding=gzip  content-length=219  vary=Accept-Encoding  content-type=text/css; charset="utf-8"  (响应体 219 字节)
  带哈希 + Accept-Encoding: br, gzip              status 200  cache-control=max-age=315360000, public, immutable  etag="6abd4d47-7b70"  last-modified=Wed, 30 Sep 2026 12:56:23 GMT  content-encoding=br  content-length=75  vary=Accept-Encoding  content-type=text/css; charset="utf-8"  (响应体 75 字节)
  原名(没有哈希)                                     status 200  cache-control=max-age=60, public  etag="6abd4d47-7b70"  last-modified=Wed, 30 Sep 2026 12:56:23 GMT  content-length=31600  vary=Accept-Encoding  content-type=text/css; charset="utf-8"  (响应体 31600 字节)
  If-None-Match(带上上面的 ETag)                    status 304  cache-control=max-age=315360000, public, immutable  etag="6abd4d47-7b70"  vary=Accept-Encoding  (响应体 0 字节)
  /media/upload.txt(WhiteNoise 不服务 MEDIA)      status 404  content-type=text/html; charset=utf-8  (响应体 179 字节)

WhiteNoise 把 STATIC_ROOT 里的文件由 WSGI 应用自己提供,根据文件名里是否有哈希来选缓存策略。配合前面的 CDN,可以让 Django 进程不成为静态文件瓶颈。

压缩与条件请求

上面的输出里同一个文件在不同 Accept-Encoding 下得到不同字节数,并且带 ETag 的条件请求回 304。压缩文件是 collectstatic 时预先生成的,请求时直接读,没有实时压缩的 CPU 开销。

媒体文件怎么办

用户上传的文件(MEDIA_ROOT)不应该由 Django 进程提供:让 nginx 直接读 MEDIA_ROOT,或用对象存储加 CDN(本文没有测)。

分步演示:静态与媒体的响应头

静态与媒体缓存
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。WhiteNoise 6.12.0、brotli 1.2.0、gunicorn 26.2 单 worker;请求用 http.client
    • 没有验证:nginx 的静态与 X-Accel-Redirect、CDN 的行为、WhiteNoise 的 max-age 配置项(WHITENOISE_MAX_AGE)、immutable 对各浏览器的支持、私有媒体文件的鉴权下载(第 133 篇)
    京ICP备2026067502号-1京公网安备11010502063487号