前言
这两个协议,在分布式通信过程中最为常用,面试中出现频率也是最多的,但是一直没有实际总结过,今天对其进行一个总结。
这里打算基于Fiddler工具,进行HTTP抓包分析,fiddler使用教程如下:fiddler使用教程
简介
HTTP协议相对来说容易一点,也是比较好理解的一个协议,本身是应用层协议,但是因为HTTP协议本身的一些问题,延伸出来的一些解决方案值得深入的学习,例如HTTP本身是无状态的协议,针对HTTP本身的无状态问题,有着相应的解决方式(增加session),这里也会做出相应的总结。
URL与URI
其实单独提出这两个的概念显得有点多余,但是这里还是提一下,总的一句就可以区别,URL是URI的子集,如果说URL表示一个人的名片的话,那么URI表示了这个人的名片信息+地址信息。下面一张图简单看一下就可以。
HTTP请求报文
fiddler中抓包的HTTP请求报文如下所示:
HTTP请求报文分为三个部分:1.请求行,2.报文头,3.报文体。
请求行
实例:GET http://www.baidu.com HTTP/1.1
请求行以方法名开头,后面跟着URI和HTTP协议版本,中间通过空格隔开,后面两个没什么好介绍的,这里主要说明一下方法,通过下面一张表格可以总结出来
方法 | 说明 | 支持的HTTP协议 |
---|---|---|
GET | 获取资源 | 1.0、1.1 |
POST | 传输实体主体(客户端要告知服务器某条消息) | 1.0、1.1 |
PUT | 传输文件 | 1.0、1.1 |
HEAD | 用于确认URI的有效性及资源更新的日期 | 1.0、1.1 |
DELETE | 删除文件,与PUT相反 | 1.0、1.1 |
OPTIONS | 查询请求URI指定的资源(服务器)所支持的方法 | 1.1 |
TRACE | 让服务器将之前的请求通信环回给客户端 | 1.1 |
CONNECT | 要求用隧道协议连接代理(暂时理解不深入) | 1.1 |
LINK | 建立和资源之间的联系 | 1.0 |
UNLINK | 断开连接关系 | 1.0 |
其中最为熟悉的可能就是GET和POST请求,这个两个请求是开发中最为常用的两种,GET与POST的区别,这里也不在赘述,可以参考相关的博客——GET与POST区别
请求报文头后面将会和响应报文头一块进行总结,这里暂时不描述,针对请求报文体,一般可以理解为发送给服务器的参数,根据方法的不同,参数不一样,请求报文体也就不一样。
HTTP响应报文
响应报文实例:
HTTP响应报文大体结构和请求报文结构区别不大,主要也是三个结构:1、状态行,2、各种首部字段,3、报文主体。
状态行比较简单,也是由三个部分组成:协议版本,状态码和状态码的文本描述(CRLF)。一些状态码能代表一些信息,而且在实际开发中,有时候会利用状态码进行进行相关信息的判断,这里针对响应状态码做一个表格总结
HTTP响应状态码
类别 | 简单描述 | |
---|---|---|
1XX | Information(信息状态码) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行俯角操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
HTTP状态响应码的大类的总结可以参照上面的表格,但是有时候会具体到某个状态码的描述,这里需要进一步总结,1XX开头的状态码几乎没怎么接触过,这里不进一步总结1XX的状态码。
200 OK 这个可以说是最常见的状态码了,表示客户端请求成功。
204 表示服务器接收的请求已经成功处理,但在返回的响应报文中不含实体的主体部分(不含有响应报文主体),一般返回204请求之后,浏览器显示的页面不会发生更新,该响应码常用于PUT、DELETE请求中。
206 表示客户端进行了范围请求(响应报文中包含Content-Range指定范围的实体内容)。
301 永久性重定向,表示客户端所请求的资源已经被分配了新的URI,这时客户端需要重新更新保存的URI
302 临时性重定向(与301的具体区别后面通过tracert进行探究)。
303 与302作用相同,但是303明确表示客户端需要采用GET方法获得资源。
304 表示服务端资源没有改变,可以使用客户端中为过期的缓存(和重定向关系不大)
307 与302作用相同,只是不会强制要求用GET请求。
400 请求报文中存在语法错误
401 认证失败(这里指的是网页认证失败,通常指的就是登录用户名和密码失败)
403 未授权访问该资源
404 这个就不用解释了——算是我朝国产最流行的动漫之一。
500 服务器端程序出现bug
503 Service Unavailable 服务器超负荷
HTTP首部字段(报头消息)
上面只是介绍了报文的请求行和状态行的相关信息,这里就详细介绍各个首部字段,分为四个部分进行介绍:1、通用首部字段,2、请求首部字段,3、响应首部字段,4、实体首部字段。
通用首部字段
顾名思义,指的就是请求报文和响应报文首部都会用到的字段
首部字段名 | 说明 |
---|---|
Cache-control | 控制缓存的行为 |
Connection | 逐跳首部、连接的管理 |
Date | 创建报文的日期时间 |
Pragma | 报文指令 |
Trailer | 报文末端的首部一览 |
Transfer-Encoding | 指定报文主体的传输编码方式 |
Upgrade | 升级为其他协议 |
Via | 代理服务器的相关信息 |
Warning | 错误通知 |
Cache-control
实例:Cache-control : private,max-age=0,no-cache
针对缓存的处理,其实是一个比较复杂的过程,Cache-control的参数是可选的,多个指令之间通过英文逗号分隔。
指令 | 参数 | 说明 |
---|---|---|
no-cache | 无 | 强制向服务器再次验证(重新缓存)。 客户端请求报文中如果有该指令,表示客户端不会接受缓存服务器的资源。 服务器端响应报文中如果有该指令,表示缓存服务器不得对该报文内容做缓存。 该指令只是表示不会缓存过期的报文资源,no-store才是真正的不缓存。 |
no-store | 无 | 不缓存请求或响应的任何内容 请求或响应中有机密信息,服务器和缓存服务器都不得缓存报文任何内容。 |
max-age=[秒] | 必需 | 响应的最大Age值。 如果客户端得知缓存资源存在的时间比指定的时间小,那么客户端就接收缓存数据,否则缓存服务器会去请求源服务器。 如果服务端的响应报文中有这个指令,表示在max-age的时间段内,服务器不会更新缓存数据 该字段优先级比Expires高。 |
max-stale=[秒] | 非必需 | 期待在指定时间内的响应扔有效。 缓存过期后指定的时间内,依旧有效。 |
min-fresh=[秒] | 必需 | 返回指定数值内会过期的缓存数据。 min-fresh = 60 表示返回60秒内会过期的缓存数据。 |
no-transform | 无 | 代理不可更改媒体类型 在请求报文和响应报文中,缓存都不能改变实体主体的媒体类型。防止缓存压缩图片或者视频等 |
only-if-cached | 无 | 从缓存获取资源。 客户端就要用缓存数据,并要求缓存服务器不重新加载响应,如果缓存的数据没有响应,则会返回504 Gateway Timeout |
cache-extension | - | 新指令标记。 可以扩展Cache-control字段内的内容, |
Connection
这个首部字段主要具有两个作用:1、控制不再转发给代理的首部字段。2、管理持久连接。
1、控制不再转发给代理的首部字段
这个结合实例好理解点
请求报文头:
GET / HTTP/1.1
Update:HTTP/1.1
Connection:Upgrade
注意这里请求报文头Connection字段,且Connection指定了Update字段。客户端报文在发送给代理服务器的时候,Update不会出现在代理服务器中。
2、管理持久连接
HTTP在1.1版本之后都默认持久连接(每次传输数据的时候,不用每次通过握手建立TCP/IP连接),当服务器明确想断开连接时,则指定Connection首部字段的值为close。
Date
这个就不用解释了
Pragma
一个兼容性设计,HTTP1.0中的遗留字段,这里也不做详细介绍。
Trailer
我个人理解为报文主题的摘要
Transfer-Encoding
规定传输报文主体的编码方式,所规定的编码方式只对分块传输编码有效。
Upgrade
用于指定能否可以用其他的通信协议,例如Upgrade:TLS/1.0,告知服务器,客户端可否使用TLS/1.0通信协议,但同时还需要配合使用Connection字段,且Connection字段需要指定为Upgrade.
Upgrade:TLS/1.0
Connection:Upgrade
上述两个字段一起才能起作用,有时候Upgrade指定了新的协议方式之后,服务器会返回101 Switching Protocols状态码
Via
为了追踪客户端与服务器之间的请求和响应报文的传输路径,报文经过代理或者网关时,会在报文via字段中增加本服务器的信息,这和traceroute类似
Warning
会告知一些缓存服务器的警告消息,这里不详细讨论。详情参见——HTTP-WRAN
请求首部字段
从客户端发往服务器的请求报文中特有的字段
名称 | 作用 | 示例 |
---|---|---|
Accept | 告知服务器客户端能处理的媒体类型 | Accept:text/html,application/xhtml+xml,application/xml;q=0.6 说明:可以指定多种媒体类型,基本是type(大类型)/subType(子类型)这种形式指定,后面的q是权值的意思,0<q<=1,默认q=1 |
Accept-Charset | 告知服务器接受的字符类型集合 | Accept-Charset:iso-8859-5,unicode-1-1;q=0.8 说明:可以一次性指定多个字符集,同时q也为权值,与Accept中的q含义一样。 |
Accept-Encoding | 告知服务器支持的文件编码类型和文件编码的优先顺序 | Accept-Encoding:gzip,deflate;q=0.2 说明:可以一次性指定多个编码格式,同样存在q权值 |
Accept-Language | 告知服务器支持的语言,以及优先级 | Accept-Language:zh-cn,zh;q=0.7,en-us;q=0.3 |
Authorization | 向服务器传递认证信息 | Authroization:Basic dWVub3NlbjpwYXNzd29yZA== |
Expect | 告知服务器期望出现的某种特定行为 | Except:100-continue |
From | 告知服务器,客户端的邮箱服务地址? | From:[email protected] |
Host | 告知服务器想要访问的互联网主机名称 | Host:www.baidu.com 说明:这个字段必须要背包含在请求头首部中 |
If-Match | 指定ETag值 | If-Match:"897695" 说明:服务器会对比客户端通过该字段带过来的ETag值,如果相同会返回与ETag相符的资源,如果没有,则会返回412 Precondition Failed的响应。 |
If-Modified-Since | 告知服务器,我只要指定日期更新后的资源 | If-Modified-Since:Thu, 15 Aug 2018 00:00:00 GMT 客户端只接受2018年8月15号以后更新的资源,如果资源从这之前就没有更新过了,服务端会给客户端返回 304 not modified |
If-None-Matched | 与If-Match的作用正好相反,返回与所指定ETag值不匹配的资源 | 略 |
If-Range | 告知服务器,若指定的if-Range字段(可以指定时间或ETag)和请求资源的ETag或时间一致,则服务器会处理请求,如果不一致,服务端会返回全部资源 | 略 |
If-Unmodified-Since | 与If-Modified-since相反 | If-Unmodified-Since: Thu, 03 Jul 2012 00:00:00 GMT 在字段指定的日期之后没有发生更新的情况下才会返回,如果发生了更新,会返回412 Precondition Failed |
Max-Forwards | 当 Max-Forwards 字段值为 0 时, 服务器就会立即返回 响应。 |
Max-Forwards: 10 每走一步,该数值就减一,到0的时候,直接返回响应 |
Proxy-Authorization | 发生在客户端与代理服务器之间的认证 | Proxy-Authorization: Basic dGlwOjkpNLAGfFY5 不同于Authorization字段,这个字段只是发生在客户端与代理服务器之间的认证。 |
Range | 只是获取服务器指定范围的资源 | Range: bytes=5001-10000 只获取服务器第5001直接到10000字节的内容。 |
Referer | 告知服务器请求的原始资源的 URI | Referer: http://www.baidu.comindex.htm 该请求从http://www.baidu.com/index.htm发起的,这个功能可以用于防盗链 |
TE | 告知服务器客户端能处理的传输编码(不是字节编码和文件编码) | TE: gzip, deflate;q=0.5 同时也有优先级字段。 |
User-Agent | 浏览器种类 | User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) |
上述表格基本都总结了各个请求字段,每一个字段都做出了示例说明,一些在实际中用的并不错,有些需要结合示例进行理解,还有一些不常见的返回码也会出现在示例中,后续可以详细探讨。
响应首部字段
服务端向客户端返回的报文中所使用的字段。依旧还是以表格的形式进行总结
名称 | 作用 | 示例 |
---|---|---|
Accept-Ranges | 告知客户端服务器能能否处理范文请求 | Accept-Ranges:bytes/none 说明:bytes表示服务器能处理范围请求,none表示不能。 |
Age | 一般由缓存服务器告知客户端,服务器在多久前创建了该响应 | Age:600 说明:缓存服务器告知客户端,源服务器在10分钟前创建了该响应。Age属性值的单位为秒。 |
ETag | 告知客户端实体标识,当服务端资源更新时,ETag值也会更新 | ETag: "82e22293907ce725faf67773957acd12" 说明:参见这篇博客——ETag详解 |
Location | 告知客户端请求的资源在指定的位置,几乎所有的浏览器客户端在接受到location字段之后都会强制跳转,重新发起请求。 | Location: http://www.baidu.com/sample.html 说明:基本上该字段会配合3XX:Redirection响应。 |
Proxy-Authenticate | 告知客户端代理服务器需要认证 | 见后面的详细阐述 |
Retry-After | 告知客户端在多久之后重新发送请求。 | Retry-After:120 说明:2分钟之后再次发送,常配合503和3XX字段使用 |
Server | 告知客户端当前的服务器信息 | Server: Apache/2.2.6 (Unix) PHP/5.2.5 |
Vary | 首部字段 Vary 可对缓存进行控制。 源服务器会向代理服务器传达关于本地缓存使用方法的命令。
|
Vary:Accept-Language 相关示例需要参考些许资料,或实际中进一步总结。 |
WWW-Authenticate | 用于HTTP访问认证 | 状态码 401 Unauthorized 响应中,肯定带有首部字段 WWW-Authenticate。 |
实体首部字段
实体首部字段是出现在报文实体中的字段,这里的报文实体包括了请求报文实体和响应报文实体。
名称 | 作用 | 实例 |
---|---|---|
Allow | 告知对方自己支持的所有HTTP方法 | Allow:GET,HEAD 说明:如果服务器接收到自己不支持的方法,会返回405 Method Not Allowed,这一点在开发中深有体会,有时会在Controller中限制使用post方法,但是利用postman发送请求的时候却是get方法,就会抛出这个错误。 |
Content-Encoding | 服务端告知客户端,响应报文的编码方式 | Content-Encoding:gzip |
Content-Language | 告知客户端,实体报文的自然语言 | Content-Language:zh-CN |
Content-Length | 告知对方实体报文的大小 | Content-Length:15000 |
Content-Location | 给出报文主体相对应的URI | Content-Location:http://www.baidu.com/index.html |
Content-MD5 | 用于检查报文主体在传输过程中是否保存完整 | 基本同上 |
Content-Range | 告知客户端返回的实体是那个范围的 | Content-Range:bytes 5001-10000/10000 |
Content-Type | 说明实体主体内对象的枚举类型 | Content-Type:text/html;charset=UTF-8 |
Expires | 告知客户端资源失效的日期 | 略 |
Last-Modified | 告知客户端最后的修改日期 | 略 |
与Cookie相关的字段
与cookie相关的字段只有两个,由于Http协议本身是无状态协议,为了期望保存客户端的回话状态,于是就引入了cookie技术。
在梳理相关字段之前,还是先理清Cookie与Session的区别,这两者之前看过N多次,都没有进行总结和梳理。
1、Cookie是存在客户端,Session是存储在服务端。之前为了让服务器记住通信状态,客户端在首次访问服务器之后的回话过程中会带上cookie中的内容,使得服务器能根据cookie信息判断客户端是否已经访问过。
2、Session是保存在服务器端的,用个SessionId来区分每个客户的session
3、现在比较通用的做法是在cookie中保存一个sessionId,客户端在访问服务器的时候,带上sessionId,服务器根据sessionId获取之前的回话信息。
4、如果客户端禁用了cookie,sessionId会通过url发送到服务端,这就是为什么有时候url后面会带有sessionId参数的原因。
与cookie相关的字段只有两个,Set-cookie和Cookie,其中Cookie只是标记本地Cookie是否能用,这个比较简单,没什么可介绍的。Set-cookie中有很多属性,这些属性中包括NAME,expires,path,domain,Secure,HttpOnly
NAME——cookie的名字
expires——cookie的有效期
path,domain——指定的路径或域名下,才发送cookie
secure——只有在HTTPS的情况下才发送cookie
HttpOnly——使得JS无法获取cookie中的内容(document.cookie无效)
==========================================完美的分割线=======================================
其实在分布式环境中,Session的同步需要花费相当大的代价,在分布式环境下,通常会用accessToken的方式去解决相关问题。
HTTPS
终于写到HTTPS了,这就是这篇博客的重点所在。
HTTP协议本身的问题除了无状态以外,还有一个最大的问题就是HTTP是明文通信,安全性是一个大问题。
HTTPS协议本身比较复杂,这里只是做一个简单了解,参考相关博客作出自己的总结。这里的总结目的只是检验自己读懂的大牛的博客而已,不做更多的要求,各位看官可以直接移步大牛的博客:HTTPS的通俗解释
HTTPS理解前需要熟悉的
还记得,大学计算机网络这门课的开篇就会有这样一个例子,所有的通信,其实就是为了解决可靠性。最简单的通信模式就是下图所示的模式,A直接发送消息给B,但是从某一种程度考虑,B能否确保消息来源就是A,A能否确保消息接收端就是B?这就是计算机网络需要解决的问题。大牛的博客就是从这样一个简单的模型开始,进行深入的介绍。
对称加密传输
在上述简单的通信模型上建立安全,我们首先想到的就是对Hello这个文字加密,然后A、B双方用对应的密钥去解密,这就能保证基本的通信安全。谈到对称加密并不陌生,常见的有DES、RSA等等。
这就是简单的对称加密模型,其中的密钥即扮演加密的角色,也扮演解密的角色,只要这个密钥不被第三个人知道,A和B的通信就是安全的。这也就是我们常说的对称加密,加密和解密的密钥是同一个。
但是,在互联网背景下,我们的通信环境可不会只有两个人。一个Web服务器请求会有多个用户访问,我们的通信模型不会简单到这个地步。
如果同时使用一个密钥,这跟没加密有和区别,心术不正的人依旧会得到通用的密钥,很容易篡改相关的数据报文。
但是由于非对称加密在通信过程中会耗费较多服务器资源,所以,服务器在与客户端正式交换数据时依旧采用的是对称加密的数据传输,只是每个请求会采用密钥协商,真正的密钥在正式交换报文前才确定下来。如下图所示:
HTTPS中的非对称加密
看似没毛病了,可是另一个问题出来了,如何安全的确定密钥?换句话说,服务端与客户端如何安全的进行密钥协商。这就要用到非对称加密了。具体非对称加密的过程这里不做探讨,这个过程依旧很复杂,可以参考《改变世界的九大算法》一书中的相关章节简单了解。
这里我们只需要知道非对称密钥的特点——私钥加密后的密文,只要是公钥,都可以解密,但是公钥加密后的密文,只有私钥可以解密。私钥只有一个人有,而公钥可以交给任何人。
目前,服务端将公钥发送给任何人,客户端用服务端给的公钥加密,然后将加密后的数据发送给服务端,服务端用私钥解密。(图是盗的,非常不错的一张图.......) 目前至少保证了从客户端到服务器端通信的安全性,但是服务器端到客户端的通信安全依旧没法保证。于是这就要用到非对称加密了,总的来说就是利用非对称加密算法加密对称加密算法的协商过程(有点绕)。
HTTPS中的公钥、数字证书、数字签名
如何让客户端的到公钥,这也是个问题。目前主要有两个方案。第一:服务器端直接将公钥发送给每一个客户端。第二:服务端将公钥放到一个远程服务器,客户端可以请求得到。
针对第一种方案,很容易出现公钥被中间者调包的情况。其实理解到这里,最重要的就是需要明白,客户端如何保证接收到的信息来自服务器
公钥这个概念就不用详细介绍了,为了将公钥安全的传递给客户端,我们并不能直接将公钥传递给客户端,而是第三方机构使用其私钥对服务器的公钥加密之后,再传递给客户端。客户端再使用第三方机构的公钥进行解密。
所谓的数字证书:就是服务器将公钥交给了第三方机构,然后第三方机构用自己的私钥将其加密后的结果,这个就是数字证书。如果客户端能够用第三方机构的公钥解密,获得数字证书,就说明这个公钥没有被调包过。因为如果中间人调包了这个数字证书,会用自己的私钥去加密这个证书,然后客户端用第三方机构的公钥并不能解密中间人发送的数据包。
但是还有一个问题,中间人其实也可以去中间机构获取证书,然后同样也能完成数据篡改。这个时候,数字签名就发挥作用了。数字签名的主要作用就是用于解决同一机构颁发的不同证书被篡改的问题,这个就和我们本身的学位证书上的证书编号作用一样,HR在入职的时候,会拿着我们的学位证书编号,去学信网(中间机构)查询我们证书的真伪。
其实,真正的数字签名的验证在客户端本地就完成了。因为证书本身已经告诉了客户端怎么去验证证书的真伪。也就是,客户端拿到数字证书后,根据证书上的方法,自己生成一个证书编号,如果生成的证书编号与证书上的证书编号一致,说明这个证书是真实的。当然这个数字签名也是被第三方机构加密的。
最终双方通信的密钥为:客户端生成的随机数+服务端生成的随机数+per-master(这个参考相关文档笔记)
一句话总结HTTPS
其实HTTPS本身就非常复杂了,不是一句话能总结的。但是大牛还是这么做了。
HTTPS要使客户端与服务器端的通信过程得到安全的保证。必须使用对称加密,但是协商对称密钥的过程,需要使用非对称加密算法来保证安全,然而直接使用非对称加密的过程本身就不安全,会有中间人篡改公钥的可能性,所以客户端与服务器不直接使用公钥,而是使用数字证书签发机构颁发的证书来保证非对称加密过程本身的安全。这样通过这种机制协商出一个对称加密算法,之后双方再使用该算法进行加解密,从而解决了客户端与服务端之间的通信安全问题。
参考资料: