这是一份关于HTTP协议的学习总结

3,367 阅读56分钟

前言

随着互联网的发展,网络已经越来越普及了,绝大多数的网络请求都是基于HTTP协议的,因此在开发中,了解HTTP的基本原理是必要的,在TCP/IP四层体系结构中,HTTP协议位于应用层,它是应用层主要使用的协议,应用层往下一层就是运输层,HTTP在运输层采用的是TCP协议来保证可靠传输,知道这些后,接下来详细介绍一下 Http。

一、HTTP协议版本演变

我们先来简单了解一下 HTTP 协议的历史演变:

  • HTTP/1.0:1996年,HTTP/1.0 版本发布,可以传输文字,图像、视频、二进制文件,它的特点是每次请求都需要建立一个单独的TCP连接,发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接,上一次请求和下一次请求完全分离,这种非持续连接过程又叫做短连接。它的特点也是它的缺点,客户端和服务端每次请求都要建立TCP连接,而建立TCP连接和关闭TCP连接都是相对比较费时的过程,严重影响客户端和服务端的性能。
  • HTTP/1.1:1997年,HTTP/1.1 版本发布,1999年广泛应用于各大浏览器网络请求中,直到现在HTTP/1.1也是使用最为广泛的HTTP协议,它进一步完善了HTTP/1.0,HTTP/1.1支持在一个TCP连接上传送多个HTTP请求和响应,即一个TCP连接可以被多个请求复用,减少了建立和关闭连接的消耗延迟,一定程度上弥补了HTTP/1.0每次请求都要创建连接的缺点,这种持续连接过程又叫做长连接,HTTP/1.1默认使用长连接。
  • HTTP/2.0:2015年,HTTP/2 .0版本发布,前面的两个版本都是基于超文本的协议,HTTP 2.0把基于超文本的协议改成了基于二进制的,把HTTP请求和响应变成数据帧,这样易于实现多路复用,在一个TCP连接上可以同时“混合”发送多个HTTP的请求和响应,效率大大提高。

长连接和多路复用的区别

上面讲到HTTP/1.1的长连接和HTTP 2.0的多路复用都是复用TCP连接,它们之间有什么区别呢?在讲解它们的区别之前先来聊一聊HTTP/1.1的长连接,HTTP/1.1的长连接可以分为非流水线和流水线工作方式:

  • 非流水线:在同一个TCP连接下,客户端的下一个请求只有收到当前请求的响应后才能发出;
  • 流水线:又称Pipelining、管道化,在同一个TCP连接下,客户端可以连续的发送请求,而不用等待响应返回,当请求一个接着一个到达服务器时,服务器能够连续发回响应;

如图:

所以你会发现非流水线工作方式的长连接的若干个请求只能排队发送,后面的请求等待前面请求的响应返回才能获得执行机会,一旦有某请求处理超时,后续请求只能被阻塞,这样会导致TCP连接空闲,浪费资源;而流水线工作方式的长连接就可以连续的发送多个请求而不必等待响应的返回,这样看起来效率提高了一些。

但是Pipelining在现代浏览器中是默认关闭的,因为由于没有规范指导和技术安全的原因,实现Pipelining在HTTP/1.X上是很复杂,同时Pipelining技术不支持所有的请求方法,而且就算开启了,由于HTTP队头阻塞存在,它的效率也会受到服务端响应处理排队的影响,HTTP规定响应的返回必须进行严格的排队,迫使每个响应必须等待前一个响应传输完成后才返回,假设多个请求同时到达服务端,如果第一个请求处理时间过长,会导致后面的请求响应阻塞,即使后面的请求先处理完成了,而且很有可能后面有优先级更高的请求,这样导致高优先级的请求不能被优先处理。

为了解决没有开启Pipelining导致请求排队的问题,现代浏览器支持在同一个域名(host)下建立多个TCP连接,一般支持同时建立5~10个TCP连接,即支持并行发送请求,每个浏览器都有一个Max-Connection最大连接限制,例如谷歌浏览器的Max-Connection值为6,即同一个域名下最多建立6个TCP连接,一次最多同时发送6个请求,超过限制后续请求就会被阻塞。

所以综上所述,由于Pipelining技术在浏览器中默认是关闭的,所以HTTP1.1长连接的工作方式是非流水线形式,为了实现并行发送请求只能通过建立多个TCP连接,这在HTTP1.x时代中已经很不错了,但是随着技术的发展,现在一个网站每秒简简单单就上百个请求,而频繁的建立和保留TCP连接又是一件很耗费系统资源的工作,所以并行TCP连接带来的效率有时也会很低。

为了解决HTTP/1.x的问题,HTTP/2.0就诞生了,HTTP/2.0在TCP层上引入一个二进制帧层, 请求和响应被拆分为更小的帧发送,不同帧之前可以交错发送,然后在另一端重新组装,从而实现了请求和响应的多路复用(multiplexing),做到了同一个TCP连接下的真正的并发请求,多个请求和响应可同时在一个TCP连接上并行发送和返回,某个请求任务超时,不会影响到其它请求的正常执行,而且每个请求都可以分配优先级,优先级高的先执行,如图:

HTTP/2.0从协议的层面改进了HTTP,是未来的应用, HTTP/2.0介绍不是本文重点,对于HTTP2.0的更多优点可以查看HTTP2.0新特性, 本文讨论都是围绕HTTP/1.x

HTTP1.1的长连接在浏览器中是默认开启的,通过指定首部Connection:Keep-Alive,后面会讲到长连接的工作原理。

HTTP2.0的多路复用解决了HTTP的队头阻塞,而TCP的队头阻塞还是存在的,要解决TCP的队头阻塞只能改变运输层协议,例如使用基于UDP的QUIC协议。

二、HTTP工作过程

HTTP是基于TCP的应用层协议,从更高层次封装了TCP的使用细节,使得网络操作更为简单,一个HTTP请求就是一个典型的C/S模式,HTTP协议首先要和服务端建立TCP连接,当建立TCP连接的三报文握手的前两次报文握手完成后,在第三次握手,客户端就把HTTP请求报文作为第三个握手报文的数据发送给服务端,服务端收到请求报文后,就把所请求的文档作为响应报文返回给客户端,如下:

HTTP的工作特点可以总结为以下3点:

  • 1、面向无连接的:即通信双方在交换HTTP报文时不需要向建立HTTP连接,但HTTP使用了面向连接的TCP作为运输层协议;
  • 2、无状态的:服务端不会记得每个客户访问的状态,同一个客户访问两次服务端上的页面时,服务端响应与第一次访问相同,所以出现了Cookie/Session机制维护连接的状态;
  • 3、面向事务的:即对一系列信息的交换,要么所有信息交换都完成,要么一次交换都不进行。

三、HTTP的请求方法

HTTP协议提供了几种请求方式,大家熟知的请求方式有8种GET、POST、DELETE、PUT、HEAD、TRACE、OPTIONS、CONNECT,其中最常用的是PUT(增)、DELETE(删)、POST(改)、GET(查)。下面以一张表来看看它们各自的作用。

