Accept-Charset 协商的是字符集。
它今天基本已经废了——浏览器只发 utf-8,规范也把它的请求语义移除了。
上一节协商格式,这一节协商字符集。两个字段长得很像,但这一个的结局是被时代淘汰。
一、字符集与编码的区别
字符集是一组符号的集合,编码是把这些符号变成字节的规则。 说「utf-8」时严格讲说的是编码,但 HTTP 里这个词一直混着用。 Demo 里那句「中文测试 abc」,用 UTF-8 编是 16 字节,用 GBK 编是 12 字节。 同一句话,两套字节,长度都不一样。 这就是协商的意义:如果服务端按 GBK 发,客户端按 UTF-8 解,看到的就是一堆乱码。 字符集错了不是「显示难看」,是「读不出来」。
二、为什么它死了
因为 UTF-8 赢了。2010 年代之后,全球绝大多数网站都用 UTF-8,浏览器也就懒得再发这个字段。
2014 年的 RFC 7231 干脆把 Accept-Charset 从请求语义里移除,同时规定:服务端可以忽略它,
客户端也不该再发。这个字段现在只剩下「历史兼容」的意义。
顺带一个相关的变化:响应里的 Content-Type 至今仍然带 charset 参数,这一半还活着。
所以字符集这件事没有消失,只是不再协商了——直接约定用 UTF-8。
三、一个常被忽略的事实
Demo 里客户端点名要 us-ascii,服务端最后给了 utf-8。
这不是服务端耍赖——是因为正文里有中文,ASCII 根本编不出来。
编码能力是有边界的:七个比特装不下一个汉字。
同理,如果客户端点名要 GBK 而服务端的内容里有生僻字,也会编不出来。
所以「协商」在这件事上天生不可靠:能不能编出来,取决于内容,而不取决于双方意愿。
这也是 UTF-8 胜出的根本原因:它是唯一能装下所有字符的编码,协商就没有必要了。
四、动手 Demo:四组请求,看服务端怎么选字符集
准备同一句话的三个编码版本(UTF-8、GBK、以及一个纯 ASCII 的替代文本),按客户端点名优先, 挑第一个服务端能编出来的。
ENC = {u'utf-8': SAMPLE.encode(u'utf-8'),
u'gbk': SAMPLE.encode(u'gbk'),
u'iso-8859-1': u'latin only abc'.encode(u'latin-1')}
want = [x.strip().lower() for x in ac.split(u',') if x.strip()]
chosen = u'utf-8'
for w in want:
w = w.split(u';')[0]
if w in ENC:
chosen = w
break
body = ENC[chosen]
return reply(u'HTTP/1.1 200 OK',
[(u'Content-Type', u'text/plain; charset=' + chosen)],
body)
实跑输出:
--------------------------------------------------------------------
Accept-Charset:字符集协商,一个已经过时的字段
--------------------------------------------------------------------
【点名要 utf-8】
Accept-Charset utf-8
选中的字符集 utf-8
响应 Content-Type text/plain; charset=utf-8
正文字节数 16
按该字符集解回 中文测试 abc
【点名要 gbk】
Accept-Charset gbk
选中的字符集 gbk
响应 Content-Type text/plain; charset=gbk
正文字节数 12
按该字符集解回 中文测试 abc
【点名要 ascii】
Accept-Charset us-ascii
选中的字符集 utf-8
响应 Content-Type text/plain; charset=utf-8
正文字节数 16
按该字符集解回 中文测试 abc
【什么都不发】
Accept-Charset (没发)
选中的字符集 utf-8
响应 Content-Type text/plain; charset=utf-8
正文字节数 16
按该字符集解回 中文测试 abc
「中文测试 abc」这句话,用 utf-8 编是 16 字节,用 gbk 编是 12 字节,用 iso-8859-1 根本编不出来。
所以字符集协商的本质是:**同一个字符串,不同编码的字节完全不同**。
发错编码的后果不是显示难看,而是直接乱码或解不出来。
这个字段今天的处境很尴尬:几乎所有浏览器都只发 `utf-8`,
于是 2014 年的 RFC 7231 干脆把它从请求语义里移除,
现在只剩「响应里的 Content-Type 带 charset」这一半还在用。
要 UTF-8:16 字节,解回来是「中文测试 abc」。
要 GBK:12 字节。同样一句话,GBK 更省——因为汉字在 GBK 里占两字节,在 UTF-8 里占三字节。
这也是早年中文网站偏爱 GBK 的原因。
要 us-ascii:退到 UTF-8,16 字节。因为那句话有中文,ASCII 编不出来。
什么都不发:默认 UTF-8,16 字节。这就是今天所有浏览器的实际行为。
30 秒实验
- 把 SAMPLE 换成纯英文,再要
us-ascii,看能不能命中。 - 加一个
gb2312的候选(GBK 的子集),看该不该接受。 - 想一想:如果服务端的内容是多语言的,还能用 GBK 吗?
常见误区
- 「字符集和编码是一回事」——严格说是两件,虽然 HTTP 里混着用。
- 「
Accept-Charset还广泛使用」——2014 年就被从请求语义里移除了。 - 「客户端点名要什么就一定给什么」——编不出来的时候给不了。
自测三问(点开对答案)
1. 同一句中文,UTF-8 和 GBK 哪个更长? —— UTF-8。汉字在 UTF-8 里通常三字节,在 GBK 里两字节。 2. 为什么 `Accept-Charset` 基本没用了? —— UTF-8 成了事实标准,浏览器不再发这个字段,RFC 7231 也移除了它的请求语义。 3. 客户端要 `us-ascii` 而内容里有中文,会怎样? —— 给不了。ASCII 只有七位,编不出汉字,服务端只能改用别的编码。字符集说完,下一个是压缩——这个字段今天很活跃。
- ☐ (本节完,下一节 6.4.3 Accept-Encoding)