数据类型与编码

HTTP借鉴了“多用途互联网邮件扩展”(Multipurpose Internet Mail Extensions)这个方案,从中借用了“MIME type”。

MIME 把数据分成了八大类,每个大类下再细分出多个子类,形式是“type/subtype”的字符串,巧得很,刚好也符合了 HTTP 明文的特点,所以能够很容易地纳入 HTTP 头字段里。

这里简单列举一下在 HTTP 里经常遇到的几个类别:

  1. text:即文本格式的可读数据,我们最熟悉的应该就是 text/html 了,表示超文本文档,此外还有纯文本 text/plain、样式表 text/css 等。
  2. image:即图像文件,有 image/gif、image/jpeg、image/png 等。
  3. audio/video:音频和视频数据,例如 audio/mpeg、video/mp4 等。
  4. application:数据格式不固定,可能是文本也可能是二进制,必须由上层应用程序来解释。常见的有 application/json,application/javascript、application/pdf 等,另外,如果实在是不知道数据是什么类型,就会是 application/octet-stream,即不透明的二进制数据。

但仅仅有MIME type还不够,因为HTTP在传输时为了节约宽带,有时会压缩数据,为了方便浏览器快速解析,还需要一个“Encoding type”,告诉数据是用的什么编码格式,这样对方才能正确解压缩,还原出原始的数据。

常用 Encoding type 的只有下面三种:

  1. gzip:GNU zip 压缩格式,也是互联网上最流行的压缩格式;
  2. deflate:zlib(deflate)压缩格式,流行程度仅次于 gzip;
  3. br:一种专门为 HTTP 优化的新压缩算法(Brotli)。

数据类型使用的头字段

通过 MIME type 和 Encoding type,浏览器和服务器之间就可以识别传递资源的类型了。

又因为 HTTP 协议的协商性质,HTTP 协议又定义了两个 Accept 请求头字段和两个 Content 实体头字段,用于客户端和服务器进行“内容协商”。也就是说,客户端用 Accept 头告诉服务器希望接收什么样的数据,而服务器用 Content 头告诉客户端实际发送了什么样的数据。

HTTP的实体数据 - 图1

Accept 字段标记的是客户端可理解的 MIME type,可以用“,”做分隔符列出多个类型,让服务器有更多的选择余地,例如下面的这个头:

  1. Accept: text/html,application/xml,image/webp,image/png

这是跟服务器说:“我能处理html、xml文件和webp、png的图片,请给我这四种格式的数据”

相应的,服务器会在响应报文里用头字段 Content-Type 告诉实体数据的真是类型

  1. Content-Type: text/html
  2. Content-Type: image/png

这样浏览器看到报文里的类型是“text/html”就知道是 HTML 文件,会调用排版引擎渲染出页面,看到“image/png”就知道是一个 PNG 文件,就会在页面上显示出图像。

Accept-Encoding 字段标记的是客户端支持的压缩格式,例如上面说的 gzip、deflate 等,同样也可以用“,”列出多个,服务器可以选择其中一种来压缩数据,实际使用的压缩格式放在响应头字段 Content-Encoding 里。

  1. Accept-Encoding: gzip, deflate, br
  2. Content-Encoding: gzip

不过这两个字段是可以省略的,如果请求报文里没有 Accept-Encoding 字段,就表示客户端不支持压缩数据;如果响应报文里没有 Content-Encoding 字段,就表示响应数据没有被压缩。

语言类型与编码

因为互联网遍布全球,而不同国家地区的人使用不同的语言,所以相应的互联网资源也要标识出自己的语言。

HTTP引入了两个概念:语言类型与字符集。

所谓的“语言类型”就是人类使用的自然语言,例如英语、汉语、日语等,而这些自然语言可能还有下属的地区性方言,所以在需要明确区分的时候也要使用“type-subtype”的形式,不过这里的格式与数据类型不同,分隔符不是“/”,而是“-”。

举几个例子:en 表示任意的英语,en-US 表示美式英语,en-GB 表示英式英语,而 zh-CN 就表示我们最常使用的汉语。

关于自然语言的计算机处理还有一个更麻烦的东西叫做“字符集”。

在计算机发展的早期,各个国家和地区的人们“各自为政”,发明了许多字符编码方式来处理文字,比如英语世界用的 ASCII、汉语世界用的 GBK、BIG5,日语世界用的 Shift_JIS 等。同样的一段文字,用一种编码显示正常,换另一种编码后可能就会变得一团糟。

所以后来就出现了 Unicode 和 UTF-8,把世界上所有的语言都容纳在一种编码方案里,遵循 UTF-8 字符编码方式的 Unicode 字符集也成为了互联网上的标准字符集。

语言类型使用的头字段

同样的,HTTP 协议也使用 Accept 请求头字段和 Content 实体头字段,用于客户端和服务器就语言与编码进行“内容协商”。

Accept-Language 字段标记了客户端可理解的自然语言,也允许用“,”做分隔符列出多个类型,例如:

  1. Accept-Language: zh-CN, zh, en

这个请求头会告诉服务器:“最好给我 zh-CN 的汉语文字,如果没有就用其他的汉语方言,如果还没有就给英文”。

相应的,服务器应该在响应报文里用头字段 Content-Language 告诉客户端实体数据使用的实际语言类型:

  1. Content-Language: zh-CN

字符集在 HTTP 里使用的请求头字段是 Accept-Charset,但响应头里却没有对应的 Content-Charset,而是在 Content-Type 字段的数据类型后面用“charset=xxx”来表示,这点需要特别注意。

例如,浏览器请求 GBK 或 UTF-8 的字符集,然后服务器返回的是 UTF-8 编码,就是下面这样:

  1. Accept-Charset: gbk, utf-8
  2. Content-Type: text/html; charset=utf-8

不过现在的浏览器都支持多种字符集,通常不会发送 Accept-Charset,而服务器也不会发送 Content-Language,因为使用的语言完全可以由字符集推断出来,所以在请求头里一般只会有 Accept-Language 字段,响应头里只会有 Content-Type 字段。

小结:

HTTP的实体数据 - 图2

  1. 数据类型表示实体数据的内容是什么,使用的是 MIME type,相关的头字段是 Accept 和 Content-Type;
  2. 数据编码表示实体数据的压缩方式,相关的头字段是 Accept-Encoding 和 Content-Encoding;
  3. 语言类型表示实体数据的自然语言,相关的头字段是 Accept-Language 和 Content-Language;
  4. 字符集表示实体数据的编码方式,相关的头字段是 Accept-Charset 和 Content-Type;
  5. 客户端需要在请求头里使用 Accept 等头字段与服务器进行“内容协商”,要求服务器返回最合适的数据;