请求作用
GET获取资源:客户端通过URL获取服务端中的某个资源,请求参数放在URL中,然后服务端返回对应资源给客户端
POST传输实体主体:POST请求通常会用来提交HTML表单,把数据填在表单中,传给服务器,然后服务器对这些数据进行处理,虽然GET方法也可以用来传输主体实体,但是一般采用POST方法
PUT传输文件:与GET相反,PUT向服务器写入数据,一般用来传输文件,把需要传输的文件放在请求报文的主体上,然后保存到URL指定的位置
DELETE删除文件:与PUT相反,DELETE请求求服务器删除URL所指定的资源,请求参数放在URL中,但是服务端可以在客户端不知情下撤销此请求
HEAD获取报文首部:HEAD与GET类似,但服务器在响应中只返回首部不会返回主体部分,HEAD是用来在不获取资源的情况下获取资源的首部进行检查,如查看响应的状态码,看看资源是否被修改,对象是否存在
TRACE追踪路径:客户端发起一个请求时,可能要穿过防火墙,代理,网关等,每一个中间点都会修改HTTP原始请求报文,TRACE允许请求最终发送给服务端时,看看它最终变成什么样,服务端会返回一个状态码200 OK的响应报文,报文主体包含了TARCE信息
OPTIONS询问支持的方法:OPTIONS询问服务端支持的用来查询指定URL资源的方法,这就让客户端不用访问那些实际的资源就能判定访问各种资源的最优方法
CONNECT要求使用隧道协议连接代理:CONNECT要求在与代理服务器通信时建立隧道,实现用隧道进行TCP通信,隧道就是经过加密的通信信路,一般使用SSL/TLS协议把通信内容加密后经隧道传输

HTTP/1.0支持的方法有:

GET、POST、PUT、HEAD、DELETE;

HTTP/1.1新增的方法有:

OPTIONS、TARCE、CONNECT。

代理服务器:代理服务器是一种具有转发功能的服务器,它扮演了服务器和客户端之间的中间人角色,它可以接收客户端发来的请求并转发给服务端,也可以接收服务端返回的响应并转发给客户端,在这个过程中它不会改变任何URL,报文每经过一个代理服务器,都需要在首部via字段的末尾插入一个可以代表代理服务器的独特的字符串, 代理服务器主要作用有:缓存代理(减少网络带宽)、访问控制(提高安全性)等。

四、HTTP的报文格式

用于HTTP协议交换的信息称为HTTP报文,客户端发出的HTTP报文叫做请求报文,服务端返回的HTTP报文叫做响应报文,它们都是由多行数据构成的字符串文本,用CR + LF (回车符 + 换行符) 作为换行符,HTTP报文大体分为报文首部报文主体两块,由第一个出现的空行(CR + LF)划分,其中报文主体不是必须的,如下:

其中报文首部又可以分为:开始行、首部行;开始行根据报文的不同又分为:请求行、状态行;首部行根据报文的不同与首部字段的作用又可以分为:请求首部字段、响应首部字段、通用首部字段、实体首部字段。

下面分别简单介绍一下HTTP的请求报文和响应报文:

1、请求报文

一个HTTP的请求报文通常由请求行,请求首部,空行(CR + LF),请求主体4个部分组成,如图:

  • 请求行

    又叫起始行,就是报文的第一行,在请求报文中说明要以什么方式做什么请求;

  • 请求首部

    又叫首部,在请求行之后,由零个或多个首部字段组成,每个字段包含一个key和value,用冒号 : 分割,如Connection:keep-Alive,每个首部字段以一个CR + LF结束;

  • 请求主体

    又叫主体,其中可以包含任意类型的数据,如图片,视频、文本等,而请求首部和请求行只能是文本形式,在请求主体中包括了要发送给Web服务端的数据。

不同的请求方式,它们的请求报文格式可能有点差别的,有些请求方式它的请求主体为空,有些则不为空,但是请求行和请求首部是必须存在的,下面以GET、POST请求报文举例:

1.1、GET的请求报文

对于GET方法来说,它所有的请求参数都是拼接在URL最后,第一个参数前通过"?"连接,然后请求参数按照"key=value"格式进行追加,每个请求参数之间通过"&"连接,如 :

www.myhost.com/text/?id=1&…

这个URL对于GET请求表示获取 www.myhost.com/text/ 位置下用户id为1名为rain的文本,相应的请求报文格式如下:

GET /text/?id=1&name=rain HTTP/1.1
Host: www.myhost.com
Cache-Control: no-cache

从上面的HTTP请求报文格式知,第一行为请求行,表明请求方式为GET,子路径为 /text/?id=1&name=rain,HTTP版本为1.1,后两行为请求首部,Host为主机地址,Cache-Controlno-cache,表示客户端不接受服务端缓存过的资源,而GET的请求参数都在URL中,所以请求主体为空。

注意:对于URL的最长长度,不同的浏览器又不同的限制,大约为1024字节(1KB)。

1.2、POST的请求报文

对于POST方法来说,它们的报文格式一般是表单格式,也就是说请求参数存储在请求主体位置上,如下:

POST /local/ HTTP/1.1
Host: www.myhost.com
Accept-Encoding:gzip
Content-Length: 222222
Content-Type: multipart/from-data;boundary=dRGP2cPPTxE6WRTssnh4jC7HJLcSde
Connection:Keep-Alive

--dRGP2cPPTxE6WRTssnh4jC7HJLcSde
Content-Disposition:from-data;name=“username”  //name = username
Content-Type:text/plain:charset=UTF-8
Content-Transfer-Encoding: 8bit

rain										//value = rain
--dRGP2cPPTxE6WRTssnh4jC7HJLcSde
Content-Diaposition:from-data:name="image"      //name = image
filename="/storage/emulated/0/image/1234.png"
Content-Type:application/octet-stream
Content-Transfer-Encoding:binary

//...省略二进制数据					        //value = 二进制数据
--dRGP2cPPTxE6WRTssnh4jC7HJLcSde--

上述的请求报文的含义是向 www.myhost.com/local/ 这个地址发送一个POST请求,接受的内容编码方式为gzip,请求的数据长度为222222字节,请求的数据格式为 multipart/from-data(表单),报文的boundary值为dRGP2cPPTxE6WRTssnh4jC7HJLcSdeKeep-Alive为开启长连接,空行之后,接下来就是请求报文的主体,主体有两个请求参数:

一个是名为username值为rain的文本;

一个是名为image值为二进制数据的图片.

请求参数是以两横杠+boundary开始的,然后是请求参数的一些首部,又称实体首部字段,如参数名,格式等,然后加上一个空行,最后才是参数的值,如上述的username=name,其表示如下:

--dRGP2cPPTxE6WRTssnh4jC7HJLcSde		       //两横杠加boundary作为参数的开始
Content-Disposition:from-data;name=“username” //name = username
Content-Type:text/plain:charset=UTF-8
Content-Transfer-Encoding: 8bit
						                    
rain						                 //value = rain

当报文主体中包含多个参数时,都要遵守这种格式:每个参数以两横杠+boundary分隔,参数首部字段与值之间有一个空行

请求主体的最后是以两横杆+boundary+两横杠作为整个报文的结束符,如上面报文的最后一个参数 (图片二进制数据) 最后的**--dRGP2cPPTxE6WRTssnh4jC7HJLcSde--**,如下:

