Accept 告诉服务端「我能看什么格式」,用 q 值排优先级。
它是最典型的内容协商字段:同一份数据,可以有多种交付形式。
通用首部讲完了,从这一节开始是 19 个请求首部。第一个就是协商家族的老大。
一、q 值怎么读
值是一串媒体类型,用逗号分隔,每个后面可以跟 ;q=0.x。q 是 0 到 1 之间的权重,越大越想要。
text/html, application/json;q=0.5 读作「HTML 最好(默认 q=1),JSON 也能凑合」。
Demo 里六组请求,服务端每次都按 q 值排序挑第一个。
q 只到小数点后三位。
写 q=0.1234 是非法的,多出来的位会被忽略或者整条解析失败——具体怎么处理看实现。
二、服务端挑不到怎么办
Demo 里有一组请求点名要 application/xml,服务端只支持三种格式。
结果是回 200,正文用了默认的 text/plain。
严格说这时候该回 406 Not Acceptable。
但现实里几乎没人这么做——因为回 406 对用户意味着「打不开」,而给一个能看的替代品通常更善意。
这也是内容协商这套机制的一个现实特点:它是个建议,不是合同。
服务端可以做它认为合理的决定,只要在 Vary 里说清依据就行。
三、三个容易忽略的细节
通配符只能用在后半段:text/* 合法,*/html 不合法。*/* 表示什么都行。
更具体的类型优先:text/html 和 text/* 同时出现时,即使 q 值一样,也该挑更具体的那个。
Accept 只管「媒体类型」,不保证服务端一定实现。
你得在响应里看实际的 Content-Type,不能假设它等于请求里排第一的那个。
四、动手 Demo:六组 Accept,看服务端怎么选
实现一个极简的协商:把 Accept 拆成「媒体类型 + q 值」的列表,按 q 从大到小排, 挑第一个服务端支持的。
def responder(raw, st):
first, hd, _b = H.parse_headers(raw)
cands = parse_q(hd.get(u'accept', u'*/*'))
st[u'cands'] = cands
cands.sort(key=lambda x: -x[1]) # q 大的在前
best = cands[0][0] if cands else u'*/*'
if best in (u'application/json', u'*/*'):
body, ct = b'{"ok":true}', u'application/json'
elif best in (u'text/html', u'application/xhtml+xml'):
body, ct = b'<html><body>ok</body></html>', u'text/html'
else:
body, ct = b'ok', u'text/plain'
return reply(u'HTTP/1.1 200 OK', [(u'Content-Type', ct)], body)
实跑输出:
--------------------------------------------------------------------
Accept 的 q 值:同一份数据,四种交付格式
--------------------------------------------------------------------
【只要 HTML】
Accept text/html
解析出的候选 text/html(q=1)
服务端选了 text/html
响应 HTTP/1.1 200 OK | 140 字节
【只要 JSON】
Accept application/json
解析出的候选 application/json(q=1)
服务端选了 application/json
响应 HTTP/1.1 200 OK | 130 字节
【HTML 优先,JSON 次之】
Accept text/html, application/json;q=0.5
解析出的候选 text/html(q=1) | application/json(q=0.5)
服务端选了 text/html
响应 HTTP/1.1 200 OK | 140 字节
【JSON 优先,HTML 次之】
Accept application/json, text/html;q=0.5
解析出的候选 application/json(q=1) | text/html(q=0.5)
服务端选了 application/json
响应 HTTP/1.1 200 OK | 130 字节
【什么都行】
Accept */*
解析出的候选 */*(q=1)
服务端选了 application/json
响应 HTTP/1.1 200 OK | 130 字节
【点名要一个服务端没有的】
Accept application/xml
解析出的候选 application/xml(q=1)
服务端选了 text/plain
响应 HTTP/1.1 200 OK | 114 字节
q 值是「偏好权重」,0 到 1 之间,越大越想要。
`text/html, application/json;q=0.5` 的意思是「HTML 最好,JSON 也能凑合」。
最后那个 `application/xml` 服务端给不了,实际会退到默认格式回 200——
严格的做法是回 406 Not Acceptable,但现实里几乎没人这么做,
因为回 406 对用户来说等于「打不开」,不如给个替代品。
text/html 与 application/json 各自单发时,服务端都照着给,响应大小分别是 140 和 130 字节。
两组带 q 值的对照很能说明问题:HTML 在前选 HTML(140 字节),JSON 在前选 JSON(130 字节)。
顺序加上 q 值,就是完整的偏好表达。
*/* 那一组服务端选了 JSON——因为代码里 */* 走进了 JSON 分支。
这只是本 Demo 的默认选择,规范没规定必须选哪个。
最后一组要 application/xml,服务端没有,退到 text/plain,回 200。
114 字节的响应——比另两种都小,因为正文只有两个字节。
30 秒实验
- 把最后那组改成服务端有 xml 分支,看会不会真的给出 xml。
- 发
text/html;q=0,看服务端会不会刻意避开 HTML。 - 想一想:为什么
Vary: Accept是必需的?不加会出什么事?
常见误区
- 「Accept 决定服务端返回什么」——它只是表达客户端偏好,服务端可以不听。
- 「q 值是百分比」——是 0 到 1 的权重,不是百分数。
- 「请求里排第一的一定会拿到」——还要看服务端支持什么。
自测三问(点开对答案)
1. `text/html, application/json;q=0.5` 是什么意思? —— HTML 优先(q=1),JSON 次之(q=0.5),两者都能接受。 2. 服务端没有任何客户端要的格式时,严格说该回什么?现实中多回什么? —— 严格说回 406 Not Acceptable;现实中多回 200 加一个默认格式。 3. 通配符 `*/html` 合法吗? —— 不合法。通配符只能出现在后半段,比如 `text/*`。格式协商说完了。接下来三个都是同一族的:字符集、编码、语言。
- ☐ (本节完,下一节 6.4.2 Accept-Charset)