This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
<audio id="audio" title="08 | 键入网址再按下回车,后面究竟发生了什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4f/c4/4f223253ac4c86e4072043ab1e6134c4.mp3"></audio>
经过上一讲的学习你是否已经在自己的电脑上搭建好了“最小化”的HTTP实验环境呢
我相信你的答案一定是“Yes”那么让我们立刻开始“螺蛳壳里做道场”在这个实验环境里看一下HTTP协议工作的全过程。
## 使用IP地址访问Web服务器
首先我们运行www目录下的“start”批处理程序启动本机的OpenResty服务器启动后可以用“list”批处理确认服务是否正常运行。
然后我们打开Wireshark选择“HTTP TCP port(80)”过滤器再鼠标双击“Npcap loopback Adapter”开始抓取本机127.0.0.1地址上的网络数据。
第三步在Chrome浏览器的地址栏里输入“[http://127.0.0.1/](http://127.0.0.1/)”再按下回车键等欢迎页面显示出来后Wireshark里就会有捕获的数据包如下图所示。
<img src="https://static001.geekbang.org/resource/image/86/b0/86e3c635e9a9ab0abd523c01fc181cb0.png" alt="">
如果你还没有搭好实验环境或者捕获与本文里的不一致也没关系。我把这次捕获的数据存成了pcap包文件名是“08-1”放到了GitHub上你可以下载到本地后再用Wireshark打开完全精确“重放”刚才的HTTP传输过程。
## 抓包分析
在Wireshark里你可以看到这次一共抓到了11个包这里用了滤包功能滤掉了3个包原本是14个包耗时0.65秒,下面我们就来一起分析一下"键入网址按下回车"后数据传输的全过程。
通过前面“破冰篇”的讲解你应该知道HTTP协议是运行在TCP/IP基础上的依靠TCP/IP协议来实现数据的可靠传输。所以浏览器要用HTTP协议收发数据首先要做的就是建立TCP连接。
因为我们在地址栏里直接输入了IP地址“127.0.0.1”而Web服务器的默认端口是80所以浏览器就要依照TCP协议的规范使用“三次握手”建立与Web服务器的连接。
对应到Wireshark里就是最开始的三个抓包浏览器使用的端口是52085服务器使用的端口是80经过SYN、SYN/ACK、ACK的三个包之后浏览器与服务器的TCP连接就建立起来了。
有了可靠的TCP连接通道后HTTP协议就可以开始工作了。于是浏览器按照HTTP协议规定的格式通过TCP发送了一个“GET / HTTP/1.1”请求报文也就是Wireshark里的第四个包。至于包的内容具体是什么现在先不用管我们下一讲再说。
随后Web服务器回复了第五个包在TCP协议层面确认“刚才的报文我已经收到了”不过这个TCP包HTTP协议是看不见的。
Web服务器收到报文后在内部就要处理这个请求。同样也是依据HTTP协议的规定解析报文看看浏览器发送这个请求想要干什么。
它一看原来是要求获取根目录下的默认文件好吧那我就从磁盘上把那个文件全读出来再拼成符合HTTP格式的报文发回去吧。这就是Wireshark里的第六个包“HTTP/1.1 200 OK”底层走的还是TCP协议。
同样的浏览器也要给服务器回复一个TCP的ACK确认“你的响应报文收到了多谢”即第七个包。
这时浏览器就收到了响应数据但里面是什么呢所以也要解析报文。一看服务器给我的是个HTML文件那我就调用排版引擎、JavaScript引擎等等处理一下然后在浏览器窗口里展现出了欢迎页面。
这之后还有两个来回共四个包重复了相同的步骤。这是浏览器自动请求了作为网站图标的“favicon.ico”文件与我们输入的网址无关。但因为我们的实验环境没有这个文件所以服务器在硬盘上找不到返回了一个“404 Not Found”。
至此,“键入网址再按下回车”的全过程就结束了。
我为这个过程画了一个交互图你可以对照着看一下。不过要提醒你图里TCP关闭连接的“四次挥手”在抓包里没有出现这是因为HTTP/1.1长连接特性,默认不会立即关闭连接。
<img src="https://static001.geekbang.org/resource/image/8a/19/8a5bddd3d8046daf7032c7d60a3d1a19.png" alt="">
再简要叙述一下这次最简单的浏览器HTTP请求过程
1. 浏览器从地址栏的输入中获得服务器的IP地址和端口号
1. 浏览器用TCP的三次握手与服务器建立连接
1. 浏览器向服务器发送拼好的报文;
1. 服务器收到报文后处理请求,同样拼好报文再发给浏览器;
1. 浏览器解析报文,渲染输出页面。
## 使用域名访问Web服务器
刚才我们是在浏览器地址栏里直接输入IP地址但绝大多数情况下我们是不知道服务器IP地址的使用的是域名那么改用域名后这个过程会有什么不同吗
还是实际动手试一下吧,把地址栏的输入改成“[http://www.chrono.com](http://www.chrono.com)”重复Wireshark抓包过程你会发现好像没有什么不同浏览器上同样显示出了欢迎界面抓到的包也同样是11个先是三次握手然后是两次HTTP传输。
这里就出现了一个问题浏览器是如何从网址里知道“www.chrono.com”的IP地址就是“127.0.0.1”的呢?
还记得我们之前讲过的DNS知识吗浏览器看到了网址里的“www.chrono.com”发现它不是数字形式的IP地址那就肯定是域名了于是就会发起域名解析动作通过访问一系列的域名解析服务器试图把这个域名翻译成TCP/IP协议里的IP地址。
不过因为域名解析的全过程实在是太复杂了,如果每一个域名都要大费周折地去网上查一下,那我们上网肯定会慢得受不了。
所以在域名解析的过程中会有多级的缓存浏览器首先看一下自己的缓存里有没有如果没有就向操作系统的缓存要还没有就检查本机域名解析文件hosts也就是上一讲中我们修改的“C:\WINDOWS\system32\drivers\etc\hosts”。
刚好里面有一行映射关系“127.0.0.1 www.chrono.com”于是浏览器就知道了域名对应的IP地址就可以愉快地建立TCP连接发送HTTP请求了。
我把这个过程也画出了一张图但省略了TCP/IP协议的交互部分里面的浏览器多出了一个访问hosts文件的动作也就是本机的DNS解析。
<img src="https://static001.geekbang.org/resource/image/57/1b/5717c967b8d46e5ba438e1d8ed605a1b.png" alt="">
## 真实的网络世界
通过上面两个在“最小化”环境里的实验你是否已经对HTTP协议的工作流程有了基本的认识呢
第一个实验是最简单的场景只有两个角色浏览器和服务器浏览器可以直接用IP地址找到服务器两者直接建立TCP连接后发送HTTP报文通信。
第二个实验在浏览器和服务器之外增加了一个DNS的角色浏览器不知道服务器的IP地址所以必须要借助DNS的域名解析功能得到服务器的IP地址然后才能与服务器通信。
真实的互联网世界要比这两个场景要复杂的多,我利用下面的这张图来做一个详细的说明。
<img src="https://static001.geekbang.org/resource/image/df/6d/df4696154fc8837e33117d8d6ab1776d.png" alt="">
如果你用的是电脑台式机那么你可能会使用带水晶头的双绞线连上网口由交换机接入固定网络。如果你用的是手机、平板电脑那么你可能会通过蜂窝网络、WiFi由电信基站、无线热点接入移动网络。
接入网络的同时网络运行商会给你的设备分配一个IP地址这个地址可能是静态分配的也可能是动态分配的。静态IP就始终不变而动态IP可能你下次上网就变了。
假设你要访问的是Apple网站显然你是不知道它的真实IP地址的在浏览器里只能使用域名“www.apple.com”访问那么接下来要做的必然是域名解析。这就要用DNS协议开始从操作系统、本地DNS、根DNS、顶级DNS、权威DNS的层层解析当然这中间有缓存可能不会费太多时间就能拿到结果。
别忘了互联网上还有另外一个重要的角色CDN它也会在DNS的解析过程中“插上一脚”。DNS解析可能会给出CDN服务器的IP地址这样你拿到的就会是CDN服务器而不是目标网站的实际地址。
因为CDN会缓存网站的大部分资源比如图片、CSS样式表所以有的HTTP请求就不需要再发到AppleCDN就可以直接响应你的请求把数据发给你。
由PHP、Java等后台服务动态生成的页面属于“动态资源”CDN无法缓存只能从目标网站获取。于是你发出的HTTP请求就要开始在互联网上的“漫长跋涉”经过无数的路由器、网关、代理最后到达目的地。
目标网站的服务器对外表现的是一个IP地址但为了能够扛住高并发在内部也是一套复杂的架构。通常在入口是负载均衡设备例如四层的LVS或者七层的Nginx在后面是许多的服务器构成一个更强更稳定的集群。
负载均衡设备会先访问系统里的缓存服务器通常有memory级缓存Redis和disk级缓存Varnish它们的作用与CDN类似不过是工作在内部网络里把最频繁访问的数据缓存几秒钟或几分钟减轻后端应用服务器的压力。
如果缓存服务器里也没有那么负载均衡设备就要把请求转发给应用服务器了。这里就是各种开发框架大显神通的地方了例如Java的Tomcat/Netty/JettyPython的Django还有PHP、Node.js、Golang等等。它们又会再访问后面的MySQL、PostgreSQL、MongoDB等数据库服务实现用户登录、商品查询、购物下单、扣款支付等业务操作然后把执行的结果返回给负载均衡设备同时也可能给缓存服务器里也放一份。
应用服务器的输出到了负载均衡设备这里请求的处理就算是完成了就要按照原路再走回去还是要经过许多的路由器、网关、代理。如果这个资源允许缓存那么经过CDN的时候它也会做缓存这样下次同样的请求就不会到达源站了。
最后网站的响应数据回到了你的设备它可能是HTML、JSON、图片或者其他格式的数据需要由浏览器解析处理才能显示出来如果数据里面还有超链接指向别的资源那么就又要重走一遍整个流程直到所有的资源都下载完。
## 小结
今天我们在本机的环境里做了两个简单的实验学习了HTTP协议请求-应答的全过程,在这里做一个小结。
1. HTTP协议基于底层的TCP/IP协议所以必须要用IP地址建立连接
1. 如果不知道IP地址就要用DNS协议去解析得到IP地址否则就会连接失败
1. 建立TCP连接后会顺序收发数据请求方和应答方都必须依据HTTP规范构建和解析报文
1. 为了减少响应时间,整个过程中的每一个环节都会有缓存,能够实现“短路”操作;
1. 虽然现实中的HTTP传输过程非常复杂但理论上仍然可以简化成实验里的“两点”模型。
## 课下作业
1. 你能试着解释一下在浏览器里点击页面链接后发生了哪些事情吗?
1. 这一节课里讲的都是正常的请求处理流程,如果是一个不存在的域名,那么浏览器的工作流程会是怎么样的呢?
欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。
<img src="https://static001.geekbang.org/resource/image/8e/56/8ef903c86d3ef548a9536bd4345f0156.png" alt="unpreview">

View File

@@ -0,0 +1,217 @@
<audio id="audio" title="09 | HTTP报文是什么样子的" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fd/04/fd314ef48924d547230a89a7115b9204.mp3"></audio>
在上一讲里我们在本机的最小化环境了做了两个HTTP协议的实验使用Wireshark抓包弄清楚了HTTP协议基本工作流程也就是“请求-应答”“一发一收”的模式。
可以看到HTTP的工作模式是非常简单的由于TCP/IP协议负责底层的具体传输工作HTTP协议基本上不用在这方面操心太多。单从这一点上来看所谓的“超文本传输协议”其实并不怎么管“传输”的事情有点“名不副实”。
那么HTTP协议的核心部分是什么呢
答案就是它传输的报文内容。
HTTP协议在规范文档里详细定义了报文的格式规定了组成部分解析规则还有处理策略所以可以在TCP/IP层之上实现更灵活丰富的功能例如连接控制缓存管理、数据编码、内容协商等等。
## 报文结构
你也许对TCP/UDP的报文格式有所了解拿TCP报文来举例它在实际要传输的数据之前附加了一个20字节的头部数据存储TCP协议必须的额外信息例如发送方的端口号、接收方的端口号、包序号、标志位等等。
有了这个附加的TCP头数据包才能够正确传输到了目的地后把头部去掉就可以拿到真正的数据。
<img src="https://static001.geekbang.org/resource/image/17/95/174bb72bad50127ac84427a72327f095.png" alt="">
HTTP协议也是与TCP/UDP类似同样也需要在实际传输的数据前附加一些头数据不过与TCP/UDP不同的是它是一个“**纯文本**”的协议所以头数据都是ASCII码的文本可以很容易地用肉眼阅读不用借助程序解析也能够看懂。
HTTP协议的请求报文和响应报文的结构基本相同由三大部分组成
1. 起始行start line描述请求或响应的基本信息
1. 头部字段集合header使用key-value形式更详细地说明报文
1. 消息正文entity实际传输的数据它不一定是纯文本可以是图片、视频等二进制数据。
这其中前两部分起始行和头部字段经常又合称为“**请求头**”或“**响应头**”,消息正文又称为“**实体**”,但与“**header**”对应,很多时候就直接称为“**body**”。
HTTP协议规定报文必须有header但可以没有body而且在header之后必须要有一个“空行”也就是“CRLF”十六进制的“0D0A”。
所以一个完整的HTTP报文就像是下图的这个样子注意在header和body之间有一个“空行”。
<img src="https://static001.geekbang.org/resource/image/62/3c/62e061618977565c22c2cf09930e1d3c.png" alt="">
说到这里我不由得想起了一部老动画片《大头儿子和小头爸爸》你看HTTP的报文结构像不像里面的“大头儿子”
报文里的header就是“大头儿子”的“大头”空行就是他的“脖子”而后面的body部分就是他的身体了。
看一下我们之前用Wireshark抓的包吧。
<img src="https://static001.geekbang.org/resource/image/b1/df/b191c8760c8ad33acd9bb005b251a2df.png" alt="unpreview">
在这个浏览器发出的请求报文里第一行“GET / HTTP/1.1”就是请求行而后面的“Host”“Connection”等等都属于header报文的最后是一个空白行结束没有body。
在很多时候特别是浏览器发送GET请求的时候都是这样HTTP报文经常是只有header而没body相当于只发了一个超级“大头”过来你可以想象的出来每时每刻网络上都会有数不清的“大头儿子”在跑来跑去。
不过这个“大头”也不能太大虽然HTTP协议对header的大小没有做限制但各个Web服务器都不允许过大的请求头因为头部太大可能会占用大量的服务器资源影响运行效率。
## 请求行
了解了HTTP报文的基本结构后我们来看看请求报文里的起始行也就是**请求行**request line它简要地描述了**客户端想要如何操作服务器端的资源**。
请求行由三部分构成:
1. 请求方法是一个动词如GET/POST表示对资源的操作
1. 请求目标通常是一个URI标记了请求方法要操作的资源
1. 版本号表示报文使用的HTTP协议版本。
这三个部分通常使用空格space来分隔最后要用CRLF换行表示结束。
<img src="https://static001.geekbang.org/resource/image/36/b9/36108959084392065f36dff3e12967b9.png" alt="">
还是用Wireshark抓包的数据来举例
```
GET / HTTP/1.1
```
在这个请求行里“GET”是请求方法“/”是请求目标“HTTP/1.1”是版本号把这三部分连起来意思就是“服务器你好我想获取网站根目录下的默认文件我用的协议版本号是1.1请不要用1.0或者2.0回复我。”
别看请求行就一行,貌似很简单,其实这里面的“讲究”是非常多的,尤其是前面的请求方法和请求目标,组合起来变化多端,后面我还会详细介绍。
## 状态行
看完了请求行,我们再看响应报文里的起始行,在这里它不叫“响应行”,而是叫“**状态行**”status line意思是**服务器响应的状态**。
比起请求行来说,状态行要简单一些,同样也是由三部分构成:
1. 版本号表示报文使用的HTTP协议版本
1. 状态码一个三位数用代码的形式表示处理的结果比如200是成功500是服务器错误
1. 原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因。
<img src="https://static001.geekbang.org/resource/image/a1/00/a1477b903cd4d5a69686683c0dbc3300.png" alt="">
看一下上一讲里Wireshark抓包里的响应报文状态行是
```
HTTP/1.1 200 OK
```
意思就是“浏览器你好我已经处理完了你的请求这个报文使用的协议版本号是1.1状态码是200一切OK。”
而另一个“GET /favicon.ico HTTP/1.1”的响应报文状态行是:
```
HTTP/1.1 404 Not Found
```
翻译成人话就是“抱歉啊浏览器刚才你的请求收到了但我没找到你要的资源错误代码是404接下来的事情你就看着办吧。”
## 头部字段
请求行或状态行再加上头部字段集合就构成了HTTP报文里完整的请求头或响应头我画了两个示意图你可以看一下。
<img src="https://static001.geekbang.org/resource/image/1f/ea/1fe4c1121c50abcf571cebd677a8bdea.png" alt="">
<img src="https://static001.geekbang.org/resource/image/cb/75/cb0d1d2c56400fe9c9988ee32842b175.png" alt="">
请求头和响应头的结构是基本一样的,唯一的区别是起始行,所以我把请求头和响应头里的字段放在一起介绍。
头部字段是key-value的形式key和value之间用“:”分隔最后用CRLF换行表示字段结束。比如在“Host: 127.0.0.1”这一行里key就是“Host”value就是“127.0.0.1”。
HTTP头字段非常灵活不仅可以使用标准里的Host、Connection等已有头也可以任意添加自定义头这就给HTTP协议带来了无限的扩展可能。
不过使用头字段需要注意下面几点:
1. 字段名不区分大小写例如“Host”也可以写成“host”但首字母大写的可读性更好
1. 字段名里不允许出现空格,可以使用连字符“-”但不能使用下划线“_”。例如“test-name”是合法的字段名而“test name”“test_name”是不正确的字段名
1. 字段名后面必须紧接着“:”,不能有空格,而“:”后的字段值前可以有多个空格;
1. 字段的顺序是没有意义的,可以任意排列不影响语义;
1. 字段原则上不能重复除非这个字段本身的语义允许例如Set-Cookie。
我在实验环境里用Lua编写了一个小服务程序URI是“/09-1”效果是输出所有的请求头。
你可以在实验环境里用Telnet连接OpenResty服务器试一下手动发送HTTP请求头试验各种正确和错误的情况。
先启动OpenResty服务器然后用组合键“Win+R”运行telnet输入命令“open www.chrono.com 80”就连上了Web服务器。
<img src="https://static001.geekbang.org/resource/image/34/7b/34fb2b5899bdb87a3899dd133c0c457b.png" alt="">
连接上之后按组合键“CTRL+]”然后按回车键就进入了编辑模式。在这个界面里你可以直接用鼠标右键粘贴文本敲两下回车后就会发送数据也就是模拟了一次HTTP请求。
下面是两个最简单的HTTP请求第一个在“:”后有多个空格,第二个在“:”前有空格。
```
GET /09-1 HTTP/1.1
Host: www.chrono.com
GET /09-1 HTTP/1.1
Host : www.chrono.com
```
第一个可以正确获取服务器的响应报文而第二个得到的会是一个“400 Bad Request”表示请求报文格式有误服务器无法正确处理
```
HTTP/1.1 400 Bad Request
Server: openresty/1.15.8.1
Connection: close
```
## 常用头字段
HTTP协议规定了非常多的头部字段实现各种各样的功能但基本上可以分为四大类
1. 通用字段:在请求头和响应头里都可以出现;
1. 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件;
1. 响应字段:仅能出现在响应头里,补充说明响应报文的信息;
1. 实体字段它实际上属于通用字段但专门描述body的额外信息。
对HTTP报文的解析和处理实际上主要就是对头字段的处理理解了头字段也就理解了HTTP报文。
后续的课程中我将会以应用领域为切入点介绍连接管理、缓存控制等头字段今天先讲几个最基本的头看完了它们你就应该能够读懂大多数HTTP报文了。
首先要说的是**Host**字段它属于请求字段只能出现在请求头里它同时也是唯一一个HTTP/1.1规范里要求**必须出现**的字段也就是说如果请求头里没有Host那这就是一个错误的报文。
Host字段告诉服务器这个请求应该由哪个主机来处理当一台计算机上托管了多个虚拟主机的时候服务器端就需要用Host字段来选择有点像是一个简单的“路由重定向”。
例如我们的试验环境在127.0.0.1上有三个虚拟主机“www.chrono.com”“www.metroid.net”和“origin.io”。那么当使用域名的方式访问时就必须要用Host字段来区分这三个IP相同但域名不同的网站否则服务器就会找不到合适的虚拟主机无法处理。
**User-Agent**是请求字段只出现在请求头里。它使用一个字符串来描述发起HTTP请求的客户端服务器可以依据它来返回最合适此浏览器显示的页面。
但由于历史的原因User-Agent非常混乱每个浏览器都自称是“Mozilla”“Chrome”“Safari”企图使用这个字段来互相“伪装”导致User-Agent变得越来越长最终变得毫无意义。
不过有的比较“诚实”的爬虫会在User-Agent里用“spider”标明自己是爬虫所以可以利用这个字段实现简单的反爬虫策略。
**Date**字段是一个通用字段但通常出现在响应头里表示HTTP报文创建的时间客户端可以使用这个时间再搭配其他字段决定缓存策略。
**Server**字段是响应字段只能出现在响应头里。它告诉客户端当前正在提供Web服务的软件名称和版本号例如在我们的实验环境里它就是“Server: openresty/1.15.8.1”即使用的是OpenResty 1.15.8.1。
Server字段也不是必须要出现的因为这会把服务器的一部分信息暴露给外界如果这个版本恰好存在bug那么黑客就有可能利用bug攻陷服务器。所以有的网站响应头里要么没有这个字段要么就给出一个完全无关的描述信息。
比如GitHub它的Server字段里就看不出是使用了Apache还是Nginx只是显示为“GitHub.com”。
<img src="https://static001.geekbang.org/resource/image/f1/1c/f1970aaecad58fb18938e262ea7f311c.png" alt="">
实体字段里要说的一个是**Content-Length**它表示报文里body的长度也就是请求头或响应头空行后面数据的长度。服务器看到这个字段就知道了后续有多少数据可以直接接收。如果没有这个字段那么body就是不定长的需要使用chunked方式分段传输。
## 小结
今天我们学习了HTTP的报文结构下面做一个简单小结。
1. HTTP报文结构就像是“大头儿子”由“起始行+头部+空行+实体”组成简单地说就是“header+body”
1. HTTP报文可以没有body但必须要有header而且header后也必须要有空行形象地说就是“大头”必须要带着“脖子”
1. 请求头由“请求行+头部字段”构成,响应头由“状态行+头部字段”构成;
1. 请求行有三部分:请求方法,请求目标和版本号;
1. 状态行也有三部分:版本号,状态码和原因字符串;
1. 头部字段是key-value的形式用“:”分隔,不区分大小写,顺序任意,除了规定的标准头,也可以任意添加自定义字段,实现功能扩展;
1. HTTP/1.1里唯一要求必须提供的头字段是Host它必须出现在请求头里标记虚拟主机名。
## 课下作业
1. 如果拼HTTP报文的时候在头字段后多加了一个CRLF导致出现了一个空行会发生什么
1. 讲头字段时说“:”后的空格可以有多个,那为什么绝大多数情况下都只使用一个空格呢?
欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。
<img src="https://static001.geekbang.org/resource/image/1a/26/1aa9cb1a1d637e10340451d81e87fc26.png" alt="unpreview">

View File

@@ -0,0 +1,161 @@
<audio id="audio" title="10 | 应该如何理解请求方法?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/07/ea/0784c7505e2fbce9f21b6ac7454b9aea.mp3"></audio>
上一讲我介绍了HTTP的报文结构它是由header+body构成请求头里有请求方法和请求目标响应头里有状态码和原因短语今天要说的就是请求头里的请求方法。
## 标准请求方法
HTTP协议里为什么要有“请求方法”这个东西呢
这就要从HTTP协议设计时的定位说起了。还记得吗蒂姆·伯纳斯-李最初设想的是要用HTTP协议构建一个超链接文档系统使用URI来定位这些文档也就是资源。那么该怎么在协议里操作这些资源呢
很显然需要有某种“动作的指示”告诉操作这些资源的方式。所以就这么出现了“请求方法”。它的实际含义就是客户端发出了一个“动作指令”要求服务器端对URI定位的资源执行这个动作。
目前HTTP/1.1规定了八种方法,单词**都必须是大写的形式**,我先简单地列把它们列出来,后面再详细讲解。
1. GET获取资源可以理解为读取或者下载数据
1. HEAD获取资源的元信息
1. POST向资源提交数据相当于写入或上传数据
1. PUT类似POST
1. DELETE删除资源
1. CONNECT建立特殊的连接隧道
1. OPTIONS列出可对资源实行的方法
1. TRACE追踪请求-响应的传输路径。
<img src="https://static001.geekbang.org/resource/image/3c/6d/3cdc8ac71b80929f4a94dfeb9ffe4b6d.jpg" alt="">
看看这些方法,是不是有点像对文件或数据库的“增删改查”操作,只不过这些动作操作的目标不是本地资源,而是远程服务器上的资源,所以只能由客户端“请求”或者“指示”服务器来完成。
既然请求方法是一个“指示”那么客户端自然就没有决定权服务器掌控着所有资源也就有绝对的决策权力。它收到HTTP请求报文后看到里面的请求方法可以执行也可以拒绝或者改变动作的含义毕竟HTTP是一个“协议”两边都要“商量着来”。
比如你发起了一个GET请求想获取“/orders”这个文件但这个文件保密级别比较高不是谁都能看的服务器就可以有如下的几种响应方式
1. 假装这个文件不存在直接返回一个404 Not found报文
1. 稍微友好一点明确告诉你有这个文件但不允许访问返回一个403 Forbidden
1. 再宽松一些返回405 Method Not Allowed然后用Allow头告诉你可以用HEAD方法获取文件的元信息。
## GET/HEAD
虽然HTTP/1.1里规定了八种请求方法,但只有前四个是比较常用的,所以我们先来看一下这四个方法。
**GET**方法应该是HTTP协议里最知名的请求方法了也应该是用的最多的自0.9版出现并一直被保留至今,是名副其实的“元老”。
它的含义是请求**从服务器获取资源**这个资源既可以是静态的文本、页面、图片、视频也可以是由PHP、Java动态生成的页面或者其他格式的数据。
GET方法虽然基本动作比较简单但搭配URI和其他头字段就能实现对资源更精细的操作。
例如在URI后使用“#”就可以在获取页面后直接定位到某个标签所在的位置使用If-Modified-Since字段就变成了“有条件的请求”仅当资源被修改时才会执行获取动作使用Range字段就是“范围请求”只获取资源的一部分数据。
**HEAD**方法与GET方法类似也是请求从服务器获取资源服务器的处理机制也是一样的但服务器不会返回请求的实体数据只会传回响应头也就是资源的“元信息”。
HEAD方法可以看做是GET方法的一个“简化版”或者“轻量版”。因为它的响应头与GET完全相同所以可以用在很多并不真正需要资源的场合避免传输body数据的浪费。
比如想要检查一个文件是否存在只要发个HEAD请求就可以了没有必要用GET把整个文件都取下来。再比如要检查文件是否有最新版本同样也应该用HEAD服务器会在响应头里把文件的修改时间传回来。
你可以在实验环境里试一下这两个方法运行Telnet分别向URI“/10-1”发送GET和HEAD请求观察一下响应头是否一致。
```
GET /10-1 HTTP/1.1
Host: www.chrono.com
HEAD /10-1 HTTP/1.1
Host: www.chrono.com
```
## POST/PUT
接下来要说的是**POST**和**PUT**方法,这两个方法也很像。
GET和HEAD方法是从服务器获取数据而POST和PUT方法则是相反操作向URI指定的资源提交数据数据就放在报文的body里。
POST也是一个经常用到的请求方法使用频率应该是仅次于GET应用的场景也非常多只要向服务器发送数据用的大多数都是POST。
比如你上论坛灌水敲了一堆字后点击“发帖”按钮浏览器就执行了一次POST请求把你的文字放进报文的body里然后拼好POST请求头通过TCP协议发给服务器。
又比如你上购物网站看到了一件心仪的商品点击“加入购物车”这时也会有POST请求浏览器会把商品ID发给服务器服务器再把ID写入你的购物车相关的数据库记录。
PUT的作用与POST类似也可以向服务器提交数据但与POST存在微妙的不同通常POST表示的是“新建”“create”的含义而PUT则是“修改”“update”的含义。
在实际应用中PUT用到的比较少。而且因为它与POST的语义、功能太过近似有的服务器甚至就直接禁止使用PUT方法只用POST方法上传数据。
实验环境的“/10-2”模拟了POST和PUT方法的处理过程你仍然可以用Telnet发送测试请求看看运行的效果。注意在发送请求时头字段“Content-Length”一定要写对是空行后body的长度
```
POST /10-2 HTTP/1.1
Host: www.chrono.com
Content-Length: 17
POST DATA IS HERE
PUT /10-2 HTTP/1.1
Host: www.chrono.com
Content-Length: 16
PUT DATA IS HERE
```
## 其他方法
讲完了GET/HEAD/POST/PUT还剩下四个标准请求方法它们属于比较“冷僻”的方法应用的不是很多。
**DELETE**方法指示服务器删除资源因为这个动作危险性太大所以通常服务器不会执行真正的删除操作而是对资源做一个删除标记。当然更多的时候服务器就直接不处理DELETE请求。
**CONNECT**是一个比较特殊的方法要求服务器为客户端和另一台远程服务器建立一条特殊的连接隧道这时Web服务器在中间充当了代理的角色。
**OPTIONS**方法要求服务器列出可对资源实行的操作方法在响应头的Allow字段里返回。它的功能很有限用处也不大有的服务器例如Nginx干脆就没有实现对它的支持。
**TRACE**方法多用于对HTTP链路的测试或诊断可以显示出请求-响应的传输路径。它的本意是好的但存在漏洞会泄漏网站的信息所以Web服务器通常也是禁止使用。
## 扩展方法
虽然HTTP/1.1里规定了八种请求方法但它并没有限制我们只能用这八种方法这也体现了HTTP协议良好的扩展性我们可以任意添加请求动作只要请求方和响应方都能理解就行。
例如著名的愚人节玩笑RFC2324它定义了协议HTCPCP即“超文本咖啡壶控制协议”为HTTP协议增加了用来煮咖啡的BREW方法要求添牛奶的WHEN方法。
此外还有一些得到了实际应用的请求方法WebDAV例如MKCOL、COPY、MOVE、LOCK、UNLOCK、PATCH等。如果有合适的场景你也可以把它们应用到自己的系统里比如用LOCK方法锁定资源暂时不允许修改或者使用PATCH方法给资源打个小补丁部分更新数据。但因为这些方法是非标准的所以需要为客户端和服务器编写额外的代码才能添加支持。
当然了你也完全可以根据实际需求自己发明新的方法比如“PULL”拉取某些资源到本地“PURGE”清理某个目录下的所有缓存数据。
## 安全与幂等
关于请求方法还有两个面试时有可能会问到、比较重要的概念:**安全**与**幂等**。
在HTTP协议里所谓的“**安全**”是指请求方法不会“破坏”服务器上的资源,即不会对服务器上的资源造成实质的修改。
按照这个定义只有GET和HEAD方法是“安全”的因为它们是“只读”操作只要服务器不故意曲解请求方法的处理方式无论GET和HEAD操作多少次服务器上的数据都是“安全的”。
而POST/PUT/DELETE操作会修改服务器上的资源增加或删除数据所以是“不安全”的。
所谓的“**幂等**”实际上是一个数学用语被借用到了HTTP协议里意思是多次执行相同的操作结果也都是相同的即多次“幂”后结果“相等”。
很显然GET和HEAD既是安全的也是幂等的DELETE可以多次删除同一个资源效果都是“资源不存在”所以也是幂等的。
POST和PUT的幂等性质就略费解一点。
按照RFC里的语义POST是“新增或提交数据”多次提交数据会创建多个资源所以不是幂等的而PUT是“替换或更新数据”多次更新一个资源资源还是会第一次更新的状态所以是幂等的。
我对你的建议是你可以对比一下SQL来加深理解把POST理解成INSERT把PUT理解成UPDATE这样就很清楚了。多次INSERT会添加多条记录而多次UPDATE只操作一条记录而且效果相同。
## 小结
今天我们学习了HTTP报文里请求方法相关的知识简单小结一下。
1. 请求方法是客户端发出的、要求服务器执行的、对资源的一种操作;
1. 请求方法是对服务器的“指示”,真正应如何处理由服务器来决定;
1. 最常用的请求方法是GET和POST分别是获取数据和发送数据
1. HEAD方法是轻量级的GET用来获取资源的元信息
1. PUT基本上是POST的同义词多用于更新数据
1. “安全”与“幂等”是描述请求方法的两个重要属性,具有理论指导意义,可以帮助我们设计系统。
## 课下作业
1. 你能把GET/POST等请求方法对应到数据库的“增删改查”操作吗请求头应该如何设计呢
1. 你觉得TRACE/OPTIONS/CONNECT方法能够用GET或POST间接实现吗
欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,欢迎你把文章分享给你的朋友。
<img src="https://static001.geekbang.org/resource/image/60/81/60ee384d93d46cd6632be0606ae21681.png" alt="unpreview">

View File

@@ -0,0 +1,194 @@
<audio id="audio" title="11 | 你能写出正确的网址吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/de/df/de20194c5948ebde8408520e53b4ccdf.mp3"></audio>
上一讲里我们一起学习了HTTP协议里的请求方法其中最常用的一个是GET它用来从服务器上某个资源获取数据另一个是POST向某个资源提交数据。
那么,应该用什么来标记服务器上的资源呢?怎么区分“这个”资源和“那个”资源呢?
经过前几讲的学习你一定已经知道了用的是URI也就是**统一资源标识符****U**niform **R**esource **I**dentifier。因为它经常出现在浏览器的地址栏里所以俗称为“网络地址”简称“网址”。
严格地说URI不完全等同于网址它包含有URL和URN两个部分在HTTP世界里用的网址实际上是URL——**统一资源定位符****U**niform **R**esource **L**ocator。但因为URL实在是太普及了所以常常把这两者简单地视为相等。
不仅我们生活中的上网要用到URI平常的开发、测试、运维的工作中也少不了它。
如果你在客户端做iOS、 Android或者某某小程序开发免不了要连接远程服务就会调用底层API用URI访问服务。
如果你使用Java、PHP做后台Web开发也会调用getPath()、parse_url() 等函数来处理URI解析里面的各个要素。
在测试、运维配置Apache、Nginx等Web服务器的时候也必须正确理解URI分离静态资源与动态资源或者设置规则实现网页的重定向跳转。
总之一句话URI非常重要要搞懂HTTP甚至网络应用就必须搞懂URI。
## URI的格式
不知道你平常上网的时候有没有关注过地址栏里的那一长串字符,有的比较简短,有的则一行都显示不下,有的意思大概能看明白,而有的则带着各种怪字符,有如“天书”。
其实只要你弄清楚了URI的格式就能够轻易地“破解”这些难懂的“天书”了。
URI本质上是一个字符串这个字符串的作用是**唯一地标记资源的位置或者名字**。
这里我要提醒你注意它不仅能够标记万维网的资源也可以标记其他的如邮件系统、本地文件系统等任意资源。而“资源”既可以是存在磁盘上的静态文本、页面数据也可以是由Java、PHP提供的动态服务。
下面的这张图显示了URI最常用的形式由scheme、host:port、path和query四个部分组成但有的部分可以视情况省略。
<img src="https://static001.geekbang.org/resource/image/46/2a/46581d7e1058558d8e12c1bf37d30d2a.png" alt="">
## URI的基本组成
URI第一个组成部分叫**scheme**,翻译成中文叫“**方案名**”或者“**协议名**”,表示**资源应该使用哪种协议**来访问。
最常见的当然就是“http”了表示使用HTTP协议。另外还有“https”表示使用经过加密、安全的HTTPS协议。此外还有其他不是很常见的scheme例如ftp、ldap、file、news等。
浏览器或者你的应用程序看到URI里的scheme就知道下一步该怎么走了会调用相应的HTTP或者HTTPS下层API。显然如果一个URI没有提供scheme即使后面的地址再完善也是无法处理的。
在scheme之后必须是**三个特定的字符**“**://**”它把scheme和后面的部分分离开。
实话实说,这个设计非常的怪异,我最早上网的时候看见地址栏里的“://”就觉得很别扭直到现在也还是没有太适应。URI的创造者蒂姆·伯纳斯-李也曾经私下承认“://”并非必要,当初有些“过于草率”了。
不过这个设计已经有了三十年的历史,不管我们愿意不愿意,只能接受。
在“://”之后,是被称为“**authority**”的部分,表示**资源所在的主机名**通常的形式是“host:port”即主机名加端口号。
主机名可以是IP地址或者域名的形式必须要有否则浏览器就会找不到服务器。但端口号有时可以省略浏览器等客户端会依据scheme使用默认的端口号例如HTTP的默认端口号是80HTTPS的默认端口号是443。
有了协议名和主机地址、端口号,再加上后面**标记资源所在位置**的**path**,浏览器就可以连接服务器访问资源了。
URI里path采用了类似文件系统“目录”“路径”的表示方式因为早期互联网上的计算机多是UNIX系统所以采用了UNIX的“/”风格。其实也比较好理解它与scheme后面的“://”是一致的。
这里我也要再次提醒你注意URI的path部分必须以“/”开始,也就是必须包含“/”,不要把“/”误认为属于前面authority。
说了这么多“理论”,来看几个实例。
```
http://nginx.org
http://www.chrono.com:8080/11-1
https://tools.ietf.org/html/rfc7230
file:///D:/http_study/www/
```
第一个URI算是最简单的了协议名是“http”主机名是“nginx.org”端口号省略所以是默认的80而路径部分也被省略了默认就是一个“/”,表示根目录。
第二个URI是在实验环境里这次课程的专用URI主机名是“www.chrono.com”端口号是8080后面的路径是“/11-1”。
第三个是HTTP协议标准文档RFC7230的URI主机名是“tools.ietf.org”路径是“/html/rfc7230”。
最后一个URI要注意了它的协议名不是“http”而是“file”表示这是本地文件而后面居然有三个斜杠这是怎么回事
如果你刚才仔细听了scheme的介绍就能明白这三个斜杠里的前两个属于URI特殊分隔符“://”,然后后面的“/D:/http_study/www/”是路径而中间的主机名被“省略”了。这实际上是file类型URI的“特例”它允许省略主机名默认是本机localhost。
但对于HTTP或HTTPS这样的网络通信协议主机名是绝对不能省略的。原因之前也说了会导致浏览器无法找到服务器。
我们可以在实验环境里用Chrome浏览器再仔细观察一下HTTP报文里的URI。
运行Chrome用F12打开开发者工具然后在地址栏里输入“[http://www.chrono.com/11-1](http://www.chrono.com/11-1)”,得到的结果如下图。
<img src="https://static001.geekbang.org/resource/image/20/9f/20ac5ee55b8ee30527492c8abb60ff9f.png" alt="">
在开发者工具里依次选“Network”“Doc”就可以找到请求的URI。然后在Headers页里看Request Headers用“view source”就可以看到浏览器发的原始请求头了。
发现了什么特别的没有?
在HTTP报文里的URI“/11-1”与浏览器里输入的“[http://www.chrono.com/11-1](http://www.chrono.com/11-1)”有很大的不同,协议名和主机名都不见了,只剩下了后面的部分。
这是因为协议名和主机名已经分别出现在了请求行的版本号和请求头的Host字段里没有必要再重复。当然在请求行里使用完整的URI也是可以的你可以在课后自己试一下。
通过这个小实验我们还得到了一个结论客户端和服务器看到的URI是不一样的。客户端看到的必须是完整的URI使用特定的协议去连接特定的主机而服务器看到的只是报文请求行里被删除了协议名和主机名的URI。
如果你配置过Nginx你就应该明白了Nginx作为一个Web服务器它的location、rewrite等指令操作的URI其实指的是真正URI里的path和后续的部分。
## URI的查询参数
使用“协议名+主机名+路径”的方式,已经可以精确定位网络上的任何资源了。但这还不够,很多时候我们还想在操作资源的时候附加一些额外的修饰参数。
举几个例子获取商品图片但想要一个32×32的缩略图版本获取商品列表但要按某种规则做分页和排序跳转页面但想要标记跳转前的原始页面。
仅用“协议名+主机名+路径”的方式是无法适应这些场景的所以URI后面还有一个“**query**”部分它在path之后用一个“?”开始,但不包含“?”,表示对资源附加的额外要求。这是个很形象的符号,比“://”要好的多,很明显地表示了“查询”的含义。
查询参数query有一套自己的格式是多个“**key=value**”的字符串这些KV值用字符“**&amp;**”连接,浏览器和服务器都可以按照这个格式把长串的查询参数解析成可理解的字典或关联数组形式。
你可以在实验环境里用Chrome试试下面这个加了query参数的URI
```
http://www.chrono.com:8080/11-1?uid=1234&amp;name=mario&amp;referer=xxx
```
Chrome的开发者工具也能解码出query里的KV对省得我们“人肉”分解。
<img src="https://static001.geekbang.org/resource/image/e4/f3/e42073080968e8e0c58d9a9126ab82f3.png" alt="">
还可以再拿一个实际的URI来看一下这个URI是某电商网站的一个商品查询URI比较复杂但相信现在的你能够毫不费力地区分出里面的协议名、主机名、路径和查询参数。
```
https://search.jd.com/Search?keyword=openresty&amp;enc=utf-8&amp;qrst=1&amp;rt=1&amp;stop=1&amp;vt=2&amp;wq=openresty&amp;psort=3&amp;click=0
```
你也可以把这个URI输入到Chrome的地址栏里再用开发者工具仔细检查它的组成部分。
## URI的完整格式
讲完了query参数URI就算完整了HTTP协议里用到的URI绝大多数都是这种形式。
不过必须要说的是URI还有一个“真正”的完整形态如下图所示。
<img src="https://static001.geekbang.org/resource/image/ff/38/ff41d020c7a27d1e8191057f0e658b38.png" alt="">
这个“真正”形态比基本形态多了两部分。
第一个多出的部分是协议名之后、主机名之前的**身份信息**“user:passwd@”表示登录主机时的用户名和密码但现在已经不推荐使用这种形式了RFC7230因为它把敏感信息以明文形式暴露出来存在严重的安全隐患。
第二个多出的部分是查询参数后的**片段标识符**“#fragment它是URI所定位的资源内部的一个“锚点”或者说是“标签”浏览器可以在获取资源后直接跳转到它指示的位置。
但片段标识符仅能由浏览器这样的客户端使用,服务器是看不到的。也就是说,浏览器永远不会把带“#fragment”的URI发送给服务器服务器也永远不会用这种方式去处理资源的片段。
## URI的编码
刚才我们看到了在URI里只能使用ASCII码但如果要在URI里使用英语以外的汉语、日语等其他语言该怎么办呢
还有某些特殊的URI会在path、query里出现“@&amp;?"等起界定符作用的字符会导致URI解析错误这时又该怎么办呢
所以URI引入了编码机制对于ASCII码以外的字符集和特殊字符做一个特殊的操作把它们转换成与URI语义不冲突的形式。这在RFC规范里称为“escape”和“unescape”俗称“转义”。
URI转义的规则有点“简单粗暴”直接把非ASCII码或特殊字符转换成十六进制字节值然后前面再加上一个“%”。
例如,空格被转义成“%20”“?”被转义成“%3F”。而中文、日文等则通常使用UTF-8编码后再转义例如“银河”会被转义成“%E9%93%B6%E6%B2%B3”。
有了这个编码规则后URI就更加完美了可以支持任意的字符集用任何语言来标记资源。
不过我们在浏览器的地址栏里通常是不会看到这些转义后的“乱码”的这实际上是浏览器一种“友好”表现隐藏了URI编码后的“丑陋一面”不信你可以试试下面的这个URI。
```
http://www.chrono.com:8080/11-1?夸父逐日
```
先在Chrome的地址栏里输入这个query里含有中文的URI然后点击地址栏把它再拷贝到其他的编辑器里它就会“现出原形”
```
http://www.chrono.com:8080/11-1?%E5%A4%B8%E7%88%B6%E9%80%90%E6%97%A5
```
## 小结
今天我们学习了网址也就是URI的知识在这里小结一下今天的内容。
1. URI是用来唯一标记服务器上资源的一个字符串通常也称为URL
1. URI通常由scheme、host:port、path和query四个部分组成有的可以省略
1. scheme叫“方案名”或者“协议名”表示资源应该使用哪种协议来访问
1. “host:port”表示资源所在的主机名和端口号
1. path标记资源所在的位置
1. query表示对资源附加的额外要求
1. 在URI里对“@&amp;/”等特殊字符和汉字必须要做编码否则服务器收到HTTP报文后会无法正确处理。
## 课下作业
1. HTTP协议允许在在请求行里使用完整的URI但为什么浏览器没有这么做呢
1. URI的查询参数和头字段很相似都是key-value形式都可以任意自定义那么它们在使用时该如何区别呢具体分析可以在“答疑篇”[第41讲](https://time.geekbang.org/column/article/146833)中的URI查询参数和头字段部分查看
欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,欢迎你把文章分享给你的朋友。
<img src="https://static001.geekbang.org/resource/image/26/cb/26e06ff1fee9a7cd0b9c1a99bf9d32cb.png" alt="">

View File

@@ -0,0 +1,141 @@
<audio id="audio" title="12 | 响应状态码该怎么用?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/48/77/48e25c659bc0c244092903ce7b3f7e77.mp3"></audio>
前两讲中我们学习了HTTP报文里请求行的组成部分包括请求方法和URI。有了请求行加上后面的头字段就形成了请求头可以通过TCP/IP协议发送给服务器。
服务器收到请求报文,解析后需要进行处理,具体的业务逻辑多种多样,但最后必定是拼出一个响应报文发回客户端。
响应报文由响应头加响应体数据组成,响应头又由状态行和头字段构成。
我们先来复习一下状态行的结构,有三部分:
<img src="https://static001.geekbang.org/resource/image/a1/00/a1477b903cd4d5a69686683c0dbc3300.png" alt="">
开头的Version部分是HTTP协议的版本号通常是HTTP/1.1,用处不是很大。
后面的Reason部分是原因短语是状态码的简短文字描述例如“OK”“Not Found”等等也可以自定义。但它只是为了兼容早期的文本客户端而存在提供的信息很有限目前的大多数客户端都会忽略它。
所以,状态行里有用的就只剩下中间的**状态码**Status Code了。它是一个十进制数字以代码的形式表示服务器对请求的处理结果就像我们通常编写程序时函数返回的错误码一样。
不过你要注意它的名字是“状态码”而不是“错误码”。也就是说它的含义不仅是错误更重要的意义在于表达HTTP数据处理的“状态”客户端可以依据代码适时转换处理状态例如继续发送请求、切换协议重定向跳转等有那么点TCP状态转换的意思。
## 状态码
目前RFC标准里规定的状态码是三位数所以取值范围就是从000到999。但如果把代码简单地从000开始顺序编下去就显得有点太“low”不灵活、不利于扩展所以状态码也被设计成有一定的格式。
RFC标准把状态码分成了五类用数字的第一位表示分类而0~99不用这样状态码的实际可用范围就大大缩小了由000~999变成了100~599。
这五类的具体含义是:
- 1××提示信息表示目前是协议处理的中间状态还需要后续的操作
- 2××成功报文已经收到并被正确处理
- 3××重定向资源位置发生变动需要客户端重新发送请求
- 4××客户端错误请求报文有误服务器无法处理
- 5××服务器错误服务器在处理请求时内部发生了错误。
在HTTP协议中正确地理解并应用这些状态码不是客户端或服务器单方的责任而是双方共同的责任。
客户端作为请求的发起方,获取响应报文后,需要通过状态码知道请求是否被正确处理,是否要再次发送请求,如果出错了原因又是什么。这样才能进行下一步的动作,要么发送新请求,要么改正错误重发请求。
服务器端作为请求的接收方也应该很好地运用状态码。在处理请求时选择最恰当的状态码回复客户端告知客户端处理的结果指示客户端下一步应该如何行动。特别是在出错的时候尽量不要简单地返400、500这样意思含糊不清的状态码。
目前RFC标准里总共有41个状态码但状态码的定义是开放的允许自行扩展。所以Apache、Nginx等Web服务器都定义了一些专有的状态码。如果你自己开发Web应用也完全可以在不冲突的前提下定义新的代码。
在我们的实验环境里也可以对这些状态码做测试验证访问URI“**/12-1**”用查询参数“code=xxx”来检查这些状态码的效果服务器不仅会在状态行里显示状态码还会在响应头里用自定义的“Expect-Code”字段输出这个代码。
例如在Chrome里访问“[http://www.chrono.com/12-1?code=405](http://www.chrono.com/12-1?code=405)”的结果如下图。
<img src="https://static001.geekbang.org/resource/image/07/d7/07e7a40241a09683c5420e7b311227d7.png" alt="">
接下来我就挑一些实际开发中比较有价值的状态码逐个详细介绍。
## 1××
1××类状态码属于提示信息是协议处理的中间状态实际能够用到的时候很少。
我们偶尔能够见到的是“**101 Switching Protocols**”。它的意思是客户端使用Upgrade头字段要求在HTTP协议的基础上改成其他的协议继续通信比如WebSocket。而如果服务器也同意变更协议就会发送状态码101但这之后的数据传输就不会再使用HTTP了。
## 2××
2××类状态码表示服务器收到并成功处理了客户端的请求这也是客户端最愿意看到的状态码。
“**200 OK**”是最常见的成功状态码表示一切正常服务器如客户端所期望的那样返回了处理结果如果是非HEAD请求通常在响应头后都会有body数据。
“**204 No Content**”是另一个很常见的成功状态码它的含义与“200 OK”基本相同但响应头后没有body数据。所以对于Web服务器来说正确地区分200和204是很必要的。
“**206 Partial Content**”是HTTP分块下载或断点续传的基础在客户端发送“范围请求”、要求获取资源的部分数据时出现它与200一样也是服务器成功处理了请求但body里的数据不是资源的全部而是其中的一部分。
状态码206通常还会伴随着头字段“**Content-Range**”表示响应报文里body数据的具体范围供客户端确认例如“Content-Range: bytes 0-99/2000”意思是此次获取的是总计2000个字节的前100个字节。
## 3××
3××类状态码表示客户端请求的资源发生了变动客户端必须用新的URI重新发送请求获取资源也就是通常所说的“重定向”包括著名的301、302跳转。
“**301 Moved Permanently**”俗称“永久重定向”含义是此次请求的资源已经不存在了需要改用新的URI再次访问。
与它类似的是“**302 Found**”,曾经的描述短语是“**Moved Temporarily**”俗称“临时重定向”意思是请求的资源还在但需要暂时用另一个URI来访问。
301和302都会在响应头里使用字段**Location**指明后续要跳转的URI最终的效果很相似浏览器都会重定向到新的URI。两者的根本区别在于语义一个是“永久”一个是“临时”所以在场景、用法上差距很大。
比如你的网站升级到了HTTPS原来的HTTP不打算用了这就是“永久”的所以要配置301跳转把所有的HTTP流量都切换到HTTPS。
再比如今天夜里网站后台要系统维护服务暂时不可用这就属于“临时”的可以配置成302跳转把流量临时切换到一个静态通知页面浏览器看到这个302就知道这只是暂时的情况不会做缓存优化第二天还会访问原来的地址。
“**304 Not Modified**” 是一个比较有意思的状态码它用于If-Modified-Since等条件请求表示资源未修改用于缓存控制。它不具有通常的跳转含义但可以理解成“重定向已到缓存的文件”即“缓存重定向”
301、302和304分别涉及了HTTP协议里重要的“重定向跳转”和“缓存控制”在之后的课程中我还会细讲。
## 4××
4××类状态码表示客户端发送的请求报文有误服务器无法处理它就是真正的“错误码”含义了。
“**400 Bad Request**”是一个通用的错误码表示请求报文有错误但具体是数据格式错误、缺少请求头还是URI超长它没有明确说只是一个笼统的错误客户端看到400只会是“一头雾水”“不知所措”。所以在开发Web应用时应当尽量避免给客户端返回400而是要用其他更有明确含义的状态码。
“**403 Forbidden**”实际上不是客户端的请求出错而是表示服务器禁止访问资源。原因可能多种多样例如信息敏感、法律禁止等如果服务器友好一点可以在body里详细说明拒绝请求的原因不过现实中通常都是直接给一个“闭门羹”。
“**404 Not Found**”可能是我们最常看见也是最不愿意看到的一个状态码它的原意是资源在本服务器上未找到所以无法提供给客户端。但现在已经被“用滥了”只要服务器“不高兴”就可以给出个404而我们也无从得知后面到底是真的未找到还是有什么别的原因某种程度上它比403还要令人讨厌。
4××里剩下的一些代码较明确地说明了错误的原因都很好理解开发中常用的有
- 405 Method Not Allowed不允许使用某些方法操作资源例如不允许POST只能GET
- 406 Not Acceptable资源无法满足客户端请求的条件例如请求中文但只有英文
- 408 Request Timeout请求超时服务器等待了过长的时间
- 409 Conflict多个请求发生了冲突可以理解为多线程并发时的竞态
- 413 Request Entity Too Large请求报文里的body太大
- 414 Request-URI Too Long请求行里的URI太大
- 429 Too Many Requests客户端发送了太多的请求通常是由于服务器的限连策略
- 431 Request Header Fields Too Large请求头某个字段或总体太大
## 5××
5××类状态码表示客户端请求报文正确但服务器在处理时内部发生了错误无法返回应有的响应数据是服务器端的“错误码”。
“**500 Internal Server Error**”与400类似也是一个通用的错误码服务器究竟发生了什么错误我们是不知道的。不过对于服务器来说这应该算是好事通常不应该把服务器内部的详细信息例如出错的函数调用栈告诉外界。虽然不利于调试但能够防止黑客的窥探或者分析。
“**501 Not Implemented**”表示客户端请求的功能还不支持这个错误码比500要“温和”一些和“即将开业敬请期待”的意思差不多不过具体什么时候“开业”就不好说了。
“**502 Bad Gateway**”通常是服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常,访问后端服务器时发生了错误,但具体的错误原因也是不知道的。
“**503 Service Unavailable**”表示服务器当前很忙暂时无法响应服务我们上网时有时候遇到的“网络服务正忙请稍后重试”的提示信息就是状态码503。
503是一个“临时”的状态很可能过几秒钟后服务器就不那么忙了可以继续提供服务所以503响应报文里通常还会有一个“**Retry-After**”字段,指示客户端可以在多久以后再次尝试发送请求。
## 小结
1. 状态码在响应报文里表示了服务器对请求的处理结果;
1. 状态码后的原因短语是简单的文字描述,可以自定义;
1. 状态码是十进制的三位数分为五类从100到599
1. 2××类状态码表示成功常用的有200、204、206
1. 3××类状态码表示重定向常用的有301、302、304
1. 4××类状态码表示客户端错误常用的有400、403、404
1. 5××类状态码表示服务器错误常用的有500、501、502、503。
## 课下作业
1. 你在开发HTTP客户端收到了一个非标准的状态码比如4××、5××应当如何应对呢
1. 你在开发HTTP服务器处理请求时发现报文里缺了一个必需的query参数应该如何告知客户端错误原因呢
欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,欢迎你把文章分享给你的朋友。
<img src="https://static001.geekbang.org/resource/image/11/ad/11d330fe6de5b9fe34464a6994162dad.png" alt="">
<img src="https://static001.geekbang.org/resource/image/56/63/56d766fc04654a31536f554b8bde7b63.jpg" alt="unpreview">

View File

@@ -0,0 +1,104 @@
<audio id="audio" title="13 | HTTP有哪些特点" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d6/d6/d6554abef86d388199d15c4bc259e0d6.mp3"></audio>
通过“基础篇”前几讲的学习你应该已经知道了HTTP协议的基本知识了解它的报文结构请求头、响应头以及内部的请求方法、URI和状态码等细节。
你会不会有种疑惑“HTTP协议好像也挺简单的啊凭什么它就能统治互联网这么多年呢
所以接下来的这两讲我会跟你聊聊HTTP协议的特点、优点和缺点。既要看到它好的一面也要正视它不好的一面只有全方位、多角度了解HTTP才能实现“扬长避短”更好地利用HTTP。
今天这节课主要说的是HTTP协议的特点但不会讲它们的好坏这些特点即有可能是优点也有可能是缺点你可以边听边思考。
<img src="https://static001.geekbang.org/resource/image/78/4a/7808b195c921e0685958c20509855d4a.png" alt="">
## 灵活可扩展
首先, HTTP协议是一个“灵活可扩展”的传输协议。
HTTP协议最初诞生的时候就比较简单本着开放的精神只规定了报文的基本格式比如用空格分隔单词用换行分隔字段“header+body”等报文里的各个组成部分都没有做严格的语法语义限制可以由开发者任意定制。
所以HTTP协议就随着互联网的发展一同成长起来了。在这个过程中HTTP协议逐渐增加了请求方法、版本号、状态码、头字段等特性。而body也不再限于文本形式的TXT或HTML而是能够传输图片、音频视频等任意数据这些都是源于它的“灵活可扩展”的特点。
而那些RFC文档实际上也可以理解为是对已有扩展的“承认和标准化”实现了“从实践中来到实践中去”的良性循环。
也正是因为这个特点HTTP才能在三十年的历史长河中“屹立不倒”从最初的低速实验网络发展到现在的遍布全球的高速互联网始终保持着旺盛的生命力。
## 可靠传输
第二个特点, HTTP协议是一个“可靠”的传输协议。
这个特点显而易见因为HTTP协议是基于TCP/IP的而TCP本身是一个“可靠”的传输协议所以HTTP自然也就继承了这个特性能够在请求方和应答方之间“可靠”地传输数据。
它的具体做法与TCP/UDP差不多都是对实际传输的数据entity做了一层包装加上一个头然后调用Socket API通过TCP/IP协议栈发送或者接收。
不过我们必须正确地理解“可靠”的含义HTTP并不能100%保证数据一定能够发送到另一端,在网络繁忙、连接质量差等恶劣的环境下,也有可能收发失败。“可靠”只是向使用者提供了一个“承诺”,会在下层用多种手段“尽量”保证数据的完整送达。
当然如果遇到光纤被意外挖断这样的极端情况即使是神仙也不能发送成功。所以“可靠”传输是指在网络基本正常的情况下数据收发必定成功借用运维里的术语大概就是“3个9”或者“4个9”的程度吧。
## 应用层协议
第三个特点HTTP协议是一个应用层的协议。
这个特点也是不言自明的,但却很重要。
在TCP/IP诞生后的几十年里虽然出现了许多的应用层协议但它们都仅关注很小的应用领域局限在很少的应用场景。例如FTP只能传输文件、SMTP只能发送邮件、SSH只能远程登录等在通用的数据传输方面“完全不能打”。
所以HTTP凭借着可携带任意头字段和实体数据的报文结构以及连接控制、缓存代理等方便易用的特性一出现就“技压群雄”迅速成为了应用层里的“明星”协议。只要不太苛求性能HTTP几乎可以传递一切东西满足各种需求称得上是一个“万能”的协议。
套用一个网上流行的段子HTTP完全可以用开玩笑的口吻说“不要误会我不是针对FTP我是说在座的应用层各位都是垃圾。”
## 请求-应答
第四个特点HTTP协议使用的是请求-应答通信模式。
这个请求-应答模式是HTTP协议最根本的通信模型通俗来讲就是“一发一收”“有来有去”就像是写代码时的函数调用只要填好请求头里的字段“调用”后就会收到答复。
请求-应答模式也明确了HTTP协议里通信双方的定位永远是请求方先发起连接和请求是主动的而应答方只有在收到请求后才能答复是被动的如果没有请求时不会有任何动作。
当然,请求方和应答方的角色也不是绝对的,在浏览器-服务器的场景里,通常服务器都是应答方,但如果将它用作代理连接后端服务器,那么它就可能同时扮演请求方和应答方的角色。
HTTP的请求-应答模式也恰好契合了传统的C/SClient/Server系统架构请求方作为客户端、应答方作为服务器。所以随着互联网的发展就出现了B/SBrowser/Server架构用轻量级的浏览器代替笨重的客户端应用实现零维护的“瘦”客户端而服务器则摈弃私有通信协议转而使用HTTP协议。
此外,请求-应答模式也完全符合RPCRemote Procedure Call的工作模式可以把HTTP请求处理封装成远程函数调用导致了WebService、RESTful和gRPC等的出现。
## 无状态
第五个特点HTTP协议是无状态的。
这个所谓的“状态”应该怎么理解呢?
“状态”其实就是客户端或者服务器里保存的一些数据或者标志,记录了通信过程中的一些变化信息。
你一定知道TCP协议是有状态的一开始处于CLOSED状态连接成功后是ESTABLISHED状态断开连接后是FIN-WAIT状态最后又是CLOSED状态。
这些“状态”就需要TCP在内部用一些数据结构去维护可以简单地想象成是个标志量标记当前所处的状态例如0是CLOSED2是ESTABLISHED等等。
再来看HTTP那么对比一下TCP就看出来了在整个协议里没有规定任何的“状态”客户端和服务器永远是处在一种“**无知**”的状态。建立连接前两者互不知情,每次收发的报文也都是互相独立的,没有任何的联系。收发报文也不会对客户端或服务器产生任何影响,连接后也不会要求保存任何信息。
“无状态”形象地来说就是“没有记忆能力”。比如浏览器发了一个请求说“我是小明请给我A文件。”服务器收到报文后就会检查一下权限看小明确实可以访问A文件于是把文件发回给浏览器。接着浏览器还想要B文件但服务器不会记录刚才的请求状态不知道第二个请求和第一个请求是同一个浏览器发来的所以浏览器必须还得重复一次自己的身份才行“我是刚才的小明请再给我B文件。”
我们可以再对比一下UDP协议不过它是无连接也无状态的顺序发包乱序收包数据包发出去后就不管了收到后也不会顺序整理。而HTTP是有连接无状态顺序发包顺序收包按照收发的顺序管理报文。
但不要忘了HTTP是“灵活可扩展”的虽然标准里没有规定“状态”但完全能够在协议的框架里给它“打个补丁”增加这个特性。
## 其他特点
除了以上的五大特点其实HTTP协议还可以列出非常多的特点例如传输的实体数据可缓存可压缩、可分段获取数据、支持身份认证、支持国际化语言等。但这些并不能算是HTTP的基本特点因为这都是由第一个“灵活可扩展”的特点所衍生出来的。
## 小结
1. HTTP是灵活可扩展的可以任意添加头字段实现任意功能
1. HTTP是可靠传输协议基于TCP/IP协议“尽量”保证数据的送达
1. HTTP是应用层协议比FTP、SSH等更通用功能更多能够传输任意数据
1. HTTP使用了请求-应答模式,客户端主动发起请求,服务器被动回复请求;
1. HTTP本质上是无状态的每个请求都是互相独立、毫无关联的协议不要求客户端或服务器记录请求相关的信息。
## 课下作业
1. 就如同开头我讲的那样你能说一下今天列出的这些HTTP的特点中哪些是优点哪些是缺点吗
1. 不同的应用场合有不同的侧重方面,你觉得哪个特点对你来说是最重要的呢?
欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,欢迎你把文章分享给你的朋友。
<img src="https://static001.geekbang.org/resource/image/a2/7d/a233c19f92c566614e4e0facbaeab27d.png" alt="unpreview">
<img src="https://static001.geekbang.org/resource/image/56/63/56d766fc04654a31536f554b8bde7b63.jpg" alt="unpreview">

View File

@@ -0,0 +1,124 @@
<audio id="audio" title="14 | HTTP有哪些优点又有哪些缺点" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c0/8d/c03e1b29c56f439415690ec5a20c138d.mp3"></audio>
上一讲我介绍了HTTP的五个基本特点这一讲要说的则是它的优点和缺点。其实这些也应该算是HTTP的特点但这一讲会更侧重于评价它们的优劣和好坏。
上一讲我也留了两道课下作业,不知道你有没有认真思考过,今天可以一起来看看你的答案与我的观点想法是否相符,共同探讨。
不过在正式开讲之前我还要提醒你一下今天的讨论范围仅限于HTTP/1.1所说的优点和缺点也仅针对HTTP/1.1。实际上专栏后续要讲的HTTPS和HTTP/2都是对HTTP/1.1优点的发挥和缺点的完善。
## 简单、灵活、易于扩展
首先HTTP最重要也是最突出的优点是“**简单、灵活、易于扩展**”。
初次接触HTTP的人都会认为HTTP协议是很“**简单**”的基本的报文格式就是“header+body”头部信息也是简单的文本格式用的也都是常见的英文单词即使不去看RFC文档只靠猜也能猜出个“八九不离十”。
可不要小看了“简单”这个优点它不仅降低了学习和使用的门槛能够让更多的人研究和开发HTTP应用而且我在[第1讲](https://time.geekbang.org/column/article/97837)时就说过,“简单”蕴含了进化和扩展的可能性,所谓“少即是多”,“把简单的系统变复杂”,要比“把复杂的系统变简单”容易得多**。**
所以在“简单”这个最基本的设计理念之下HTTP协议又多出了“**灵活和易于扩展**”的优点。
“灵活和易于扩展”实际上是一体的它们互为表里、相互促进因为“灵活”所以才会“易于扩展”而“易于扩展”又反过来让HTTP更加灵活拥有更强的表现能力。
HTTP协议里的请求方法、URI、状态码、原因短语、头字段等每一个核心组成要素都没有被“写死”允许开发者任意定制、扩充或解释给予了浏览器和服务器最大程度的信任和自由也正好符合了互联网“自由与平等”的精神——缺什么功能自己加个字段或者错误码什么的补上就是了。
“请勿跟踪”所使用的头字段 DNTDo Not Track就是一个很好的例子。它最早由Mozilla提出用来保护用户隐私防止网站监测追踪用户的偏好。不过可惜的是DNT从推出至今有差不多七八年的历史但很多网站仍然选择“无视”DNT。虽然DNT基本失败了但这也正说明HTTP协议是“灵活自由的”不会受单方面势力的压制。
“灵活、易于扩展”的特性还表现在HTTP对“可靠传输”的定义上它不限制具体的下层协议不仅可以使用TCP、UNIX Domain Socket还可以使用SSL/TLS甚至是基于UDP的QUIC下层可以随意变化而上层的语义则始终保持稳定。
## 应用广泛、环境成熟
HTTP协议的另一大优点是“**应用广泛**”,软硬件环境都非常成熟。
随着互联网特别是移动互联网的普及HTTP的触角已经延伸到了世界的每一个角落从简单的Web页面到复杂的JSON、XML数据从台式机上的浏览器到手机上的各种APP从看新闻、泡论坛到购物、理财、“吃鸡”你很难找到一个没有使用HTTP的地方。
不仅在应用领域在开发领域HTTP协议也得到了广泛的支持。它并不限定某种编程语言或者操作系统所以天然具有“**跨语言、跨平台**”的优越性。而且因为本身的简单特性很容易实现所以几乎所有的编程语言都有HTTP调用库和外围的开发测试工具这一点我觉得就不用再举例了吧你可能比我更熟悉。
HTTP广泛应用的背后还有许多硬件基础设施支持各个互联网公司和传统行业公司都不遗余力地“触网”购买服务器开办网站建设数据中心、CDN和高速光纤持续地优化上网体验让HTTP运行的越来越顺畅。
“应用广泛”的这个优点也就决定了无论是创业者还是求职者无论是做网站服务器还是写应用客户端HTTP协议都是必须要掌握的基本技能。
## 无状态
看过了两个优点我们再来看看一把“双刃剑”也就是上一讲中说到的“无状态”它对于HTTP来说既是优点也是缺点。
“无状态”有什么好处呢?
因为服务器没有“记忆能力”所以就不需要额外的资源来记录状态信息不仅实现上会简单一些而且还能减轻服务器的负担能够把更多的CPU和内存用来对外提供服务。
而且,“无状态”也表示服务器都是相同的,没有“状态”的差异,所以可以很容易地组成集群,让负载均衡把请求转发到任意一台服务器,不会因为状态不一致导致处理出错,使用“堆机器”的“笨办法”轻松实现高并发高可用。
那么,“无状态”又有什么坏处呢?
既然服务器没有“记忆能力”,它就无法支持需要连续多个步骤的“事务”操作。例如电商购物,首先要登录,然后添加购物车,再下单、结算、支付,这一系列操作都需要知道用户的身份才行,但“无状态”服务器是不知道这些请求是相互关联的,每次都得问一遍身份信息,不仅麻烦,而且还增加了不必要的数据传输量。
所以HTTP协议最好是既“无状态”又“有状态”不过还真有“鱼和熊掌”两者兼得这样的好事这就是“小甜饼”Cookie技术第19讲
## 明文
HTTP协议里还有一把优缺点一体的“双刃剑”就是**明文传输**。
“明文”意思就是协议里的报文准确地说是header部分不使用二进制数据而是用简单可阅读的文本形式。
对比TCP、UDP这样的二进制协议它的优点显而易见不需要借助任何外部工具用浏览器、Wireshark或者tcpdump抓包后直接用肉眼就可以很容易地查看或者修改为我们的开发调试工作带来极大的便利。
当然明文的缺点也是一样显而易见HTTP报文的所有信息都会暴露在“光天化日之下”在漫长的传输链路的每一个环节上都毫无隐私可言不怀好意的人只要侵入了这个链路里的某个设备简单地“旁路”一下流量就可以实现对通信的窥视。
你有没有听说过“免费WiFi陷阱”之类的新闻呢
黑客就是利用了HTTP明文传输的缺点在公共场所架设一个WiFi热点开始“钓鱼”诱骗网民上网。一旦你连上了这个WiFi热点所有的流量都会被截获保存里面如果有银行卡号、网站密码等敏感信息的话那就危险了黑客拿到了这些数据就可以冒充你为所欲为。
## 不安全
与“明文”缺点相关但不完全等同的另一个缺点是“不安全”。
安全有很多的方面明文只是“机密”方面的一个缺点在“身份认证”和“完整性校验”这两方面HTTP也是欠缺的。
“身份认证”简单来说就是“**怎么证明你就是你**”。在现实生活中比较好办,你可以拿出身份证、驾照或者护照,上面有照片和权威机构的盖章,能够证明你的身份。
但在虚拟的网络世界里这却是个麻烦事。HTTP没有提供有效的手段来确认通信双方的真实身份。虽然协议里有一个基本的认证机制但因为刚才所说的明文传输缺点这个机制几乎可以说是“纸糊的”非常容易被攻破。如果仅使用HTTP协议很可能你会连到一个页面一模一样但却是个假冒的网站然后再被“钓”走各种私人信息。
HTTP协议也不支持“完整性校验”数据在传输过程中容易被窜改而无法验证真伪。
比如你收到了一条银行用HTTP发来的消息“小明向你转账一百元”你无法知道小明是否真的就只转了一百元也许他转了一千元或者五十元但被黑客窜改成了一百元真实情况到底是什么样子HTTP协议没有办法给你答案。
虽然银行可以用MD5、SHA1等算法给报文加上数字摘要但还是因为“明文”这个致命缺点黑客可以连同摘要一同修改最终还是判断不出报文是否被窜改。
为了解决HTTP不安全的缺点所以就出现了HTTPS这个我们以后再说。
## 性能
最后我们来谈谈HTTP的性能可以用六个字来概括“**不算差,不够好**”。
HTTP协议基于TCP/IP并且使用了“请求-应答”的通信模式,所以性能的关键就在这两点上。
必须要说的是TCP的性能是不差的否则也不会纵横互联网江湖四十余载了而且它已经被研究的很透集成在操作系统内核里经过了细致的优化足以应付大多数的场景。
只可惜如今的江湖已经不是从前的江湖现在互联网的特点是移动和高并发不能保证稳定的连接质量所以在TCP层面上HTTP协议有时候就会表现的不够好。
而“请求-应答”模式则加剧了HTTP的性能问题这就是著名的“队头阻塞”Head-of-line blocking当顺序发送的请求序列中的一个请求因为某种原因被阻塞时在后面排队的所有请求也一并被阻塞会导致客户端迟迟收不到数据。
为了解决这个问题就诞生出了一个专门的研究课题“Web性能优化”HTTP官方标准里就有“缓存”一章RFC7234非官方的“花招”就更多了例如切图、数据内嵌与合并域名分片、JavaScript“黑科技”等等。
不过现在已经有了终极解决方案HTTP/2和HTTP/3后面我也会展开来讲。
## 小结
1. HTTP最大的优点是简单、灵活和易于扩展
1. HTTP拥有成熟的软硬件环境应用的非常广泛是互联网的基础设施
1. HTTP是无状态的可以轻松实现集群化扩展性能但有时也需要用Cookie技术来实现“有状态”
1. HTTP是明文传输数据完全肉眼可见能够方便地研究分析但也容易被窃听
1. HTTP是不安全的无法验证通信双方的身份也不能判断报文是否被窜改
1. HTTP的性能不算差但不完全适应现在的互联网还有很大的提升空间。
虽然HTTP免不了这样那样的缺点但你也不要怕别忘了它有一个最重要的“灵活可扩展”的优点所有的缺点都可以在这个基础上想办法解决接下来的“进阶篇”和“安全篇”就会讲到。
## 课下作业
1. 你最喜欢的HTTP优点是哪个最不喜欢的缺点又是哪个为什么
1. 你能够再进一步扩展或补充论述今天提到这些优点或缺点吗?
1. 你能试着针对这些缺点提出一些自己的解决方案吗?
欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,欢迎你把文章分享给你的朋友。
<img src="https://static001.geekbang.org/resource/image/75/ad/7573b0a37ed275bbf6c94eb20875b1ad.png" alt="unpreview">