--dRGP2cPPTxE6WRTssnh4jC7HJLcSde			 //两横杠加boundary作为参数的开始
Content-Diaposition:from-data:name="image"    //name = image
filename="/storage/emulated/0/image/1234.png" 
Content-Type:application/octet-stream
Content-Transfer-Encoding:binary
										  //不可省略的空行
//...省略二进制数据						  //value = 二进制数据
--dRGP2cPPTxE6WRTssnh4jC7HJLcSde--	          //整个报文的结束符

2、响应报文

一个HTTP的响应报文通常由状态行、响应首部、空行(CR + LF)、响应主体组成,如下:

  • 状态行

    在响应报文中粗略的说明了报文的执行结果;

  • 响应首部

    又叫首部,在状态行之后,由零个或多个首部字段组成,每个字段包含一个key和value,用冒号 : 分割,每个首部以一个CR + LF结束;

  • 响应主体

    其中可以包含任意类型的数据,如图片,视频、文本等,而首部和状态行只能是文本形式,在响应主体中包含了服务端要返回给客户端的数据.

可以看到响应报文与请求报文的格式类似,最大的不同的就是第一行用状态信息代替了请求信息,格式如下:

HTTP-Version Status-Code Reason-Phrase CRLF

其中HTTP-Version代表HTTP协议版本,Status-Code代表响应状态码,Reason-Phrase代表状态码的文本描述,其中状态码的5种取值范围如下:

取值范围含义
100~199信息状态码,表示请求已被接收,正在处理
200~299成功状态码,表示请求已被成功处理
300~399重定向状态码,表示完成请求必须要进行进一步的操作
400~499客户端错误状态码,表示客户端请求有语法错误或请求无法实现
500~599服务端错误状态码,表示服务端处理请求时出错

例如这是一个GET请求的返回的响应报文格式:

HTTP1.1 200 OK
Data:Sat, 30, Dec 2006 23:23:00 GMT
Content-Type:text/html;charset=UTF-8
Content-Length:852

<!DOCTYPE html>
<html lang="zh-CN">
	//...省略文档内容
</html>

上面HTTP响应报文表示,HTTP协议版本为1.1,响应状态码为200,表示请求成功,返回数据的类型为text/html(html), 编码为UTF-8,返回数据的内容长度为852字节,空行之后,接下来就是返回的数据,是一个html文档。

五、常见的状态码

状态码的职责是当客户端向服务端发送请求时,描述服务端返回的请求结果,借助状态码,我们就可以得知服务端是正常处理了请求,还是出现了错误,下面是开发中经常遇见的状态码:

1、2XX成功

2XX的响应结果表示请求被正常处理了.

  • 200 OK:该状态码表示从客户端发来的请求在服务端被正常处理了,在返回的响应报文中,随状态码返回的信息会因为请求方法的不同而不同。例如GET方法,响应报文的主体会包含请求的资源,而对于HEAD方法,响应报文不包含主体部分,只包含响应首部;
  • 204 No Content:该状态码表示服务端接收的请求已成功处理,但是在返回的响应报文中不包含主体部分,这说明请求处理成功,但是没有任何资源返回。比如当浏览器发出的请求处理后,返回204响应,那么浏览器显示的页面将不会发生任何更新;
  • 206 Partial Content: 该状态码表示客户端进行了范围请求,而服务端成功返回了这一部分范围的资源,即响应报文的主体部分中会包含由Content-Range指定范围的内容。

2、3XX重定向

3XX的响应结果表示客户端需要执行某些特殊操作后,服务端才能继续处理请求.

  • 301 Moved Permanently:永久性重定向,该状态码表示请求的资源已被分配了新的URL,以后都应使用新的URL来访问该资源,这时响应报文首部的Location字段会提示新的URL。例如你使用这个最后忘记加斜杠 / 的地址 www.myhost.com 来访问服务端,就会返回301状态码,提示你使用正确的地址访问,不过这些重定向的操作浏览器在背后已经替我们处理了,所以用户是无法感知的;
  • 302 Found:临时重定向,该状态码表示请求的资源暂时被分配了新的URL,本次应使用新的URL来访问该资源,302和304的区别就是一个是临时性,一个是永久性,302代表资源不是被永久移动,而是临时移动,即本次会重定向到地址a,下一次可能会重定向到地址b或者不变,所以响应报文首部的Location字段提示的新URL并不是永久性的,而是临时性的;
  • 303 See Other:临时重定向,该状态码表示请求的资源暂时被分配了新的URL,本次应使用新的URL通过GET方法来访问该资源,303和302功能相似,但是303明确表示客户端重定向时应采用GET方法获取资源,而302就没有这个要求。(但是在现实中,大部分浏览器都没有遵循规范,不管是301、302还是303,都会把POST改成GET,然后重新获取资源);
  • 304 Not Modified:304虽然被划分在3XX中,但是它和重定向没有任何关系,该状态码表示客户端访问的资源存在,但是未符合请求的附带条件,不允返回,这时返回的304响应报文不包含主体部分,请求的附带条件是指请求报文中包含If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since中任一首部。例如客户端想要检查本地资源缓存是否过期,就在GET请求中附带If-Modified-Since条件,If-Modified-Since的值是本地资源的Last-Modified(上一次修改时间)的值,服务端接收请求后,就会检查资源在服务端的上一次修改的时间是否比If-Modified-Since的值更新,如果没有,说明资源没有被修改过,返回304响应报文,告诉客户端可以继续使用本地缓存。(关于HTTP的缓存机制可以查看HTTP 协议缓存机制详解 )。

3、4XX客户端错误

4XX的响应结果表示客户端发生错误的原因所在.

  • 400 Bad Request:该状态码表示客户端的请求报文中有语法错误,不能被服务端理解,当发生该错误后需要修改请求内容后再次发送;
  • 401 Unauthorized:该状态码表示发送的请求需要有通过HTTP认证的认证信息,401的响应报文会包含一个被请求资源的WWW-Authenticate首部用来质询用户信息,当浏览器初次接收到401响应,会弹出认证窗口,当浏览器第二次收到401响应,表示认证失败;
  • 403 Forbidden:该状态码表示服务端收到了请求,但是拒绝提供服务,服务端不会给出拒绝的详细理由,例如没有文件的访问权限等都会返回403响应;
  • 404 Not Found:该状态码表示请求资源在服务端上不存在,或者服务端拒绝请求但不想说明理由也会返回404;

4、5XX服务端错误

5XX的响应结果表示服务端发送错误的原因所在.

  • 500 Internal Server Error:该状态码表示服务端执行请求时发生了不可预估的错误,它表明服务端Web应用存在bug或其他故障;
  • 503 Server Unavailable:该状态码表示服务端当前不能处理客户端请求,一段时间后可能恢复正常,它表明服务端暂时处于超负载或停机维护状态,如果服务端得知故障恢复时间,它会在响应报文的Retry-After首部字段写入返回给客户端。

更多状态码信息请访问HTTP状态码

六、常见的首部字段

下面列举了HTTP/1.1中的47种常见首部字段,分为通用首部字段、请求首部字段、响应首部字段、实体首部字段:

