静态文件放哪里、怎么缓存;用户上传的媒体文件又该怎么办?这篇分别在 runserver、没有 WhiteNoise 的 gunicorn、有 WhiteNoise 的 gunicorn 三种环境里请求同一批文件,直接对比响应头。
/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 字节)
开发服务器提供静态文件只是为了方便,它没有缓存策略、也不是为生产设计的。
/static/css/site.css status 404 content-type=text/html; charset=utf-8 (响应体 179 字节)
这是「本地好好的,上线静态文件全 404」的经典原因。
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(本文没有测)。