bigywater.tech
Flask · 50

Content Negotiation

同一个地址,浏览器要 HTML、脚本要 JSON,怎么区分?Accept 里的 q 值怎么比?没有 Accept 头会怎样?语言怎么匹配?这篇逐项实测。

406
没有可用类型时的状态码
Vary: Accept
按 Accept 变化的响应必须加的头
*/*
curl 与 requests 的默认 Accept

Accept 的解析

实测 · 本机执行输出
  Accept                                                 → 服务端提供 ['application/json', 'text/html', 'text/csv'] 时选择
  */*                                                    → 200 application/json   curl 默认
  application/json                                       → 200 application/json   
  text/html                                              → 200 text/html; charset=utf-8   
  text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 → 200 text/html; charset=utf-8   Chrome 的导航请求
  application/json, text/html                            → 200 application/json   同 q 值
  text/html, application/json                            → 200 application/json   同 q 值,顺序相反
  application/json;q=0.5, text/html;q=0.9                → 200 text/html; charset=utf-8   
  text/*                                                 → 200 text/html; charset=utf-8   
  image/png                                              → 406 application/json   服务端没有的
  application/json;q=0                                   → 406 application/json   q=0 表示不接受
  text/html;q=0, */*;q=0.1                               → 200 application/json   
  (无)                                                    → 406 application/json   没有 Accept 头
  garbage                                                → 406 application/json   

上一篇:Range 请求与断点下载;下一篇:Jinja2 Template Loader。

accept_mimetypes 的接口

实测 · 本机执行输出
   {'accept_html': True, 'accept_json': True, 'best': 'text/html', 'json_in': True, 'quality_json': 0.8, 'values': [['text/html', 1], ['application/xhtml+xml', 1], ['application/xml', 0.9], ['*/*', 0.8]]}

406 与 Vary

实测 · 本机执行输出
  Accept: image/png → 406 {'error': 'not acceptable', 'offered': ['application/json', 'text/html', 'text/csv']} | Vary = Accept
  Accept: application/json → Vary = Accept

Accept-Language

实测 · 本机执行输出
  Accept-Language: zh-CN,zh;q=0.9,en;q=0.8    → best_match(zh-CN, zh-TW, en, en-GB, ja; default=en) = zh-CN
  Accept-Language: zh                         → best_match(zh-CN, zh-TW, en, en-GB, ja; default=en) = zh-CN
  Accept-Language: zh-TW                      → best_match(zh-CN, zh-TW, en, en-GB, ja; default=en) = zh-TW
  Accept-Language: en-US,en;q=0.9             → best_match(zh-CN, zh-TW, en, en-GB, ja; default=en) = en
  Accept-Language: fr                         → best_match(zh-CN, zh-TW, en, en-GB, ja; default=en) = en
  Accept-Language: ja;q=0.5, en;q=0.9         → best_match(zh-CN, zh-TW, en, en-GB, ja; default=en) = en
  Accept-Language: *                          → best_match(zh-CN, zh-TW, en, en-GB, ja; default=en) = zh-CN
  Accept-Language: (无)                        → best_match(zh-CN, zh-TW, en, en-GB, ja; default=en) = en

Accept-Encoding

实测 · 本机执行输出
  gzip, deflate, br                  → gzip q=1 br q=1 identity q=0
  gzip;q=0, identity                 → gzip q=0.0 br q=0 identity q=1
  br;q=1.0, gzip;q=0.8, *;q=0.1      → gzip q=0.8 br q=1.0 identity q=0.1
  (无)                                → gzip q=0 br q=0 identity q=0

URL 后缀

实测 · 本机执行输出
  GET /ext.json  → 200 application/json
  GET /ext.html  → 200 text/html; charset=utf-8
  GET /ext.xml   → 404 text/html; charset=utf-8

真实客户端的 Accept

实测 · 本机执行输出
  curl 默认: {"accept":"*/*","ae":null,"ua":"curl/8.7.1"}
  requests 默认: {"accept":"*/*","ae":"gzip, deflate, zstd","ua":"python-requests/2.34.2"}

分步演示

内容协商
本步的对象

    本文用到的工具与边界

    • 本机 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,数字每次运行会有小幅抖动。requests 2.34.2
    • 没有验证:Accept-Charset(Werkzeug 3 已不再提供)、API 版本通过 Accept 的 vendor 类型协商(见第 105 篇)、浏览器对 Accept-Language 的真实排序、响应压缩(gzip)的实际实现
    京ICP备2026067502号-1京公网安备11010502063487号