1、通用首部字段

表示请求报文和响应报文双方都会使用的首部。

首部字段名说明
Cache-Control控制缓存的行为
Connection允许客户端和服务端指定与请求/响应连接相关的选项
Date创建报文的日期时间
Pragma报文指令
Trailer报文末端的首部一览
Transfer-Encoding告知接收端为了保证报文的可靠传输性,对报文采用了什么的编码方式
Upgrade升级为其他协议
Via代理服务器的相关信息
Warning错误通知

2、实体首部字段

表示请求报文和响应报文的主体的实体部分使用的首部,主要作用是补充资源内容的更新时间与实体相关的信息,可以看到大多都是以Content开头的。

首部字段名说明
Allow资源可支持的 HTTP 方法
Content-Encoding实体主体适用的编码方式
Content-Language实体主体的自然语言
Content-Length实体主体的大小
Content-Location替代对应资源的 URI
Content-MD5实体主体的报文摘要
Content-Range实体主体的位置范围
Content-Type实体主体的媒体类型
Expires实体主体过期的日期时间
Last-Modified资源的最后修改日期时间

3、请求首部字段

表示从客户端向服务端发送请求报文时使用的首部,主要作用是补充请求的附加内容、客户端信息、响应内容相关优先级、编码等信息。

首部字段名说明
Accept客户端可识别的内容类型列表
Accept-Charset客户端可识别的字符集
Accept-Encoding客户端可识别的数据编码
Accept-Language客户端可识别的语言(自然语言)
AuthorizationWeb 认证信息
Expect期待服务器的特定行为
From用户的电子邮箱地址
Host请求的主机名
If-Match比较实体标记(ETag)
If-Modified-Since比较资源的更新时间
If-None-Match比较实体标记(与 If-Match 相反)
If-Range资源未更新时发送实体 Byte 的范围请求
If-Unmodified-Since比较资源的更新时间(与 If-Modified-Since 相反)
Max-Forwards最大传输逐跳数
Proxy-Authorization代理服务器要求客户端的认证信息
Range实体的字节范围请求
Referer对请求中 URI 的原始获取方
TE传输编码的优先级
User-Agent发出请求的浏览器类型,可以自行设置

4、响应首部字段

表示服务端向客户端返回响应报文时使用的首部,主要作用是补充响应的附加内容、要求客户端附加额外的内容等信息

首部字段名说明
Accept-Ranges是否接受字节范围请求
Age推算资源创建经过时间
ETag资源的匹配信息
Location令客户端重定向至指定 URI
Proxy-Authenticate代理服务器对客户端的认证信息
Retry-After对再次发起请求的时机要求
ServerHTTP 服务器的安装信息
Vary代理服务器缓存的管理信息
WWW-Authenticate服务器对客户端的认证信息

下面列举几个对HTTP首部的常见使用。

5、具体应用

5.1、 长连接原理

从HTTP/1.1起,默认都开启了长连接保持连接特性,通过在首部指定Connection:Keep-Alive,Keep-Alive也是一个首部,简单地说,当一个网页打开完成后,客户端和服务端之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务端上的网页,会继续使用这一条已经建立的TCP连接,Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同服务端软件中设置这个时间。

那么,长连接是如何工作的呢?长短连接是运输层(TCP)的概念,HTTP是应用层协议,它只能说告诉运输层我打算一段时间内复用TCP通道,而没有自己去建立、释放TCP通道的能力,那么HTTP是如何告诉运输层复用TCP通道的呢?分为以下几个步骤:

  • 1、客户端第一次发送请求报文时,顺带发送一个Connection: Keep-Alive的Header,表示需要保持连接,同时客户端可以顺带发送Keep-Alive: timeout=5, max=100这个Header给服务端;
  • 2、然后服务端识别Connection: Keep-Alive这个Header,并且通过响应报文Header带同样的Connection: Keep-Alive,告诉客户端我可以保持连接;
  • 3、客户端和服务端之间通过保持的TCP连接收发数据;
  • 4、当客户端最后一次发送请求报文,顺带发送Connection:close这个Header,表示长连接关闭。

Keep-Alive: timeout=5,max=100表示TCP连接空闲时最多保持5秒,长连接接受100次请求就断开,长连接虽好,但是长时间的TCP连接容易导致系统资源无效占用,浪费系统资源,所以需要有一些限制。

Connection首部除了用于管理连接外,还能控制不再转发的首部,格式为:Connection: 不再转发的首部,当经过代理服务器时,代理服务器会把Connection首部字段中指定的首部删除后,再把报文转发给服务端。

5.2、内容协商

一个网站在服务器中可能有多种语言版本、有多份相同内容的页面,例如英文版的网页和中文版的网页,在HTTP通信时客户端与服务端进行内容协商,让服务端返回最合适的内容给客户端,内容协商会以类型、字符集、编码、语言等方式为标准返回合适的响应资源。

