bigywater.tech
Flask · 22

静态文件路由的注册方式

/static 是怎么注册的?能不能被路径穿越?浏览器缓存怎么处理?怎么改目录或前缀?这篇逐项实测,并量一下 Flask 自己发静态文件的开销。

0
各种 ../ 写法里成功读到 static 目录之外文件的个数
304
带 If-None-Match 或 If-Modified-Since 时的状态码
7.2×
Flask 发 10 KB 静态文件相对直接读文件的耗时倍数

默认的 /static

实测 · 本机执行输出
  GET /static/app.js         → 200  Content-Type: text/javascript; charset=utf-8 | Content-Length: 18
  GET /static/css/site.css   → 200  Content-Type: text/css; charset=utf-8 | Content-Length: 16
  GET /static/nope.js        → 404  Content-Type: text/html; charset=utf-8 | Content-Length: 207
  GET /static/css/           → 404  Content-Type: text/html; charset=utf-8 | Content-Length: 207
  GET /static/.hidden        → 200  Content-Type: application/octet-stream | Content-Length: 1

上一篇:url_for() 如何反向生成 URL;下一篇:HTTP 方法限制。ETag 与条件请求的专题:ETag 与条件请求。

路径穿越

实测 · 本机执行输出
  GET /static/../secret.txt              → 404
  GET /static/%2e%2e/secret.txt          → 404
  GET /static/css/../../secret.txt       → 404
  GET /static/..%2fsecret.txt            → 404
  GET /static//etc/passwd                → 308 Location=http://localhost/static/etc/passwd
  同一个文件直接请求 /static/app.js → 200 | secret.txt 在 static 目录之外,没有任何一种写法读到: True

缓存头

实测 · 本机执行输出
  首次: 200  Content-Type: text/javascript; charset=utf-8 | Content-Length: 18 | Cache-Control: no-cache | ETag: "1790910641.6606114-18-3612814441" | Last-Modified: Fri, 02 Oct 2026 03:10:41 GMT | Accept-Ranges: bytes
  If-None-Match = "1790910641.6606114-18-3612814441" → 304 | 正文长度 0
  If-Modified-Since = Fri, 02 Oct 2026 03:10:41 GMT → 304
  config SEND_FILE_MAX_AGE_DEFAULT = None (None → Cache-Control: no-cache)
  设成 3600 后: 200  Cache-Control: public, max-age=3600 | Expires: Fri, 02 Oct 2026 04:10:41 GMT

Range 与 HEAD

实测 · 本机执行输出
  Range: bytes=0-99 → 206  Content-Type: application/octet-stream | Content-Length: 100 | Cache-Control: no-cache | ETag: "1790910641.6608205-10000-62991542" | Last-Modified: Fri, 02 Oct 2026 03:10:41 GMT | Content-Range: bytes 0-99/10000 | Accept-Ranges: bytes
  HEAD → 200  Content-Type: text/javascript; charset=utf-8 | Content-Length: 18 | Cache-Control: no-cache | ETag: "1790910641.6606114-18-3612814441" | Last-Modified: Fri, 02 Oct 2026 03:10:41 GMT | Accept-Ranges: bytes | 正文长度 0

自定义目录与前缀

实测 · 本机执行输出
  app2(static_folder="assets", static_url_path="/files"): {'/files/logo.txt': 200, '/static/logo.txt': 404}
  app2 的规则: ['/files/<path:filename>']
  app3(static_folder=None):规则 = [] | GET /static/app.js → 404
  app4(static_url_path=""): {'/app.js': 200, '/css/site.css': 200, '/nope': 404} | 规则 = ['/<path:filename>']

开销

实测 · 本机执行输出
  Flask /static 每个请求 144.1 µs;直接 open().read() 同一个文件 20.1 µs

分步演示

静态文件
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动
    • 没有验证:符号链接指向 static 目录之外时的行为、Windows 路径分隔符、真实浏览器的缓存表现、Nginx 直接发静态文件的耗时对比(见第 176 篇)、gzip 与 Content-Encoding
    • ETag 的具体数值(含文件修改时间)每次运行不同,文中只写它的形式
    京ICP备2026067502号-1京公网安备11010502063487号