HTTP 协议分析
💡 应用层协议:定义了运行在不同端系统上的应用程序进程如何相互传递报文
应用层协议定义了如下内容
- 交换的报文类型,例如请求报文和响应报文
- 各种报文类型的语法,如报文的各个字段及这些字段如何描述
- 字段的语义
- 确定一个进程何时以及如何发送报文,对报文进行响应的规则
💡 http协议是由RFC文档定义的一个Web的应用层协议,全名是超文本传输协议(HyperText Transfer Protocol, HTTP) 💡 http定义了Web客户向Web服务器请求Web页面的方式,以及服务器向客户传送Web页面的方式。
TCP三次握手
💡 HTTP使用TCP作为它的支撑运输协议,HTTP客户首先发起一个与服务器的TCP连接,一旦连接成立,该浏览器和服务器进程就可以通过套接字接口访问TCP。
三次握手:
- 第一次握手:由客户端向服务端发送连接请求
SYN
报文,该报文段中包含自身的数据通讯初始序号,请求发送后,客户端便进入SYN-SENT
状态。 - 第二次握手:服务端收到连接请求报文段后,如果同意连接,则会发送一个包含了
ACK
和SYN
报文信息的应答,该应答中也会包含自身的数据通讯初始序号 (在断开连接的"四次挥手"时,ACK
和SYN
这两个报文是作为两次应答,独立开来发送的,因此会有四次挥手),服务端发送完成后便进入SYN-RECEIVED
状态。 - 第三次握手:当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入
ESTABLISHED
状态,服务端收到这个应答后也进入ESTABLISHED
状态,此时连接建立成功。
💡 提示:关于 `ACK`、`FIN`、`SYN` 状态码的含义
ACK
用于确认,表示通知对方,我已经收到你发来的信息了。FIN
用于结束,表示告知对方,我这边已经结束,数据全部发送完毕,没有后续输出,请求终止连接。SYN
用于同步和建立连接,表示告知对方,我这边请求同步建立连接。
💡 面试时可能会问的一个问题就是,明明两次握手就能确定的连接,为什么需要三次握手? 因为由于很多不可控制的因素,例如网络原因,可能会造成第一次请求隔了很久才到达服务端,这个时候客户端已经等待响应等了很久,之前发起的请求已超时,已经被客户端废弃掉不再继续守着监听了。 然而服务端过了很久,收到了废弃的延迟请求,发起回应的同时又开启了一个新的 `TCP` 连接端口,在那里呆等客户端。 而服务端能维护的 `TCP` 连接是有限的,这种闲置的无用链接会造成服务端的资源浪费。 因此在服务端发送了 `SYN` 和 `ACK` 响应后,需要收到客户端接的再次确认,双方连接才能正式建立起来。三次握手就是为了规避这种由于网络延迟而导致服务器额外开销的问题。
四次挥手
和建立 TCP
连接类似,断开 TCP
连接也同样需要客户端于服务端的双向交流,因为整个断开动作需要双端共发送 4 个数据包才能完成,所以简称为"四次挥手"。
- 第一次挥手:客户端认为自己这边的数据已经全部发送完毕了,于是发送一个
FIN
用来关闭客户端到服务端的数据传输,发送完成以后,客户端进入FIN_WAIT_1
状态。 - 第二次挥手:服务端收到客户端发送回来的
FIN
以后,会告诉应用层要释放 TCP 链接,并且发送一个ACK
给客户端,表明已经收到客户端的释放请求了,不会再接受客户端发来的数据,自此,服务端进入CLOSE_WAIT
的状态。 - 第三次挥手:服务端如果此时还有未发送完的数据可以继续发送,发送完毕后,服务端也会发送一个释放连接的
FIN
请求用来关闭服务端到客户端的数据传送,然后服务端进入LAST_ACK
状态。 - 第四次挥手:客户端接收到服务端的
FIN
请求后,发送最后一个ACK
给服务端,接着进入TIME_WAIT_2
状态,该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,客户端就进入CLOSED
状态.服务端在收到应答消息后,也会进入CLOSED
状态,至此完成四次挥手的过程,双方正式断开连接。
💡 可能有些面试中会问,为什么建立连接有三次握手,而断开连接却有四次? 这是因为在建立连接过程中,服务端在收到客户但建立连接请求的 `SYN` 报文后,会把 `ACK` 和 `SYN` 放在一个报文里发送给客户端。 而关闭连接时,服务端收到客户端的 `FIN` 报文,只是表示客户端不再发送数据了,但是还能接收数据,而且这会儿服务端可能还有数据没有发送完,不能马上发送 `FIN` 报文,只能先发送 `ACK` 报文,先响应客户端,在确认自己这边所有数据发送完毕以后,才会发送 `FIN`。 所以,在断开连接时,服务器的 `ACK` 和 `FIN` 一般都会单独发送,这就导致了断开连接比请求连接多了一次发送操作。
RTT:往返时间,指一个短分组从客户到服务器然后再返回客户所花费的时间
所以发送请求到接受响应的总时间是:2个RTT加一个服务器传输文件的时间。一个RTT用来创建TCP,一个用来请求和接收对象
非持续连接和持续连接
问题:客户和服务器可能在相当长的时间范围内通信,在此期间,客户发出一系列的请求,并且服务器对每个请求进行响应。
思考:每个请求/响应对是经一个单独的TCP连接发送(非持续连接),还是所有的请求及其响应经相同的TCP连接发送(持续连接)。
💡 HTTP 1.1支持持续连接,服务器在发送响应后保持该TCP连接打开,后续的请求和响应能够通过相同的连接进行传送。
非持续连接缺点:
- 必须为每一个请求的对象建立和维护一个全新的连接,因为在客户和服务器都要分配TCP的缓冲区和保持TCP变量,给Web服务器带来了严重的负担
- 每一个对象经受两倍的RTT交付时延,一个RTT用来创建TCP,一个用来请求和接收对象
HTTP 报文格式
HTTP请求报文
makefile
GET /somedir/page.html HTTP/1.1
Host:www.someschool.deu
Connection:close
User-agent:Mozilla/5.0
Accept-language: fr
请求报文的第一行:请求行,后继叫:首部行
请求行三个字段:方法字段、URL字段和HTTP版本字段
首部行:常见的请求头如下
属性 | 说明 |
---|---|
Accept | 接收类型,表示浏览器支持的MIME类型(对标服务端返回的Content-Type) |
Content-Type | 客户端发送出去实体内容的类型 |
Cache-Control | 指定请求和响应遵循的缓存机制,如no-cache |
lf-Modified-Since | 对应服务端的Last-Modified,用来匹配看文件是否变动,只能精确到1s之内 |
Expires | 缓存控制,在这个时间内不会请求,直接使用缓存,服务端时间 |
Max-age | 代表资源在本地缓存多少秒.有效时间内不会请求,而是使用缓存 |
lf-None-Match | 对应服务端的ETag,用来匹配文件内容是否改变(非常精确) |
Cookie | 有cookie并且同域访问时会自动带上 |
Referer | 该页面的来源URL(适用于所有类型的请求,会精确到详细页面地址, csrf拦截常用到这个字段) |
Origin | 最初的请求是从哪里发起的(只会精确到端口),Origin比Referer更尊重隐私 |
User-Agent | 用户客户端的一些必要信息,如UA头部等 |
HTTP响应报文
ruby
HTTP/1.1 200 OK
Connection:close
Date:Tue,18 Aug 2015 15:44:04 GMT
Server:Apache/2.2.3 (CentOS)
Last-Modified:Tue,18 Aug 2015 15:11:03 GMT
Content-Length:6821
Content-Type:text/html
(data data data data .....)
响应报文有三个部分:初始状态行,首部行,实体体,实体体是报文的主要部分,包含了所请求的对象本身(data data data data ....)
属性 | 说明 |
---|---|
Content-Type | 服务端返回的实体内容的类型 |
Cache-Control | 指定请求和响应遵循的缓存机制,如no-cache |
Last-Modified | 请求资源的最后修改时间 |
Expires | 应该在什么时候认为文档已经过期,从而不再缓存它 |
Max-age | 客户端的本地资源应该缓存多少秒,开启了Cache-Control后有效 |
ETag | 资源的特定版本的标识符, Etags类似于指纹 |
Set-Cookie | 设置和页面关联的cookie,服务器通过这个头部把cookie传给客户端 |
Server | 服务器的一些相关信息 |
Access-Control-Allow-Origin | 服务器端允许的请求Origin头部(譬如为*) |
cookie
HTTP 服务是无状态的,服务器不会因为刚刚响应这个请求就对第二次相同请求不做响应。
问题:一个Web站点通常希望能过识别用户,可能是因为服务器希望限制用户的访问,或者因为它希望把内容与用户身份联系起来。
解决:HTTP 使用了cookie,在[RFC 625]中定义,允许站点对用户跟踪。
cookie技术有4个组件:
- 在HTTP 响应报文中的一个cookie首部行
- 在HTTP 请求报文中的一个cookie首部行
- 在用户端系统的保留的一个cookie文件,并由用户的浏览器进行管理
- 位于Web站点的一个后端数据库
举例:
Susan首次在家中用PC与Amazon.com联系,当请求报文到达Amazon Web服务器时,该Web站点产生一个唯一识别码,以此为索引在后端数据库中产生一个表项,接下来服务器用一个包含 "Set-cookie:"首部的HTTP 响应报文对Susan的浏览器响应。当Susan的浏览器收到响应报文时,发现"Set-cookie",会在它管理的cookie文件中添加一行,包含服务器主机名和cookie识别码。当Susan继续浏览Amazon网站时,每请求一个Web页面,其浏览器就会查询cookie文件,放到HTTP 请求报文中的cookie首部行中。
HTTP/2
2015年标准化的HTTP/2是自HTTP/1.1以后的首个版本。2020你那,排名前1000万的Web站点中超过40%支持HTTP/2。
💡 HTTP/2的基本目标:摆脱(或至少减少其数量)传送单一Web页面时的并行 TCP 连接。
问题:HTTP/1.1使用持续TCP连接,每个Web页面使用一个TCP,使服务器的套接字数量被压缩,但单一TCP连接出现了******队首阻塞(HOL)******问题。HTTP/1.1解决HOL的方法是打开多个并行的TCP连接,但增加了TCP数量。
💡 HOL:如果一个响应返回延迟了,那么其后续的响应都会被延迟,直到队头的响应送达。
解决:HTTP/2解决方案是将每个报文分成小帧,在相同的TCP连接上交错发送请求和响应报文。
HTTP/2成帧
将一个HTTP报文分成独立的帧、交错发送它们并在接收端将其装配起来的能力。
💡 成帧子层:对每个HTTP报文划分为独立的帧,对这些帧进行二进制编码。
流程:服务器要发送一个HTTP响应时,其响应由成帧子层处理划分为帧,该响应的帧与其他响应的帧交错并经过单一持续TCP连接发送。当这些帧到达客户是,在成帧子层装配成初始的响应报文。
服务器推
允许服务器为一个客户请求而发送多个响应,即除了初始请求的响应,推送额外的对象。
💡 因为HTML基本页指示了需要在页面呈现的全部对象,所以是可实现的。消除了因等待这些请求而产生的额外延时。
结语
之后会更新缓存和浏览器跨域的知识点。