客户端可以在请求报文中设定特定的Accept-XX首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language等,服务端根据这些字段返回特定的资源,这些字段的解释如下:

  • Accept

    示例:Accept: text/html,image/jepg;q=0.8,video/mpeg;q=0.5,*/*;q=0.1

    客户端在Accept首部中列举了它支持的内容类型,服务端从这些内容类型中挑选出一个优先级最高的类型,返回这个类型的内容给客户端,内容类型一般是type/subtybe形式,例如文本类型text/html、text/plain、...,图片类型image/jepg、image/gif、...,视频类型video/mpeg、...,等,多个类型之间用逗号 , 分隔,可以用 * 通配符表示接受所有类型,通过q表示优先级,与类型用分号 ; 分隔, q值越大,优先级越高,q值得范围是(0~1),如果不指定优先级,默认优先级都是1.0,内容类型放置顺序按优先级排序;

  • Accept-Charset

    示例:Accept-Charset: iso-8859,unicode-1-1;q=0.5

    客户端在Accept-Charset首部中列举了它支持的字符集,服务端从这些字符集中挑选出一个优先级最高的字符集,返回这个字符集的内容给客户端,与Accept相同,可以通过q指定字符集的优先级,并且和Accept一样,字符集的放置顺序按优先级排序;

  • Accept-Encoding

    示例:Accept-Encoding: gzip,compress,deflate;q=0.5

    客户端在Accept-Encoding首部中列举了它支持的内容压缩格式,服务端从这些内容压缩格式中挑选出一个优先级最高的,把响应内容用这种格式压缩后再返回给客户端,客户端收到压缩的内容后,用相应的解压算法解压内容,再显示出来,与Accept相同,可以通过q指定优先级,通过 * 通配符表示支持任意压缩格式;

  • Accept-Language

    示例:Accept-Language: zh-cn,en-us;q=0.5

    客户端在Accept-Language首部中列举了它支持的语言,服务端从这些语言中挑选出一个优先级最高的,返回相应语言版本的内容给客户端,如果没有优先级最高的语言版本,就返回次优先级的语言版本,例如示例中,没有中文版时,就返回英文版。

当服务器从各种选择列表中挑选出客户端支持的类型、字符集、编码、语言后,就会在响应报文的首部指定Content-XX首部字段,告诉客户端响应内容的类型、字符集、编码、语言,例如Content-Type、Content-Encoding、Content-Language等,Content-Type表示内容类型,还会指明内容的字符集,Content-Encoding表示内容的编码类型,Content-Language表示内容的语言。

上述的内容协商叫做服务器驱动协商,即由客户端通过首部告诉服务端它支持的东西,然后服务端做出选择,但是对于用户来说,浏览器替我们做的决定不一定是最优的,例如我看不懂英文,浏览器根据代理地理位置做了判断,返回了英文版的内容,这然对用户来说不是最优的,所以还有一种叫做客户端驱动协商,由用户告诉服务端他想要什么类型的内容,这时浏览器会弹出选择列表让用户选择,用户选择后,浏览器把用户的选择填入Accept-XX首部,再发送给服务端。

5.3、Cookie机制

Cookie是用来管理客户端和服务端之间的状态,它是服务器发送到客户端并保存在本地的小型文本文件,其内容为一系列的键值对,Cookie并不属于HTTP/1.x的规范,但是由于HTTP的无状态特性,Cookie被广泛应用于各大Web网站的状态管理及用户识别。

Cookie的工作过程主要使用到了Set-CookieCookie这两个首部,Set-Cookie首部存在于响应报文中,Set-Cookie首部包含服务端返回给客户端状态管理使用的Cookie信息,客户端收到响应后会从Set-Cookie首部中取出Cookie信息保存到本地;Cookie首部存在于请求报文中,Cookie首部包含客户端从服务端接收到的Cookie信息,每次客户端发起请求时,都会在请求报文的Cookie首部中携带Cookie信息发送给服务端。

Cookie需要和服务端的Session配合使用,Cookie是存储在客户端中,而Session是存储在服务器端中,Session是服务端保存用户状态的方式,它们的工作过程大概如下:

  • 1、当用户登陆网站时,填入账号、密码等信息,然后提交表单,这些信息会被放入HTTP的请求报文,然后发送给服务端;
  • 2、服务端收到请求后,验证该用户名和密码,如果正确,则为该用户创建一个Session对象,Session对象中保存了用户的状态信息,并生成Session对象的唯一ID,称为Session ID,然后把Session对象存储到内存或数据库中,根据Session ID可以从内存或数据库获取到Session对象;
  • 3、接着服务端把Session ID的值以name=value的形式放入响应报文的Set-Cookie首部中,其中name为Session ID的名字,value就是Session ID的值,name=value形式的Session ID就称为Cookie,Set-Cookie首部除了Session ID之外,还有一些其他信息,如Cookie的有效期、Cookie的域名范围等,然后把这个响应报文发送给客户端;
  • 4、客户端收到响应报文之后从Set-Cookie首部中取出所有Cookie信息,然后把它保存到本地,客户端有时候会收到不止一个Set-Cookie首部,如果有多个,每个Set-Cookie首部中的Cookie信息都要保存;
  • 5、客户端之后对同一个服务端进行请求时都会从本地取出Cookie信息,这时可以校验Cookie的有效期、路径、域名等信息,然后取出其中的Cookie值放入请求报文的Cookie首部字段,如果有多个Cookie值,Cookie首部中就用 ; 分隔,然后把这个请求报文发送给服务端;
  • 6、服务器收到请求后,从Cookie首部中取出Cookie,从Cookie中取出Session ID,然后用Session ID从内存或数据库取出用户信息,恢复用户之前的操作状态。

下面是我登陆掘金时在响应报文找到的Set-Cookie首部,如下:

HTTP/1.1 200 OK
//...省略很多首部
Set-Cookie: ab={}; path=/; expires=Fri, 15 Jan 2021 11:13:21 GMT; secure; httponly
Set-Cookie: auth.sig=nl1rsPof1lOURBJ1F81MyhGsoxs; path=/; expires=Thu, 23 Jan 2020 11:13:21 GMT; secure; httponly
Set-Cookie: QINGCLOUDELB=8015b18e7b6ee1bafcfd11812d999975a4db71eff5b47ab5974a1647066247c5|XiBFV|XiBFO; path=/; HttpOnly
Set-Cookie: auth=eyJ0b2tlbiI6ImV5SmhZMk5sYzNOZmRHOXJaVzRpT2lKdVNYTkJVVzlOWldwMWNuSkJjamQ2SWl3aWNtVm1jbVZ6YUY5MGIydGxiaUk2SWpNMWNYZzVhME52V21wMFNuUXdVbTRpTENKMGIydGxibDkwZVhCbElqb2liV0ZqSWl3aVpYaHdhWEpsWDJsdUlqb3lOVGt5TURBd2ZRPT0iLCJjbGllbnRJZCI6MTU3OTE3MTcxNTI0OCwidXNlcklkIjoiNWI0MzcxNzNlNTFkNDUxOTFkNzljMjdhIn0=; path=/; expires=Thu, 23 Jan 2020 11:13:21 GMT; secure; httponly

可以看到它发送了4个Cookie给我,这个4个Cookie分别为ab=XX、_auth.sig=XX、_QINGCLOUDELB=XX、auth=XX,后面的一些path=XX、expires=XX、secure、httponly都是Cookie的附加信息,待会解释。

我随便点开几篇文章,它的请求报文里的Cookie首部都会携带上面的Cookie,如下:

GET /user/5b437173e51d45191d79c27a/posts HTTP/1.1
//...省略很多首部
Cookie: ab={};auth=eyJ0b2tlbiI6ImV5SmhZMk5sYzNOZmRHOXJaVzRpT2lKdVNYTkJVVzlOWldwMWNuSkJjamQ2SWl3aWNtVm1jbVZ6YUY5MGIydGxiaUk2SWpNMWNYZzVhME52V21wMFNuUXdVbTRpTENKMGIydGxibDkwZVhCbElqb2liV0ZqSWl3aVpYaHdhWEpsWDJsdUlqb3lOVGt5TURBd2ZRPT0iLCJjbGllbnRJZCI6MTU3OTE3MTcxNTI0OCwidXNlcklkIjoiNWI0MzcxNzNlNTFkNDUxOTFkNzljMjdhIn0=; auth.sig=nl1rsPof1lOURBJ1F81MyhGsoxs;QINGCLOUDELB=7526744c262201bf8ae89c7035a8ce8c9eb2c663a78c233d245e9356cc89386b|XiBG2|XiBG2;//省略了一些其他Cookie值

可以看到Cookie首部都携带上了ab=XX、_auth.sig=XX、_QINGCLOUDELB=XX、auth=XX这4个Cookie。

挑一个Set-Cookie值解释一下它里面每个属性的含义,如下:

Set-Cookie: ab={}; path=/; expires=Fri, 15 Jan 2021 11:13:21 GMT; secure; httponly

可以看到服务器端返回的Set-Cookie首部值中,每个属性用分号 ; 分隔,这些属性的解释如下:

属性解释
NAME=VALUE表示Cookie的名称和值,上面已经说过了,它是name=value形式的,在这里Cookie为ab={},ab就是名称,{}就是值,每一个Set-Cookie首部都必需含有这个,在客户端发送请求时,Cookie会放在Cookie首部
path=路径Path属性指定了服务端下的哪些路径可以接受Cookie值,以斜杠**/** 作为路径分隔符,子路径也会被匹配,在这里path=/,表示根目录包括根目录下的所有子路径都可以接受这个Cookie值
domain=域名虽然上面举的例子里没有domain属性,但是domain属性常常和path属性一起指定Cookie的作用域, domain属性指定了哪些域名的服务端可以接受Cookie,如果指定了domain属性,子域名也会包含,例如设置 domain=.example.com,则 子域名www2.example.com也可以使用这个Cookie,如果不指定,默认为当前服务端,但不包含子域名
expires=过期时间expires属性表示Cookie的有效期,在服务端发送Cookie给客户端时,可以设定Cookie的过期时间,当省略expires属性时,Cookie的有效期仅维持到浏览器关闭之前,携带过期的Cookie给服务端是无效的,服务端发送过来的新的Cookie可以覆盖过期的Cookie
secure含有secure属性表示仅在进行HTTPS连接时,才允许发送这个Cookie
httponly含有httponly属性表示这个Cookie不能被JavaScript脚本调用,因为跨站脚本攻击 (XSS) 常常使用 JavaScript 的 document.cookie api窃取用户的 Cookie 信息,而Cookie附加了httponly属性后,document.cookie这个api就无法访问Cookie信息,从而避免了XSS,但是在Web页面内还是可以对Cookie进行读取操作

