文章目录
HTTP——“为Web而生的协议”
一、HTTP概述
HTTP协议用于描述客户端(通常是浏览器)如何向服务端(Web服务器)请求超文本资源(如文本、声音与图像等),以及Web服务器应该怎样通过发送响应报文来响应浏览器发来的请求报文。
HTTP具有如下一些特点:
1.C/S(Client-Server)结构
HTTP是一种客户端服务器(C/S)结构的协议,客户端向服务端(Web服务器)发送HTTP请求,服务端接收处理HTTP请求后向客户端发送HTTP响应,而在实际中,客户端与服务端之间可能存在代理(Proxy)。
- Web服务器(Web Server):一种能根据HTTP请求向客户端发送响应报文的应用程序,常见的web服务器有Apache、Nginx及IIS等。
- 代理服务器(Proxy Server,又称Web Cache):代理服务器可以把请求和响应存储在本地,在收到发来的请求时,会判断本地是否由对应的被请求资源对象,若有则返回响应报文,若没有则会代替发来请求的网络实体继续向下发送请求。
合理使用代理服务器可以提高访问速度、过滤经过代理服务器的内容,隐藏真实IP等。
2.面向事务(请求/响应)
“事务(Transaction)是指一系列的信息交互,而这一系列的信息交互是一个不可分割的整体,要么完成所有的信息交互,要么一次交互都不进行。”
而通常来说,HTTP面向的事务是指:一次完整的请求+响应
3.简单、易于扩展
可以在HTTP报文的头部字段按规插入自定义字段实现更多功能。
4.无连接
HTTP通常基于面向连接的TCP,HTTP报文交换本身不需要“某种HTTP连接”。
起初HTTP默认使用“短连接”,即每次TCP连接的建立与释放之间仅仅只有一次HTTP报文交换,网络利用率很低,后来默认采用了持续连接后,网络的利用率得到了一定的提高。
5.无状态
各个HTTP事务之间没有关联,Web服务器也因而无法知晓所收到请求的发送方的状态信息,以致无法实现对客户的身份追踪,通过引入Cookie和Session来解决这个问题。
6.不安全
虽然HTTP简单易用,但是没有本身相应可靠的安全机制,所以HTTP依赖其他层的协议来实现报文的安全传输,如HTTPS(借助了下层的SSL/TLS协议)
二、HTTP的报文
HTTP是面向文本的(在HTTP/1.1及之前),所以报文内的字段都是一些由Ascii码组成的串。
HTTP报文分为请求(Request)报文和响应(Response)报文。
可以看到,HTTP报文主要包含了三个内容:开始行、头字段和报文主体(可选)
1.开始行
请求报文的第一行——请求行(Request Line)
响应报文的第一行——状态行(Status Line)
-
HTTP版本是这两种开始行都包含的
-
HTTP版本:记录了本HTTP报文的协议版本
包括:HTTP/0.9、HTTP1.0、HTTP/1.1(目前主流)、HTTP/2.0,HTTP/3.0
-
-
请求报文的请求行独有这样两个特殊内容:
-
URL:记录了本次请求的目标URL,有时会使用相对URL,并在请求头的
Host
字段中附加上目标主机的域名 -
请求方法(Method):记录了本次请求的请求方法
方法名 描述 GET 向URL发出“读取资料”的请求,通过在URL后附加参数实现带参请求。GET应只用于读取内容。 POST 向URL发出“提交数据”的请求,例如上传表单或文件等,数据存在请求报文的主体中。POST可能会新建数据或修改现有的数据。 HEAD 类似于GET,向URL发出“读取资源”的请求,但不要求对方回传文本部分。 PUT 向URL发出“更新内容”的请求。 CONNECT (HTTP/1.1新增),使用这个方法来控制代理。 DELETE 向URL发出“删除URL所指向资源”的请求。 OPTIONS 向URL发出“查看可选的方法种类”的请求。 TRACE 向URL发出“回显服务器所收到的请求报文“的请求。 上述方法中,GET、HEAD、PUT和DELETE是幂等的,所谓幂等方法,就是说在使用了这个方法无论多少次,所得的结果都是相同的。
比如,我连续发送十次GET请求来下载一张图片
a.jpg
,那么这十次请求得到的图片应当都是相同的。(除非服务器在这期间修改了a.jpg
的内容)
-
-
响应报文的状态行独有这样两个特殊内容:
-
状态码:记录了本次请求的状态信息(由三位数字组成,以第一位数字的不同分为五大类)
- 1xx:服务端接收了请求消息(可以继续处理……)
- 2xx:成功
- 3xx:重定向,本次请求还需要后续操作
- 4xx:客户端的错误(请求含有错误)
- 5xx:服务端的错误(请求正确,但服务端无法正常执行)
-
原因短语:状态码对应的描述字符串
比如:
状态码
200
对应的原因短语为:OK
——请求成功状态码
304
对应的原因短语为:Not Modified
——该资源未被修改,请继续使用现有缓存状态码
404
对应的原因短语为:Not Found
——没找到URL对应的资源
-
2.请求头(Request headers)
-
描述浏览器可以接收的内容(与"Accept"有关)
-
Accept:浏览器可以接收的MIME类型
*/* 表示浏览器可以接收所有的MIME类型
-
Accept-Encoding:浏览器接收的编码方法
-
Accept-Language:浏览器接收的语言
-
-
与实现HTTP持续连接(“长连接”)有关的字段:
- Connection:
- keep-alive表示使用“长连接”
- close表示关闭”长连接“
- Connection:
-
Host:本次请求的目标URL
通常包括域名(或IP地址)和端口
-
Referer:本次请求从哪儿过来
比如,在百度的首页点击了一条新闻,那么本次新闻请求报文的请求头中
Referer
的值应该为https://www.baidu.com/
-
User-Agent:本次请求的用户代理(注:User-Agent也易于伪造)
如使用火狐浏览器时,User-Agent的值类似于:
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0
-
Cookie:用于存储用户的
Cookie
-
与HTTP缓存机制有关的字段:
- If-Modified-Since:记录了所请求资源在客户端的缓存时间
- If-None-Match:记录了所请求资源在客户端缓存时,其唯一实体标识符Etag的值
3.响应头(Response headers)
-
描述报文内容的字段(与"Content"有关)
-
Content-Type:资源的MIME类型和字符编码方式(注意是字符的编码,与资源的编码有区别)
以
;
分割,如text/html;charset=UTF-8
-
Content-Encoding:资源的编码方式
-
Content-Language:内容的语言
-
Content-Length:内容的长度
-
-
与实现HTTP长连接有关的字段:
- Connection:
- keep-alive表示使用长连接
- close表示关闭长连接
- Connection:
-
Date:服务器发送报文的时间
-
Server:web服务器类型及其版本信息
-
Set-Cookie:记录了给客户端保存的Cookie值
-
与HTTP缓存机制有关的字段:
- Expires:本次文档缓存的到期时间
- Cache-Control:用于缓存控制
- Last-Modified:记录被请求资源在服务端上最近修改时间
- Etag(Entity tag):实体标识符,记录了指定对象的唯一实体标识符Etag的值
4.请求/响应主体(Entity Body)
三、HTTP的会话机制——解决HTTP无状态带来的问题
为了提高Web服务的质量,HTTP本身不会对已发送的请求或响应做持久化处理。这就导致Web服务器“没有记忆”,服务器不记得某个客户访问过,或为其服务过多少次,但这种特性在实际中也会带来一些新的麻烦。
比如,在健身打卡这个情景中,因为HTTP是无状态的,所以无论用户是连续第几天打卡,服务器都会认为这是个第一天打卡的新人。
为了解决这种问题,有了
Cookie
和Session
。
1.Cookie
由服务端发送(通常将数据存放在响应头中以Set-cookie字段中)给客户端保存的一种特殊信息(文本文件)。
为了让服务端记得发送请求者的身份,客户端应在每次请求中都附带上相应的Cookie。
2.Session
一种位于服务端的关于客户端状态信息,一般存储在服务器的内存中,当然也可以通过相应的配置将Session存储在硬盘中。
服务端在对客户端做第一次响应时,会在响应报文中加上一个特殊的标识符(SessionID)供客户端保存在Cookie中;
客户端再次请求时会附上Cookie(其中保存了SessionID),这样服务端就能将这个SessionID作为索引,来找到存储于本地对应的用户状态数据,“拾起”起这个客户的状态信息。
虽然会话机制给Web带来了很多便利,但带来了很多安全问题。
这篇文件就针对Cookie安全进行了一定的分析和总结:总结Cookie安全:安全风险和防范建议
四、HTTP的持续连接——解决HTTP无连接带来的问题
1.HTTP的无连接
HTTP基于面向连接的TCP,但HTTP协议本身是无连接的,即双方在交换HTTP报文之前不需要先建立一个所谓的HTTP连接。
但前面提到HTTP是面向事务的,要求可靠的传输。而起初(在HTTP/1.0及以前)HTTP默认需要为了完成每个事务(一次完整的请求+响应)而单独建立TCP连接,并在客户端收到响应后立刻释放TCP连接。
无连接的HTTP要想实现可靠地完成事务,只能依赖于底层TCP协议建立的连接,但TCP也不知道啥时候该释放连接呀,还得向上层看HTTP的“脸色”。而在HTTP/1.1之前,默认的处理方式都是一次HTTP事务对应一次TCP连接与释放;
这便导致了一些问题,如当访问一个网站时,有时需要请求许多文件(如.html、.css、.js以及各种图片资料等),当需要请求的资源数量、种类变多,往往就需要客户端发送更多的HTTP请求,那么底层TCP的连接与释放也会因此而更加频繁,访问时延、网络传输、CPU及内存的开销也都会变大;
此外还会导”队头阻塞“问题(由于一直没能收到队首的响应,所以后续的所有请求都无法发送),非常容易影响Web服务的质量。
2.HTTP的持续连接(长连接)
HTTP的持续连接(Persistent Connection),也叫HTTP长连接或HTTP keep-alive。虽有其名,但HTTP的持续连接实际是指底层的TCP连接是可持续、延续的,不会总是在完成一次HTTP报文交换之后就释放连接。
自HTTP/1.1起,默认使用长连接,即在请求和响应报文的
headers
中默认包含Connection: keep-alive
在HTTP的持续连接中,复用一个TCP连接来完成多组HTTP报文交换(如果可以),在所有HTTP报文交换完毕后再释放连接。有效地提高了HTTP报文的传输效率,后续的请求无需再进行额外的TCP握手,因而可以把请求延迟进一步降低。
HTTP的持续连接有两种工作方式:
-
非流水线(Without Pipelining)
在非流水线模式下,在同一个TCP连接中,客户端只能在收到上一次请求对应的响应报文之后,才能发送下一个请求报文。
但如若遇到“队头阻塞”的情况,客户端这边排队等待被发送的请求队列就会被堵住,直到收到队头请求的响应报文。
-
流水线(Pipelining),又称管线化
HTTP持续连接的流水线模式允许客户端在收到上一个请求对应的响应报文之前发送新的请求,服务器也会根据收到的连续请求来发送连续的响应报文。
如同CPU的流水线设计提高相同时间内CPU的利用效率,HTTP持续连接的流水线模式减少了TCP连接后的空闲时间,从时间上提高了web服务的效率。
虽然客户端可以连续地发送请求,但服务端也必须按序发送响应(如同队列一般先入先出),所以还是存在队头阻塞的问题,只是从请求队列的堵塞变成了响应队列的堵塞。此外,流水线模式仅在特定条件下支持“幂等”的方法(GET、HEAD、PUT和DELETE:因为非幂等的请求方法队列可能会因到达顺序不同而产生不同的响应)。
可以看到,实现流水线模式并不是那么容易,而现在的浏览器也几乎都不默认支持HTTP持续链接的流水线。
不过可以曲线救国嘛,现在许多浏览器允许同域的Web应用同时建立有限多个TCP连接来交换HTTP报文(即默认使用HTTP持续连接的非流水线模式),一定程度上降低了队头阻塞的发生概率。
五、HTTP的缓存机制——减少流量,优化体验
通过将资源缓存在客户端,减少请求资源的次数,也可以有效地提高Web服务的质量。
HTTP的缓存机制有两种,一种是服务端缓存规则交由客户端,让其判断是否使用缓存的强制缓存,而另一种是客户端服务端双方实时交互的协商缓存。
1.强制缓存
-
HTTP/1.0使用Expires(响应报文)字段:
服务端在响应报文的Expires字段中记录该资源的过期时间tExpires(一个由服务端设定的时刻),客户端在过期时间之前都可以使用该资源的已缓存数据。
但问题是,客户端与服务端的时间可能不同步,以此导致客户端误判过期时间。
所以现在的服务器使用Expires字段通常是为了向下兼容HTTP/1.0(尽管现在HTTP/1.0已经很少见)
-
HTTP/1.1以后使用Cache-Control(响应报文)字段:
通过在Cache-Control字段中添加不同的属性来控制资源在客户端的缓存方式,如:
- 控制资源由谁缓存
- private:客户端可以缓存(默认)
- public:客户端、服务端均可缓存
- no-cache:让客户端先别着急缓存,去进行协商缓存(使用协商缓存时,应在Cache-Control字段中添加此项)
- no-store:让客户端永不缓存(例如某资源更新频率很高)
- 强制缓存(设定资源的过期时间)
- max-age:设置资源在客户端的到期时间(与使用Expires字段不同的是,这里的到期时间是一个相对的时间tmax-age:假如值为3600,则表明只要距上次对该资源的请求还没过去3600秒,客户端就可以直接使用缓存)
- 控制资源由谁缓存
2.协商缓存
-
使用If-Modified-Since和Last-Modified(借助最后修改时间Last-Modified字段来协商)
-
Last-Modified(响应报文):记录了所请求资源在服务端最近一次被修改的时间
-
If-Modified-Since(请求报文):记录了所请求资源在客户端缓存时的时间
双方就目的资源在自上次被客户端缓存以来是否有被修改过而协商,如下图:
-
-
使用If-None-Match和Etag(借助实体标识符Etag字段来协商)
-
Etag(响应报文):记录了所请求资源在服务端最新的唯一标识符
-
If-None-Match(请求报文):记录了所请求资源在客户端缓存时的唯一标识符
过程几乎与上述的If-Modified-Since/Last-Modified相同,只是从绝对的时间比较变成了更灵活的实体标识符比较。
Etag的生成方式由服务端决定,可以是哈希值或其他由特定算法生成的值,甚至也可以是资源的最近修改时间(虽同是代表修改时间,与Last-Modified相比,使用Etag模式记录时间可以更精确——Last-Modified模式只能精确到秒(S))。
-
六、HTTP的安全机制——依托于安全协议SSL/TLS
1.HTTP面临的安全问题
- ①数据保密性:由于使用明文传输数据,内容易被窃取
- ②数据完整性:HTTP没有针对数据完整性的检验机制,即使报文被中间人篡改也无法发现
- ③网站的身份鉴别:互联网中存在大量伪造的”假网站“,而HTTP没有提供对应的身份鉴别机制来检测网站的身份真实性,由此也导致了大量的电信诈骗的发生……
- ④恶意的重放攻击:攻击者重复地发送所截获的报文,以期让接收端发生处理错误(或是愚弄对方)
2.HTTPS(安全版的HTTP)
在TCP之上加入了SSL/TLS安全协议,再基于SSL/TLS的HTTP就叫HTTP over SSL或HTTP over TLS,统称为HTTPS(HTTP Secure),默认端口:443。
SSL/TLS从以下四点来解决了HTTP的安全问题:
-
①拒绝裸奔:利用双方的特有“指纹”来定制钥匙,并以此加密数据,实现“天不知地不知,只有你知我知”;
如何生成只有双方才拥有的“独二无三”的密钥呢?
这里可以用公钥密码体制。开始时,客户端服务端先互相打招呼(互相发送客户端hello和服务端hello,过程中双方交互了一个随机数)随后客户端构造一个预主密钥,并用服务端分发的公钥加密后发给服务端。只有真服务端能够解密得到预主密钥(拥有公钥对应的私钥),随后双方便可以按照事先协商好的规则,用双方的随机数和预主密钥来生成主密钥。
假设将主密钥生成的过程简单抽象为一个函数mainKey(),则主密钥 = mainKey(服务端随机数和客户端随机数(双方打招呼时已经交换),预主密钥(客户端生成并用公钥加密,只有拥有私钥的真服务端才能解密)),所以只有客户端和真正的服务端手上拥有主密钥,那么双方后续的报文都可以用这个主密钥来加解密,不必担心内容被窃取。
-
②提防篡改:利用消息摘要算法和数字签名技术,轻松识别篡改痕迹;
单通过消息摘要算法生成哈希值确实可以证明内容的完整性:
但是这还不能保证内容不被篡改,比如中间人将内容和哈希值一起换掉:
这时就可以使用数字签名技术,对哈希值用公钥PK加密(E),接收方能够用私钥SK成功解密(D)得到哈希值,并同时与由内容计算得出的哈希值对比,若两者不相同则相信内容已被篡改,如下:
而对哈希值再进行加密得到的值又称为MAC(消息校验码Message Authentication Code,注意:不是数据链路层的“媒介访问控制”),如上图中的EPK(哈希值A)。
-
③使用“照妖镜”:借助权威第三方机构进行电子证书检验,拥有了鉴别目的网站真伪的能力。
要实现上述①、②的效果需要一个前提,服务器分发的公钥是“真”的,即:这个公钥的确绑定目标域名服务器。
在进行TLS握手的时候,客户端会在客户端hello数据包中向服务端表明自己信赖的认证中心,服务端必须在接下来回复的服务端hello数据包中提供合法的证书链(其中必须包含有客户端信赖的认证中心),来证明此公钥的确属于被客户端访问的域名。
-
④另外,在TLS记录协议中,通过对HTTP报文使用HMAC算法计算MAC值会加入一个序号字段,客户端和服务端在初始的发送序号均为0,每发送一个TLS报文时,发送序号增加1,这可预防重放攻击,“中间人”再也不能轻易整蛊了。
SSL/TLS协议比较复杂,这里只进行了简单介绍,更多内容欢迎阅读这篇:传输层的安全协议——SSL/TLS
除了从协议规范上增强HTTP的安全性以外,也可以在实际中利用HTTP的灵活易扩展。
比如,使用Cookie-Session的Web应用容易受到CSRF攻击,可以在字段中增加一个令牌(Token)字段来帮助验证身份或状态信息。
七、HTTP各版本的特点(HTTP/1.1及之前)
1.历史版本
-
HTTP/0.9(已被淘汰):只接受GET、不支持请求头,且不在报文中指明HTTP版本。
-
HTTP/1.0(比较少见):使用Expires做强制缓存,官方没有实现“持续连接”。
2.现用版本&未来方向
- HTTP/1.1(现今主流):默认采用“持续连接”,加入了Etag、Cache-Control来控制缓存……
- HTTP/2.0、HTTP/3.0:To be continued……