阅读 104

透视HTTP协议-HTTP的实体数据

HTTP的实体数据(body)

在TCP/IP协议栈里,传输数据基本上都是"header+body"的格式。由于HTTP是应用层的协议,所以必须告诉上层应用这是什么数据才行;而TCP、UDP这些传输层的协议则不会关心body数据是什么。

数据类型与编码

MIME(Multipurpose Internet Mail Extention)多用途互联网邮件扩展,用在电子邮件系统里的,让电子邮件可以发送ASCII码以外的任意数据。HTTP”顺手牵羊“取了其中的一部分,用来标记body的数据类型,即”MIME type“

MIME把数据分成了八大类,每个大类下再细分出多个子类,形式是”type/subtype“的字符串。

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,appllication/javascript、application/pdf等,另外,如果实在是不知道数据是什么类型,像刚才说的“黑盒”,就会是 application/octet-stream,即不透明的二进制数据。

另外因为HTTP在传输时为了节约带宽,有时候还会压缩数据,为了不要让浏览器继续“猜”,还需要有一个“Encoding type”,告诉数据是用的什么编码格式,这样对方才能正确解压缩。

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

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

数据类型使用的头字段

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

Accept字段标记的是客户端可理解的MIME type,可以用“,”做分隔符列出多个类型,例:

Accept: text/html,application/xml,image/webp,image/png
复制代码

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

Content-Type: text/html
Content-Type: image/png
复制代码

Accept-Encoding字段标记的是客户端支持的压缩格式,可以用","列出多个,服务器可以选择其中一种来压缩数据,实际使用的压缩格式放在响应头字段Content-Encoding里。

Accept-Encoding: gzip, deflate, br
Content-Encoding: gzip
复制代码

语言类型与编码

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

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

语言类型使用的头字段

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

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

Accept-Language: zh-CN, zh, en
复制代码

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

Content-Language: zh-CN
复制代码

字符集在 HTTP 里使用的请求头字段是 Accept-Charset,但响应头里却没有对应的 Content-Charset,而是在 Content-Type 字段的数据类型后面用“charset=xxx”来表示,这点需要特别注意。例如,浏览器请求 GBK 或 UTF-8 的字符集,然后服务器返回的是 UTF-8 编码,就是下面这样:

Accept-Charset: gbk, utf-8
Content-Type: text/html; charset=utf-8
复制代码

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

内容协商的质量值

在 HTTP 协议里用 Accept、Accept-Encoding、Accept-Language 等请求头字段进行内容协商的时候,还可以用一种特殊的“q”参数表示权重来设定优先级,这里的“q”是“quality factor”的意思。

权重的最大值是 1,最小值是 0.01,默认值是 1,如果值是 0 就表示拒绝。具体的形式是在数据类型或语言代码后面加一个“;”,然后是“q=value”。

这里要提醒的是“;”的用法,在大多数编程语言里“;”的断句语气要强于“,”,而在 HTTP 的内容协商里却恰好反了过来,“;”的意义是小于“,”的。

例如下面的 Accept 字段:


Accept: text/html,application/xml;q=0.9,*/*;q=0.8
复制代码

它表示浏览器最希望使用的是 HTML 文件,权重是 1,其次是 XML 文件,权重是 0.9,最后是任意数据类型,权重是 0.8。服务器收到请求头后,就会计算权重,再根据自己的实际情况优先输出 HTML 或者 XML。

内容协商的结果

内容协商的过程是不透明的,每个 Web 服务器使用的算法都不一样。但有的时候,服务器会在响应头里多加一个 Vary 字段,记录服务器在内容协商时参考的请求头字段,给出一点信息,例如:

Vary: Accept-Encoding,User-Agent,Accept
复制代码

这个 Vary 字段表示服务器依据了 Accept-Encoding、User-Agent 和 Accept 这三个头字段,然后决定了发回的响应报文。

Vary 字段可以认为是响应报文的一个特殊的“版本标记”。每当 Accept 等请求头变化时,Vary 也会随着响应报文一起变化。也就是说,同一个 URI 可能会有多个不同的“版本”,主要用在传输链路中间的代理服务器实现缓存服务。

Q&A

Q:试着解释一下这个请求头“Accept-Encoding: gzip, deflate;q=1.0, *;q=0.5, br;q=0”,再模拟一下服务器的响应头。

Q:假设你要使用 POST 方法向服务器提交一些 JSON 格式的数据,里面包含有中文,请求头应该是什么样子的呢?

Q:试着用快递发货收货比喻一下 MIME、Encoding 等概念。