跨站脚本攻击 (XSS): 是指攻击者诱用户进入圈套,由用户在不知情的情况下执行攻击代码,攻击者事先编写脚本植入到用户的浏览器页面,用户在运行这些页面时就会触发脚本,发起攻击,常见的攻击有:利用脚本窃取用户的Cookie值、利用虚假输入表单骗取用户个人信息、显示伪造的图片或文章等。

七、HTTPS是什么

1、HTTP的问题

讲到HTTP不得不讲HTTPS,HTTPS并非一种新的协议,HTTPS就是安全版的HTTP,由于HTTP设计简单,使用简单,导致了它存在了大量安全问题,主要体现在以下三个方面:

  • 1、HTTP通信双方使用不加密的明文,内容可能会被窃听:HTTP是使用明文在网络上进行报文的传输,而在网络上进行传输的任何内容,都有可能被截获,例如通过一些Wireshark、Fiddler等抓包工具就可以抓取HTTP的报文,从而查看报文中的内容,而报文中的内容是未经过加密的明文,那么一些重要的信息就被别人窃取到了;
  • 2、HTTP无法验证报文的完整性,所以报文有可能遭到篡改:无法验证报文的完整性是指通信方收到这个报文时,无法证明这个报文没有经过中间人的篡改,无法判断报文中的信息是否正确,因为HTTP在发出请求到接收响应的这段时间内,有可能被其他中间人拦截报文,中间人拦截后可能会把报文中的信息做了一些恶意的修改后才发送给通信双方,这样通信方收到报文后,就无法得知报文内容的正确性,虽然HTTP有消息摘要校验方法,但是不可靠;
  • 3、HTTP通信不验证通信方的身份,因此有可能遇到伪装的通信方:HTTP是基于请求/响应的方式工作,只要客户端发出请求,服务端就要返回响应,在这个过程HTTP不会验证通信双方的身份,所以很有可能客户端会遇到伪装的服务端,服务端会遇到伪装的客户端,这样就会把一些重要的信息发送给伪装者。

所以,为了解决以上3个安全问题,就出现了HTTPS。

2、HTTPS的解决办法

HTTPS使用加密 + 完整性保护 + 认证的方法解决上述3个问题,通过在HTTP的应用层与运输层之间加了一层SSL/TLS,如下:

HTTP协议运行在TCP之上,HTTPS协议运行在SSL/TLS之上,SSL/TLS协议运行在TCP之上,使用HTTPS,所有传输的内容都要通过SSL/TLS层,加密 + 完整性保护 + 认证的工作就在SSL/TLS层中进行,换句话说HTTPS = HTTP + SSL/TLS

SSL(Secure Socket Layer)与TLS(Transport Layer Security)都是安全性协议,TLS是以SSL为原型开发的协议,所以TLS是基于SSL,有时会把SSL和TLS统称为TLS,目前的主流使用是TLS1.2、TLS1.3。

下面简单讲解一下加密、完整性保护、认证的实现方式:

2.1、采用混合加密机制进行加密

为了防止报文内容被窃听,HTTPS采用了对称加密 + 非对称加密的混合加密机制。

  • 对称加密:算法是公开的,在对称加密算法中,加密和解密都是使用的同一个密钥,因此对称加密算法要保证安全性的话,密钥要做好保密,只能让使用的人知道,不能对外公开;

  • 非对称加密:算法是公开的,在非对称加密算法中,加密使用的密钥和解密使用的密钥是不相同的,用来加密的密钥叫做公钥,用来解密的密钥叫做私钥,公钥是公开的,所有人都可以获得,私钥就需要做好保密,只能让使用的人知道,不能对外公开,非对称密钥除了用来加密,还可以用来进行签名,因为私钥无法被其他人获取,因此通信发送方使用其私钥进行签名(加密),通信接收方使用发送方的公钥对签名进行解密,就能判断这个签名是否正确.

HTTPS在通信的时候使用对称加密,但是使用对称加密就有一个问题,如何把密钥安全的发送给对方?如果简单的通过HTTP把密钥发送给对方,就有可能被中间人截获,这样对称加密就没有意义了,所以HTTPS采用了非对称加密来发送对称加密的密钥,这个过程如下:

1、首先服务端通过非对称加密算法生成一对密钥:公钥和私钥;

2、服务端把公钥发送给客户端,私钥自己保存;

3、客户端收到公钥后,利用公钥对对称加密使用的密钥S进行加密得到T,然后再发送给服务端;

4、服务端收到T后,利用私钥解密T,得到密钥S;

5、这样客户端和服务端都拥有了对称加密使用的密钥S,在之后的通信过程中就使用对称加密进行。

在这个过程中,就算中间人得到了T和公钥,想要通过公钥把T破解得到密钥S是非常困难的,以目前的技术来说,是几乎不可能实现,所以这样比直接发送密钥安全了很多。

综上所述,混合加密使得通信过程的安全得到保证,上述是为了讲解方便,在实际中,对称密钥不会只在客户端生成的,它同时会在服务端生成,这在后面的HTTPS的通信过程中讲到。

既然非对称加密破解困难,安全,为什么通信时不一直使用? 首先非对称加密的运算比对称加密的运算复杂很多,运算速度慢,所以不可能一直使用非对称加密来通信,而对称加密的运算速度比非对称加密快,效率高,所以,HTTPS就充分利用两者的优缺点,结合使用,在交换密钥时使用非对称加密保证安全,在通信时使用对称加密保证安全和效率。

2.2、采用数字签名进行进行完整性保护

在讲解数字签名校验之前,先来讲一下HTTP的消息摘要校验,又称散列值校验。

  • 消息摘要(Message Digest):原文通过MD5、SHA-1等散列值生成算法计算出的散列值就称为消息摘要,无论输入的消息有多长,它的输出长度总是固定的,并且输出值是随机的,消息摘要生成函数是单向函数,即无法通过消息摘要恢复到原文,消息摘要广泛应用于数字签名中。

