You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 15 | 海纳百川HTTP的实体数据
你好我是Chrono。
今天我要与你分享的话题是“海纳百川HTTP的实体数据”。
这一讲是“进阶篇”的第一讲从今天开始我会用连续的8讲的篇幅来详细解析HTTP协议里的各种头字段包括定义、功能、使用方式、注意事项等等。学完了这些课程你就可以完全掌握HTTP协议。
在前面的“基础篇”里我们了解了HTTP报文的结构知道一个HTTP报文是由“header+body”组成的。但那时我们主要研究的是header没有涉及到body。所以“进阶篇”的第一讲就从HTTP的body谈起。
## 数据类型与编码
在TCP/IP协议栈里传输数据基本上都是“header+body”的格式。但TCP、UDP因为是传输层的协议它们不会关心body数据是什么只要把数据发送到对方就算是完成了任务。
而HTTP协议则不同它是应用层的协议数据到达之后工作只能说是完成了一半还必须要告诉上层应用这是什么数据才行否则上层应用就会“不知所措”。
你可以设想一下假如HTTP没有告知数据类型的功能服务器把“一大坨”数据发给了浏览器浏览器看到的是一个“黑盒子”这时候该怎么办呢
当然它可以“猜”。因为很多数据都是有固定格式的所以通过检查数据的前几个字节也许就能知道这是个GIF图片、或者是个MP3音乐文件但这种方式无疑十分低效而且有很大几率会检查不出来文件类型。
幸运的是早在HTTP协议诞生之前就已经有了针对这种问题的解决方案不过它是用在电子邮件系统里的让电子邮件可以发送ASCII码以外的任意数据方案的名字叫做“**多用途互联网邮件扩展**”Multipurpose Internet Mail Extensions简称为MIME。
MIME是一个很大的标准规范但HTTP只“顺手牵羊”取了其中的一部分用来标记body的数据类型这就是我们平常总能听到的“**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/jsonapplication/javascript、application/pdf等另外如果实在是不知道数据是什么类型像刚才说的“黑盒”就会是application/octet-stream即不透明的二进制数据。
但仅有MIME type还不够因为HTTP在传输时为了节约带宽有时候还会压缩数据为了不要让浏览器继续“猜”还需要有一个“Encoding type”告诉数据是用的什么编码格式这样对方才能正确解压缩还原出原始的数据。
比起MIME type来说Encoding type就少了很多常用的只有下面三种
1. gzipGNU zip压缩格式也是互联网上最流行的压缩格式
2. deflatezlibdeflate压缩格式流行程度仅次于gzip
3. br一种专门为HTTP优化的新压缩算法Brotli
## 数据类型使用的头字段
有了MIME type和Encoding type无论是浏览器还是服务器就都可以轻松识别出body的类型也就能够正确处理数据了。
HTTP协议为此定义了两个Accept请求头字段和两个Content实体头字段用于客户端和服务器进行“**内容协商**”。也就是说客户端用Accept头告诉服务器希望接收什么样的数据而服务器用Content头告诉客户端实际发送了什么样的数据。
![](https://static001.geekbang.org/resource/image/51/b9/5191bce1329efa157a6cc37ab9e789b9.png)
**Accept**字段标记的是客户端可理解的MIME type可以用“,”做分隔符列出多个类型,让服务器有更多的选择余地,例如下面的这个头:
```
Accept: text/html,application/xml,image/webp,image/png
```
这就是告诉服务器“我能够看懂HTML、XML的文本还有webp和png的图片请给我这四类格式的数据”。
相应的,服务器会在响应报文里用头字段**Content-Type**告诉实体数据的真实类型:
```
Content-Type: text/html
Content-Type: image/png
```
这样浏览器看到报文里的类型是“text/html”就知道是HTML文件会调用排版引擎渲染出页面看到“image/png”就知道是一个PNG文件就会在页面上显示出图像。
**Accept-Encoding**字段标记的是客户端支持的压缩格式例如上面说的gzip、deflate等同样也可以用“,”列出多个,服务器可以选择其中一种来压缩数据,实际使用的压缩格式放在响应头字段**Content-Encoding**里。
```
Accept-Encoding: gzip, deflate, br
Content-Encoding: gzip
```
不过这两个字段是可以省略的如果请求报文里没有Accept-Encoding字段就表示客户端不支持压缩数据如果响应报文里没有Content-Encoding字段就表示响应数据没有被压缩。
## 语言类型与编码
MIME type和Encoding type解决了计算机理解body数据的问题但互联网遍布全球不同国家不同地区的人使用了很多不同的语言虽然都是text/html但如何让浏览器显示出每个人都可理解可阅读的语言文字呢
这实际上就是“国际化”的问题。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**字段标记了客户端可理解的自然语言,也允许用“,”做分隔符列出多个类型,例如:
```
Accept-Language: zh-CN, zh, en
```
这个请求头会告诉服务器“最好给我zh-CN的汉语文字如果没有就用其他的汉语方言如果还没有就给英文”。
相应的,服务器应该在响应报文里用头字段**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字段。
![](https://static001.geekbang.org/resource/image/0e/10/0e9bcd6922fa8908bdba79d98ae5fa10.png)
## 内容协商的质量值
在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可能会有多个不同的“版本”主要用在传输链路中间的代理服务器实现缓存服务这个之后讲“HTTP缓存”时还会再提到。
## 动手实验
上面讲完了理论部分接下来就是实际动手操作了。可以用我们的实验环境在www目录下有一个mime目录里面预先存放了几个文件可以用URI“/15-1?name=file”的形式访问例如
```
http://www.chrono.com/15-1?name=a.json
http://www.chrono.com/15-1?name=a.xml
```
在Chrome里打开开发者工具就能够看到Accept和Content头
![](https://static001.geekbang.org/resource/image/b4/9e/b4d1671872876a60ad717821f0fb819e.png)
你也可以把任意的文件拷贝到mime目录下比如压缩包、MP3、图片、视频等再用Chrome访问观察更多的MIME type。
有了这些经验后你还可以离开实验环境直接访问各大门户网站看看真实网络世界里的HTTP报文是什么样子的。
## 小结
今天我们学习了HTTP里的数据类型和语言类型在这里为今天的内容做个小结。
![](https://static001.geekbang.org/resource/image/b2/58/b2118315a977969ddfcc7ab9d26cb358.png)
1. 数据类型表示实体数据的内容是什么使用的是MIME type相关的头字段是Accept和Content-Type
2. 数据编码表示实体数据的压缩方式相关的头字段是Accept-Encoding和Content-Encoding
3. 语言类型表示实体数据的自然语言相关的头字段是Accept-Language和Content-Language
4. 字符集表示实体数据的编码方式相关的头字段是Accept-Charset和Content-Type
5. 客户端需要在请求头里使用Accept等头字段与服务器进行“内容协商”要求服务器返回最合适的数据
6. Accept等头字段可以用“,”顺序列出多个可能的选项,还可以用“;q=”参数来精确指定权重。
## 课下作业
1. 试着解释一下这个请求头“Accept-Encoding: gzip, deflate;q=1.0, \*;q=0.5, br;q=0”再模拟一下服务器的响应头。
2. 假设你要使用POST方法向服务器提交一些JSON格式的数据里面包含有中文请求头应该是什么样子的呢
3. 试着用快递发货收货比喻一下MIME、Encoding等概念。
欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,欢迎你把文章分享给你的朋友。