HTTP的消息摘要校验过程是这样的:

1、服务端在发送报文之前,先用散列值生成算法生成报文的消息摘要,然后把报文和消息摘要一并发送给客户端;

2、客户端接收到报文和消息摘要后,就用相同散列值生成算法(已经协商过了)重新计算报文的消息摘要,如果重新计算的消息摘与发送来的消息摘要相同,就说明报文在中途没有被篡改过,否则被篡改过;

为什么HTTP的消息摘要校验是不可靠的?假设中间人拦截了上述过程的报文,首先通过穷举法找出你所用的散列值生成算法,修改报文后,用散列值生成算法重新计算报文的消息摘要,然后替换掉原本的消息摘要,然后把修改后的报文和重新计算的消息摘要一并发给客户端,客户端按照上述相同的验证流程,会得出报文没有被篡改过的结论,但其实报文已经被被篡改过了,所以HTTP的消息摘要校验是不可靠的,HTTP的消息摘要校验不可靠主要在于中间人可以重新生成篡改后报文的消息摘要。

所以为了解决HTTP消息摘要校验的缺点,HTTPS采用了数字签名进行完整性保护,其中签名使用到了非对称加密的公钥和私钥。

  • 数字签名(Digital Signature):用私钥对原文进行加密后生成的密文称为数字签名,数字签名可以通过公钥进行验证,把数字签名通过公钥解密后,如果和原文相同,就说明原文是完整的,没有被篡改过。

所以采用了数字签名后,校验过程是这样的:

1、服务端在发送报文之前,先用散列值生成算法生成报文的消息摘要,然后再用私钥加密消息摘要生成数字签名,把数字签名与报文一起发送给客户端;

2、客户端接收到报文和数字签名后,先用相同的散列值生成算法重新计算报文的消息摘要,然后再用公钥解密数字签名得到报文的消息摘要,然后比较两份消息摘要是否相同,如果相同,说明报文在中途没有被篡改过,否则被篡改过;

HTTPS的数字签名是如何保证可靠性的?假设中间人截获了报文,把报文修改后重新生成消息摘要,但是中间人没有私钥对生成的消息摘要进行签名,因为私钥是保密的,这样他就无法重新生成新的数字签名,由于中间人没有办法生成修改后报文的数字签名,所以这就保证数字签名的可靠性,接收方收到数字签名和报文后,通过数字签名的校验流程,就能判断出报文的正确性。

综上所述,数字签名使得消息的完整性得到保证,在HTTPS中,数字签名一般会用到数字证书的传递上,下面会讲。

为什么HTTPS要生成报文的消息摘后,再对消息摘要进行签名,而不对报文直接签名? 这是因为报文内容一般都很长,而报文的消息摘要输出的长度是固定,比报文长度短,这样通过私钥进行加密的运算量就大大减少,提高效率,所以当非对称加密与消息摘要结合使用后,便形成了一种高效又安全的数字签名方案。

2.3、采用数字证书进行身份认证

大家有没有发现,前面所讲的数字签名和混合加密技术,客户端都必须事先知道服务端的公钥,如果一开始公钥就被中间人篡改了,那么坏人就会被你当成好人,你就会拿着这把假的公钥和假的服务端通信,所以如何保证公钥是真正的服务端颁发,又是另外一个问题,为了保证公钥的安全可信,HTTPS通过数字证书来解决服务端的身份认证问题。

  • 数字证书(Digital Certificate):数字证书是由数字认证机构(Certificate Authority, 简称CA)颁发的公开密钥证书,它里面大概包含如下信息:

    1、发布机构(Issuer): 表示该证书是由哪个机构(CA)颁发的;

    2、有效期(Validity): 表示证书的使用期限,过了有效期,证书就失效了

    3、名称(Subject):表示证书所有人的名字,这个证书是发给谁的,一般是某个人或者某个公司名称、机构的名称、公司网站的网址等;

    4、公钥(Public-Key): 表示证书所有人想要公布出去的公钥;

    5、签名算法(Signature algorithm): 表示证书的数字签名所使用的加密算法,这样就可以使用证书发布机构的公钥,根据这个算法对数字签名进行解密;

    6、数字签名(Digital Signature):表示证书发行者CA对该证书的数字签名,用于保证数字证书的完整性,确保证书没有被修改过.

数字认证机构是处于客户端和服务端双方都信赖的第三方机构,由CA颁发的数字证书一定是可靠、可信的,下面来简单介绍一下服务端向CA申请数字证书的流程:

1、服务端的运营人员向CA提交自己的公钥、组织信息、域名等信息,然后CA会通过各种渠道、各种手段来判断服务端的身份是否真实,是否合法等(在这里就可以杜绝中间人非法申请证书);

2、服务端的身份审核通过后,CA就会把服务端的公钥和证书的其他信息通过散列值算法生成一个消息摘要,然后用CA的私钥对消息摘要进行签名生成数字签名,然后把数字签名放入证书中,然后把这个证书颁发给服务端,所以数字证书最终包含服务端公钥 + 证书的数字签名 + 证书的其他信息,如下:

现在服务端拿到了数字证书,客户端第一次请求服务端时,服务端就会把这个数字证书发送给客户端,客户端收到数字证书后,就会用CA的公钥对数字证书进行数字签名的验证,如果验证通过,说明数字证书中途没有被篡改过,是可靠的,从而知道数字证书中的公钥也是可靠的,所以客户端就放心的取出数字证书中的公钥进行以后的HTTPS通信。

在对数字证书的数字签名进行验证之前,必须先知道CA的公钥,因为数字证书的数字签名是由CA的私钥进行签名的,所以CA的公钥必须安全的转交给客户端,如何安全的转交又是一个困难的问题,所以,大多数浏览器开发商发布版本时,会事先在内部植入常用的CA机构的根证书, 这些根证书中会包含CA的公钥,也就是说CA的公钥已经内置在浏览器中了。

综上所述,数字证书可以确认服务端的身份,可以解决公钥的安全发放问题,同时数字证书也是通过数字签名来验证的。

浏览器内置的CA都是常用的、信任的CA机构,所以如果服务端发来的数字证书的相关CA机构刚好不在浏览器的内置CA列表中,浏览器就会找不到该数字证书的CA公钥,就会判定该数字证书是非法,这时浏览器会提示你手动安装该数字证书的CA机构的根证书,这个时候你就要自己承担风险了,很有可能这个网站是不可信任的,安装了根证书后就可以拿到CA的公钥进行数字证书的验证,验证通过后就能与服务端通信。

打开一个由HTTPS连接 (地址栏上有一个锁的标志) 的网站,通过以下方式查看它的数字证书,如下:

3、HTTPS的通信过程

在进行HTTPS通信前,必须先进行TCP三次握手建立TCP连接,然后进行TLS握手,进行完TLS握手后才会开始加密的HTTPS通信,在TLS握手的过程中主要进行密钥交换(对称加密使用的密钥)、身份认证等步骤,根据密钥交换时使用的算法不同,TLS握手可以分为RSA握手和DH握手,目前主流的是DH握手,而且RSA握手由于它没有向前保密,已经在TLS1.3中被淘汰了,关于这两个握手算法的主要细节与区别可以查看下面文章,限于篇幅,本文不展开讨论:

Keyless SSL: The Nitty Gritty Technical Details

HTTPS篇之SSL握手过程详解

我通过抓取各大主流网站的TLS包发现,目前HTTPS使用的TLS版本几乎都是TLS1.2TLS1.3版本,下面的分析都是基于TLS1.2的DH握手过程,下面是我打开第一次打开csdn博客时抓取的TLS包(经过过滤后),如下:

其中info栏的信息表示TLS握手过程中客户端和服务端之间交互时发送的TLS包的名称,有Server Key Exchange包表示它本次的握手类型是DH握手,上面的TLS握手过程可以用下图表示,如下:

上图省略了New Session Ticket包,New Session Ticket包是用于会话复用,并不是必要的,省略它并不会影响对TLS握手过程的理解,在TLS握手完成之前,报文的传输都是明文,TLS握手过程的每个步骤的解释如下:

  • 步骤1:客户端发送Client Hello报文给服务端,表示开始TLS握手,Client Hello报文中包含客户端支持的TLS版本(Version)、支持的加密组件列表(Cipher Suites)、客户端生成的随机数(Random)等信息;
  • 步骤2:服务端收到Client Hello报文后,如果可以开始进行通信,就会发送Server Hello报文给客户端作为回应,Server Hello报文中包含支持的TLS版本(Version)、从加密组件列表中选择的加密组件(Cipher Suite)、服务端生成的随机数(Random)等信息,其中加密组件用于告诉客户端接下来身份认证、密钥交换、对称加密等过程使用的算法;
  • 步骤3:紧接着服务端就会发送Certificate报文,报文中包含服务端的数字证书,即公钥证书,客户端收到证书后,就会对服务端进行身份认证,身份认证使用的算法就是在步骤2协商好的认证算法,一般会使用RSA;
  • 步骤4:接着服务端就会发送Server Key Exchange报文,Server Key Exchange报文中包含服务端根据相应的DH算法生成DH参数(PubKey),这个DH算法就是在步骤2协商好的密钥交换算法;
  • 步骤5: 最后服务端发送Server Hello Done报文通知客户端它完成了TLS握手的一半,服务端到目前为止向客户端发送了服务端的随机数、证书、服务端的DH参数;
  • 步骤6:接下来客户端向服务端发送Client Key Exchange报文,Client Key Exchange报文中包含客户端根据相应的DH算法生成的DH参数(PubKey),这个DH算法同样是在步骤2协商好的密钥交换算法;
  • 步骤7:紧接着客户端就会使用在步骤1自己生成的随机数、在步骤2收到的服务端随机数、在步骤4收到的服务端DH参数、在步骤6自己生成的DH参数这四个参数,通过在步骤2协商好的对称加密算法来生成以后通信过程中使用的密钥,这里记为S,然后客户端就会发送Change Cipher Spec报文通知服务端对称加密使用的密钥生成完毕,接下来客户端向服务端发送的数据都会经过密钥加密;
  • 步骤8:最后客户端发送Encrypted Handshake Message报文通知服务端它完成了TLS握手的所有内容,Encrypted Handshake Message报文包含至今所有报文的整体校验值,并用*S加密,本次握手能否成功,取决于服务端能否解密这个报文,Encrypted Handshake Message报文下面的两个Application Data报文就是客户端经过加密后发给服务端的数据,客户端到目前为止向服务端发送了客户端的随机数、客户端的DH参数;
  • 步骤9:紧接着服务端就会使用在步骤2自己生成的随机数、在步骤1收到的客户端随机数、在步骤6收到的客户端DH参数、在步骤4自己生成的DH参数这四个参数,通过在步骤2协商好的对称加密算法来生成以后通信过程中使用的密钥,这里记为T,由于DH算法的保证:T == S,这样客户端和服务端都拥有了通信加密使用的密钥,然后服务端就会发送Change Cipher Spec报文通知客户端对称加密使用的密钥生成完毕,接下来服务端向客户端发送的数据都会经过密钥加密;
  • 步骤10:最后服务端发送Encrypted Handshake Message报文通知客户端它完成了TLS握手的所有内容,Encrypted Handshake Message报文包含至今所有报文的整体校验值,并用*T加密,本次握手能否成功,取决于客户端能否解密这个报文。

如果客户端和服务端都成功解密了最后那个Encrypted Handshake Message报文,证明客户端和服务端的对称密钥生成完毕,TLS握手都全部完成,接下来双方都可以通过对称密钥进行加密通信,Application Data报文中的数据就是加密后的HTTP报文,当HTTPS通信结束后,由客户端主动发出Client Close Notify报文断开连接。

由上面DH握手过程可以看出,对称密钥是根据一些参数在各端生成,并不是在客户端生成后通过公钥加密传输给服务端,这样做是为了保证服务端的私钥不和对称密钥有关联,什么意思呢?如果在客户端生成密钥通过公钥加密传输给服务端,服务端可以由私钥解密出密钥,这样私钥就参与了对称密钥的解密,到也就是说,只要我拥有服务端的私钥,我就可以解密出密钥,所以如果中间人把你TLS握手过程中的所有报文拦截、保存,直到某一天服务端的私钥泄漏了,中间人就可以使用私钥在保存的报文中解密出密钥,这样,中间人就轻松的解密出客户端和服务端以后的通信内容,而通过DH参数的交换就可以避免这个漏洞,为什么DH交换就可以? 这归根到底是一个数学问题,大家可以自行查找资料。

大家只需要知道在TLS握手中,对称密钥是服务端和客户端各自生成的,服务端公钥和私钥的功能被削弱到用来进行身份认证或者签名验证,这么做的目的都是为了保证通信的向前保密、安全。

HTTPS这么安全,为什么不所有网站都使用? 1、效率问题:与HTTP的明文通信相比,加密通信需要消耗更多的CPU资源与内存; 2、部署问题:使用HTTPS需要有权威CA的证书颁发,从证书的选择、购买到部署都是一个耗时耗力的过程; 3、成本问题:购买证书也是一笔开销.

结语

网络请求已经成为了一个应用最基本的部分,所以熟悉HTTP对于我们开发很重要,我们不仅会用开发环境提供给我们的API,还要属性它的原理,本文从发展历史、工作特点、报文格式、状态码、常见首部、HTTPS这几个方面总结了一番HTTP,当然,HTTP肯定不止这一些,一篇文章是无法讲完的,限于篇幅,还有HTTP的缓存机制没有讲,这也是很重要的内容,掌握以上这些足够平常使用了。

以上就是本文全部内容,希望大家有所收获。

参考资料:

图解HTTP

RFC2626: Hypertext Transfer Protocol

HTTP/1.x 的连接管理

HTTP/1.x: HTTP Pipelining

HTTP/2.0: Request and Response Multiplexing

深度解密 HTTP 通信细节

深入理解https工作原理

SSL中的RSA、DHE、ECDHE、ECDH流程与区别

Cookie/Session的机制与安全