一、计算机网络体系结构
目前,由国际化标准组织ISO制定的网络体系结构国际标准是 OSI七层模型。OSI的七层协议体系结构的概念清楚,理论也比较完整,但它既复杂又不实用。TCP/IP体系结构则不同,但它却得到了非常广泛的应用。TCP/IP是一个四层的体系结构,它包含应用层、运输层、网际层和网络接口层(用网际层这个名字是强调这一层是为了解决不同网络的互联问题)。不过从实质上讲,TCP/IP只有最上面的三层,因为最下面的网络接口层并没有什么具体内容。因此在学习计算机网络的原理时往往采取折中的办法,即综合OSI和TCP/IP的优点,采取一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚。有时为了方便,也可把最底下两层称为网络接口层。
五层协议的体系结构知识为了介绍网络原理而设计的,实际应用还是采用TCP/IP四层体系结构。
五层的体系结构至上往下,最终可以实现端对端之间的数据传输与通信,他们各自负责一些什么,最终如何实现端对端之间的通信?
- 1、应用层:如http协议,它实际上是定义了如何包装和解析数据,应用层是http协议的话,则会按照协议规定包装数据,如按照请求行、请求头、请求体包装,包装好数据后将数据传至运输层。
- 2、运输层:运输层有TCP和UDP两种协议,分别对应可靠的运输和不可靠的运输,如TCP因为要提供可靠的传输,所以内部要解决如何建立连接、如何保证传输是可靠的不丢数据、如何调节流量控制和拥塞控制。关于这一层,我们平常一般都是和Socket打交道,Socket是一组封装的编程调用接口,通过它,我们就能操作TCP、UDP进行连接的建立等。我们平常使用Socket进行连接建立的时候,一般都要指定端口号,所以这一层指定了把数据送到对应的端口号。
- 3、网络层:这一层IP协议,以及一些路由选择协议等等,所以这一层的指定了数据要传输到哪个IP地址。中间涉及到一些最优线路,路由选择算法等等。
- 4、数据链路层:主要知识点就是ARP协议,负责把IP地址解析为MAC地址,即硬件地址,这样就找到了对应的唯一的机器。
- 5、物理层:这一层就是最底层了,提供二进制流传输服务,也就是也就是真正开始通过传输介质(有线、无线)开始进行数据的传输了。
所以通过上面五层的各司其职,实现物理传输介质–MAC地址–IP地址–端口号–获取到数据根据应用层协议解析数据最终实现了网络通信和数据传输。
二、TCP连接
客户端与服务器之间数据的发送和返回的过程当中需要创建一个叫TCP connection的东西。其中比较重要的字段有:
(1)序号(sequence number):Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
(2)确认号(acknowledgement number):Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
(3)标志位(Flags):共6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下:
- URG:紧急指针(urgent pointer)有效。
- ACK:确认序号有效。
- PSH:接收方应该尽快将这个报文交给应用层。
- RST:重置连接。
- SYN:发起一个新连接。
- FIN:释放一个连接。
需要注意的是:不要将确认序号Ack与标志位中的ACK搞混了。确认方Ack=发起方Seq+1,两端配对。
三次握手建立连接
- 第一次:客户端发送请求到服务器,服务器知道客户端发送,自己接收正常。SYN=1,seq=x
- 第二次:服务器发给客户端,客户端知道自己发送、接收正常,服务器接收、发送正常。ACK=1,ack=x+1,SYN=1,seq=y
- 第三次:客户端发给服务器:服务器知道客户端发送,接收正常,自己接收,发送也正常.seq=x+1,ACK=1,ack=y+1
为什么建立连接需要三次握手?
握手两次达不到让双方都得出自己、对方的接收、发送能力都正常的结论的。第三次握手是为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误
假设一下如果没有第三次握手,而是两次握手后我们就认为连接建立,那么会发生什么?具体情况就是:C端发出去的第一个网络连接请求由于某些原因在网络节点中滞留了,导致延迟,直到连接释放的某个时间点才到达S端,这是一个早已失效的报文,但是此时S端仍然认为这是C端的建立连接请求第一次握手,于是S端回应了C端,第二次握手。如果只有两次握手,那么到这里,连接就建立了,但是此时C端并没有任何数据要发送,而S端就会傻傻的等待着,造成很大的资源浪费。所以需要第三次握手,只有C端再次回应一下,就可以避免这种情况。
四次挥手断开连接
- 第一次:客户端请求断开 FIN=1,seq=u。
- 第二次:服务器确认客户端的断开请求 ACK=1,ack=u+1,seq=v。
- 第三次:释放服务器端到客户端方向上的资源之后,服务器请求断开FIN=1,seq=w,ACK=1,ack=u+1。
- 第四次:客户端确认服务器的断开 ACK=1,ack=w+1,seq=u+1。
为什么比建立连接时多了一次握手?
可以看到这里服务端的ACK(回复客户端)和FIN(终止)消息并不是同时发出的,而是先ACK,然后再FIN,这也很好理解,当客户端要求断开连接时,此时服务端可能还有未发送完的数据,所以先ACK,然后等数据发送完再FIN。这样就变成了四次握手了。
为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
TCP如何保证长连接
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
三、HTTP
HTTP是一种应用层的超文本传输协议,这就意味着它能够传输包括文字、图片、音频、视频等格式的内容。HTTP默认端口号为80,但是你也可以改为8080或者其他端口。
HTTP请求原理
HTTP是一种应用层协议,它通过tcp实现了可靠的数据传输。详细的交互流程如下:
- 1、客户端执行网络请求,从url里面解析出来服务器的主机名
- 2、将服务器的主机名转换成服务器的ip地址
- 3、将端口号从url解析出来
- 4、建立一条客户端和服务器端的tcp链接
- 5、客户端通过输入流向服务器发送一条http请求
- 6、服务器向客户端回送一条http响应报文
- 7、客户端从输入流获取报文
- 8、解析报文,关闭连接
HTTP各个版本之间的区别
HTTP0.9
- 仅支持GET方法,只能传输纯文本内容,所以请求结束服务段会给客户端返回一个HTML格式的字符串,然后由浏览器自己渲染。
- 典型的无状态连接(无状态是指协议对于事务处理没有记忆功能,对同一个url请求没有上下文关系,每次的请求都是独立的,服务器中没有保存客户端的状态)
HTTP1.0
- HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
- 任何文件形式都可以被传输,本质上支持长连接,但是默认还是短连接,增加了keep-alive关键字来由短链接变成长连接。
- HTTP的请求和回应格式也发生了变化,除了要传输的数据之外,每次通信都包含头信息,用来描述一些信息。
- 增加了状态码(status code)、多字符集支持、多部分发送(multi-part type)、权限(authorization)、缓存(cache)、内容编码(content encoding)等。
HTTP1.1
- 新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
- 引入长链接:也就是TCP链接默认是不关闭的可以被多个请求复用。客户端或者服务器如果长时间发现对方没有活动就会关闭链接,但是规范的做法是客户端在最后一个请求的时候要求服务器关闭链接。对于同一个域名,目前浏览器支持建立6个长链接。
- 节约带宽:HTTP1.1支持只发送header头信息不带任何body信息,如果服务器认为客户端有权限请求指定数据那就返回100,没有就返回401,当客户端收到100的时候可以才把要请求的信息发给服务器。并且1.1还支持了请求部分内容,如果当前客户端已经有一部分资源了,只需要向服务器请求另外的部分资源即可,这也是支持文件断点续传的基础。
- 增加了host处理:在HTTP1.0中认为每台服务器都绑定一个唯一的ip地址,因此在URL中并没有传递主机名,但是随着虚拟机技术的发展,可能在一台物理机器上存在多个虚拟主机,并且他们共享了一个ip地址,http1.1中请求消息和响应消息都支持host头域,如果不存在还会报出错误。
HTTP2.0
- 多路复用(二进制分帧):HTTP 2.0 会将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。在一个连接里面并发处理请求,不像http1.1在一个tcp连接中各个请求是串行的,花销很大。
- 2.0版本通过算法把header进行了压缩这样数据体积就更小,在网络上传输就更快。
- 头部压缩:2.0版本通过算法把header进行了压缩这样数据体积就更小,在网络上传输就更快。
- 服务器端推流:服务端有了推送功能,将客户端感兴趣的东西推给客户端,当客户端请求这些时,直接去缓存中取就行。
HTTP是无状态的,同一个客户端对服务端多次请求,服务端无法识别是否是同一个用户。但是随着交互式Web应用的兴起,像在线购物网站,需要登录的网站等等,马上就面临一个问题,那就是要管理会话,必须记住哪些人登录系统, 哪些人往自己的购物车中放商品, 也就是说我必须把每个人区分开,这就是一个不小的挑战,因为HTTP请求是无状态的,因此引入了Cookie、Session和Token的技术。
Cookie、Session、Token
为了在去状态的HTTP请求中区别不同的用户,于是有人就想出了一个办法,这个办法就是给服务端存着每个客户端的唯一会话标识sessionId(一个随机的字符串),然后下发给客户端,保存在客户端的的cookie中。每次客户端向服务端发起HTTP请求的时候,就会把cookie中的随机字符串(sessionId)一并捎过来,这样就能区分开是哪个用户发起的请求了。(由于cookie本身最大支持4096字节,以及cookie本身保存在客户端,可能被拦截或窃取,自身安全性较差,我们应该在服务器的的session里保存私密的信息以及超过4096字节的文本)
这样,服务器就增加了压力,因为服务器需要保存所有用户的sessionId。如果有上千上万个sessionId,对服务器来说就是一个巨大的开销,严重地限制了服务器的扩展能力。比如说用两个机器组成了一个集群,小明通过机器A登陆了系统,那sessionId就会保存在机器A上,假设小明的下一次请求被转发到机器B怎么办?机器B可没有小F的sessionId啊。有时候会采用session sticky的小技巧,就是让小明的请求一直粘连在机器A上。但是这也不管用,要是机器A挂掉了,还得转到机器B上去。
为了解决A机器挂掉的问题,只好做sessionId的复制了,把sessionId在两个机器之间搬来搬去。
后来有个叫Memcached的支了招: 把session id 集中存储到一个地方, 所有的机器都来访问这个地方的数据, 这样一来,就不用复制了, 但是增加了单点失败的可能性, 要是那个负责session 的机器挂了, 所有人都得重新登录一遍, 估计得被人骂死。
也尝试把这个单点的机器也搞出集群,增加可靠性。但不管如何,这小小的session对服务器来说都是一个沉重的负担。
于是,就有人提出了摆脱session的想法。具体就是不再在服务端保存sessionId了,让客户端去保存一个服务端生成的token,每次请求的时候附加上这个token,服务端只要对这个token进行相应的校验就可以完成身份验证。比如说,小明已经登陆了系统,服务端给他返回了一个令牌(token),里面包含了小明的userId,下一次小明再通过HTTP请求访问服务端的时候,就把这个token通过HTTP的Header带过来就可以了。
因为服务端不再存sessionId了,也不会存token,那么就可能会造成有心怀不轨的人伪造token来做坏事。因此,服务端需要对token做一些防伪造的操作,具体是对数据做一个签名。比如说用HMAC-SHA256算法加上一个只有服务器知道的密钥,对数据做一个签名,然后把签名和数据一起作为token,由于密钥别人不知道,就无法伪造token了。服务器并不会保存这个token,当小明再次将token发到服务器的时候,服务端会用同样的HMAC-SHA256算法和同样的密钥去对数据再计算一次签名,并和token中的签名做一个比较,如果相同的话,就可以判断出小明已经登陆过,并且可以直接取到小明的userId;如果不相同,则数据部分肯定被人篡改过,这时就能够做一些身份校验失败的相应处理。
这样一来,服务器就不需要保存sessionId了,只需要生成token,然后校验token,相当于用CPU计算时间换回了存储空间。解决了sessionId这个负担,可以说是无事一身轻。机器集群现在可以轻松地做水平扩展,用户访问量增大的时候直接加机器就OK。
总结
- Cookie
由服务器生成,发送给浏览器,浏览器把Cookie以Key-Value的形式保存到某个目录下的文本文件内,下一次请求同一个网站的时候会把该Cookie发送给服务器。由于Cookie是存在客户端上的,所以浏览器加入了一些限制确保Cookie不会被恶意使用,同时不会占据太多的磁盘空间,每个域的Cookie数量都是有限的,最大支持4096字节。 - Session
服务器使用Session把用户的信息临时保存在了服务器上,用户离开网站之后Session会被销毁。这种用户信息存储方式相对Cookie来说更加安全,而且存储空间是无限量的。可是Session有一种缺陷,如果Web服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候Session就会丢失。 - Token
基于Token的身份验证是无状态的,不将用户信息存在服务器、Session或者Cookie中,客户端存储Token,服务器运算验证,用CPU计算时间换回了存储空间,支持跨域访问,性能更好,安全性更高,方便服务器负载均衡。
HTTP请求方法
方法类型 | 描述 |
---|---|
GET | 请求指定的页面信息,并返回实体主体。 |
POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 |
OPTIONS | 允许客户端查看服务器的性能,可以查询针对请求URI指定资源支持的方法 |
HEAD | 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 |
PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
DELETE | 请求服务器删除指定的页面。 |
CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
禁用部分不安全的HTTP方法
众所周知,GET、POST是最为常见方法,而且大部分主流网站只支持这两种方法,因为它们已能满足功能需求。其中,GET方法主要用来获取服务器上的资源,而POST方法是用来向服务器特定URL的资源提交数据。而其它方法出于安全考虑被禁用,所以在实际应用中,九成以上的服务器都不会响应其它方法,并抛出404或405错误提示。以下列举几个HTTP方法的不安全性:
- 1、OPTIONS方法,将会造成服务器信息暴露,如中间件版本、支持的HTTP方法等。
- 2、PUT方法,由于PUT方法自身不带验证机制,利用PUT方法即可快捷简单地入侵服务器,上传Webshell或其他恶意文件,从而获取敏感数据或服务器权限。
- 3、DELETE方法,由于PUT方法自身不带验证机制,利用DELETE方法可以删除服务器上特定的资源文件,造成恶意攻击。
除GET、POST之外的其它HTTP方法,其刚性应用场景较少,且禁止它们的方法简单,即实施成本低。一旦让低权限用户可以访问这些方法,他们就能够以此向服务器实施有效攻击,即威胁影响大。
HTTP请求头
Header类型 | 解释 | 示例 |
---|---|---|
Accept | 指定客户端能够接收的内容类型 | Accept: text/plain, text/html |
Accept-Charset | 浏览器可以接受的字符编码集 | Accept-Charset: iso-8859-5 |
Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型 | Accept-Encoding: compress, gzip |
Accept-Language | 浏览器可接受的语言 | Accept-Language: en,zh |
Accept-Ranges | 可以请求网页实体的一个或者多个子范围字段 | Accept-Ranges: bytes |
Authorization | HTTP授权的授权证书 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Cache-Control | 指定请求和响应遵循的缓存机制 | Cache-Control: no-cache |
Connection | 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) | Connection: close |
Cookie | HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 | Cookie: $Version=1; Skin=new; |
Content-Length | 请求的内容长度 | Content-Length: 348 |
Content-Type | 请求的与实体对应的MIME信息 | Content-Type: application/x-www-form-urlencoded |
Date | 请求发送的日期和时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
Expect | 请求的特定的服务器行为 | Expect: 100-continue |
From | 发出请求的用户的Email | From: user@email.com |
Host | 指定请求的服务器的域名和端口号 | Host: www.zcmhi.com |
If-Match | 只有请求内容与实体相匹配才有效 | If-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Modified-Since | 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 | If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
If-None-Match | 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 | If-None-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Range | 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag | If-Range:“737060cd8c284d8af7ad3082f209582d” |
If-Unmodified-Since | 只在实体在指定时间之后未被修改才请求成功 | If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
Max-Forwards | 限制信息通过代理和网关传送的时间 | Max-Forwards: 10 |
Pragma | 用来包含实现特定的指令 | Pragma: no-cache |
Proxy-Authorization | 连接到代理的授权证书 | Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Range | 只请求实体的一部分,指定范围 | Range: bytes=500-999 |
Referer | 先前网页的地址,当前请求网页紧随其后,即来路 | Referer: http://www.zcmhi.com/archives/71.html |
TE | 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 | TE: trailers,deflate;q=0.5 |
Upgrade | 向服务器指定某种传输协议以便服务器进行转换(如果支持) | Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 |
User-Agent | User-Agent的内容包含发出请求的用户信息 | User-Agent: Mozilla/5.0 (Linux; X11) |
Via | 通知中间网关或代理服务器地址,通信协议 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 关于消息实体的警告信息 | Warn: 199 Miscellaneous warning |
HTTP状态码
值 | 状态 | 说明 |
---|---|---|
200 | OK | 表示请求被服务器正常处理 |
204 | No Content | 表示请求已成功处理,但是没有内容返回,也就是返回的响应报文中没有报文实体,浏览器页面不会发生更新 |
301 | Moved Permanently | 永久重定向,表示请求的资源已经永久的搬到了其他位置,就是说资源已经被分配了新的URI |
302 | Found | 临时重定向,表示请求的资源临时搬到了其他位置请求的资源暂时被配到到了新的URI |
400 | Bad Request | 表示请求报文存在语法错误或参数错误,服务器不理解服务器不应该重复提交这个请求。 |
401 | Unauthorized | 表示发送的请求需要有HTTP认证信息或者是认证失败了返回401的响应必须包含一个适用于被请求资源的WWW-Authenticate首部以质询用户信息 |
403 | Forbidden | 表示对请求资源的访问被服务器拒绝了 |
404 | Not Found | 表示服务器找不到你请求的资源 |
500 | Internal Server Error | 表示服务器执行请求的时候出错了,可能是Web应用有bug或临时故障 |
503 | Service Unavailable | 表示服务器超负载或正停机维护,无法处理请求 |
HTTP报文
HTTP报文的分类有两种:请求报文和响应报文。顾名思义,请求报文就是客户端向服务端发送请求的信号,响应报文就是服务端响应处理后回传给客户端的信号。
请求报文
HTTP 请求报文由四部分组成,分别是请求行、请求头、空行和请求体,其中空行也是组成部分之一,作用是进行分隔,必不可少。
- 请求行
第一行为请求行,由请求方法、URI和HTTP协议版本3个字段组成,它们之间用空格分隔,最后以回车和换行符结尾进行内容分割,表示接下来的内容(下一行开始的)不是请求行的内容例如。
举例:GET /index.php HTTP/1.1,GET是请求方法,/index.php是URI,HTTP/1.1表示使用的HTTP版本为1.1。 - 请求头
请求头部由键/值对组成,每行一对,键和值用冒号“:”(英文)分隔。请求头部告知服务器所有有关于客户端请求的信息。
举例:Accept:application/xml;Accept-Language:zh-CN;Accept-Encoding: gzip - 空行
用户进行内容分割,表示请求头到此为止,下一行的内容不再是请求头。 - 请求体
请求体包含的就是请求数据,正如上文提高的,当使用的是GET方法的时候,没有请求体,POST方法有。
响应报文
客户端向服务端发送请求之后,服务器接收并处理客户端发过来的请求后正常情况下会返回一个HTTP的响应消息,这个就是响应报文。
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。形式上除了状态行之外,其他三个部分与请求报文类似。
- 状态行
格式为:HTTP-Version Status-Code Reason-Phrase CRLF。其中,HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态代码;Reason-Phrase表示状态代码的文本描述。状态代码由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值(上文已介绍)。
举个例子:HTTP/1.1 200 OK(CRLF)。 - 消息报头
和请求报文的请求头类似,响应头也由键值对组成,每行一对,键和值用英文冒号 : 分隔。响应头域允许服务器传递不能放在状态行的附加信息,这些域主要描述服务器的信息和Request-URI进一步的信息。
举个例子:Server:包含处理请求的原始服务器的软件信息;Date:服务器日期; - 空行
作为内容分割,表示以下不再是响应头的内容。 - 响应正文
这个是服务器返回给浏览器响应的消息体,情况有多种:如果是纯数据就是返回纯数据,如果请求的是HTML页面,那么返回的就是HTML代码,如果是JS就是JS代码,如此之类。
HTTP缓存机制
Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。浏览器缓存也包含很多内容: HTTP 缓存、indexDB、cookie、localstorage 等等。这里我们只讨论 HTTP 缓存相关内容。浏览器缓存主要是 HTTP 协议定义的缓存机制。HTML meta 标签,例如
<META HTTP-EQUIV="Pragma" CONTENT="no-store">
含义是让浏览器不缓存当前页面。但是代理服务器不解析 HTML 内容,一般应用广泛的是用 HTTP 头信息控制缓存。
HTTP头信息控制缓存
大致分为两种:强制缓存和对比缓存。强制缓存如果命中缓存不需要和服务器端发生交互,而对比缓存不管是否命中都要和服务器端发生交互。强制缓存的优先级高于对比缓存。不管是哪种缓存,在第一次请求的的逻辑都是一致的,此时缓存数据库中没有对应的缓存数据,需要请求服务器,服务器返回后,将数据根据头信息规则存储至缓存数据库中,如下图:
强制缓存
可以理解为无须验证的缓存策略。对强缓存来说,响应头中有两个字段 Expires/Cache-Control 来表明规则。
Expires
Expires 指缓存过期的时间,超过了这个时间点就代表资源过期。有一个问题是由于使用具体时间,如果时间表示出错或者没有转换到正确的时区(服务器和客户端时区不同)都可能造成缓存生命周期出错。并且 Expires 是 HTTP/1.0 的标准,现在更倾向于用 HTTP/1.1 中定义的 Cache-Control。两个同时存在时也时,Cache-Control 的优先级比Expires高。
Cache-Control
Cache-Control 是最重要的规则。常见的取值有private、public、no-cache、max-age,no-store,默认为private。
- private:客户端可以缓存
- public:客户端和代理服务器都可缓存(前端的同学,可以认为public和private是一样的)
- max-age=xxx: 缓存的内容将在 xxx 秒后失效
- no-cache: 需要使用对比缓存来验证缓存数据(后面介绍)
- no-store: 所有内容都不会缓存,强制缓存,协商缓存都不会触发
假设浏览器存在一个缓存数据库,用于存储缓存信息。在第二次请求的时候,已存在缓存数据时,仅基于强制缓存,请求数据的流程如下:
我们可以看到,如果强制缓存生效,不需要再和服务器发生交互,直接从缓存数据库读取数据。
对比缓存
缓存的资源到期了,并不意味着资源内容发生了改变,如果和服务器上的资源没有差异,实际上没有必要再次请求。客户端和服务器端通过某种验证机制验证当前请求资源是否可以使用缓存。
浏览器第一次请求数据之后会将数据和响应头部的缓存标识存储起来。再次请求时会带上存储的头部字段,服务器端验证是否可用。如果返回 304 Not Modified,代表资源没有发生改变可以使用缓存的数据,获取新的过期时间。反之返回 200 就相当于重新请求了一遍资源并替换旧资源。
对比缓存有两种组合,分别为Last-modified/If-Modified-Since和Etag/If-None-Match,如果两种组合同时存在,Etag/If-None-Match优先级比Last-modified/If-Modified-Since高。
Last-modified/If-Modified-Since
Last-modified: 服务器端资源的最后修改时间,响应头部会带上这个标识。第一次请求之后,浏览器记录这个时间,再次请求时,请求头部带上 If-Modified-Since 即为之前记录下的时间。服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200,若没有修改过则返回 304。
注意:如果响应头中有 Last-modified 而没有 Expire 或 Cache-Control 时,浏览器会有自己的算法来推算出一个时间缓存该文件多久,不同浏览器得出的时间不一样,所以 Last-modified 要记得配合 Expires/Cache-Control 使用。
Etag/If-None-Match
由服务器端上生成的一段 hash 字符串,第一次请求时响应头带上 ETag: abcd,之后的请求中带上 If-None-Match: abcd,服务器收到请求后发现有头If-None-Match 则与被请求资源的唯一标识进行比对,不同,说明资源又被改动过,则响应整片资源内容,返回状态码200;相同,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。
对比缓存流程如下:
我们可以看到,对比缓存无论缓存是否生效都会请求服务器。
总结
- 强制缓存优先于对比缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行对比缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match)
- 对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。
- 对于比较缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。
忽略第一次请求,在第二次有缓存的情况下,HTTP缓存的策略流程如下:
四、HTTPS
HTTP协议因为其轻、小、快、简单,所以在全世界普及开来,各种应用都离不开它。但是随着业务复杂度的提高,HTTP的这些优点逐渐成为了短板。所以就开始各种打补丁,比如为了管理状态而诞生的Cookie,为了安全而诞生的超级补丁SSL(HTTPS)等等。
HTTP的短板
1、容易被窃听
HTTP使用明文进行传输,因此传输内容可能会被窃听。以太网工作方式是将要发送的数据包发往连接在一起的所有主机。在包头中包括所有应该接收数据包的主机的正确地址,因为只有与数据包中目标地址一致的那台主机才能接收到信息包,但是当主机工作在监听模式下的话不管数据包中的目标物理地址是什么,主机都将可以接收到。
2、容易被伪装
HTTP协议中不管是请求还是响应都不会对客户端和服务器的身份进行确认。在HTTP协议通信时,任何人都可以发起请求,服务器只要接收到请求,不管对方是谁都会返回一个响应(当然,可以在服务端对IP或者端口进行限制)。因此,可能服务器或者客户端并不是我们想象中的对方。而且,对于无意义的请求服务器也会照单全收,可能会遭遇DoS攻击。
3、容易被篡改
对于信息的准确性我们可以用一个专业术语—报文完整性来说明,由于HTTP无法验证通信报文的完整性,因此,在请求或者响应发送之后到接收这段时间内,传输的内容可能已经被篡改(中间人攻击)。
HTTP+ 加密 + 身份认证 + 完整性保护=HTTPS
HTTPS端口号:443
为了解决这些问题,HTTPS顺应而生。可以这么理解,HTTPS是安全版的HTTP,它不是一个新的协议,而是HTTP 加上加密处理(解决HTTP通信使用明文被窃听问题)和认证(解决HTTP不验证通信方的身份被伪装问题)以及完整性保护(解决HTTP无法证明报文完整性被篡改问题)后的东西。
HTTPS是 HTTP通信接口部分用SSL(Secure Socket Layer)和 TLS(Transport Layer Security)协议代替而已。通常,HTTP 直接和 TCP 通信。当使用 SSL 时,则演变成先和 SSL 通信,再由 SSL 和 TCP 通信了。简言之,所谓 HTTPS,其实就是身披SSL 协议这层外壳的 HTTP。
加密方法
近代的加密方法中加密算法是公开的,而密钥却是保密的。就比如:制造一把锁的方法是公开的,但是要想解开这把锁得有钥匙(别说可以砸,这触及到我的知识盲区了),这个钥匙就相当于密钥。你虽然知道是什么加密算法,但你没密钥就是没法还原密文。通过这种方式得以保持加密方法的安全性。现在的加密方法一般有两种:对称密钥加密和非对称密钥加密。
- 对称密钥加密
加密和解密同用一个密钥的方式称为共享密钥加密,如下图:
存在的问题:如何把密钥安全的发给对方?(如果不发密钥,对方解不了密。发送就有安全风险,而且密钥如果能安全发送,那数据也可以,那还要密钥干啥?所以就很矛盾)另外还要安全的保管接受到的密钥。
- 非对称密钥加密
使用两把密钥,一把叫做私有密钥(private key),另一把叫做公开密钥(public key)。私有密钥不能让其他任何人知道,而公开密钥则可以随意发布,任何人都可以获得。如下图:
解释一下这幅图: Alice是发送方,Bob是接收方。Alice(发送方)使用Bob(接收方)的公钥对要发送的明文数据进行加密,通过网络传输给Bob,Bob接收到密文后使用自己的私钥解密得到明文。那有人会有疑问了?密文和公钥都在网络上传输,肯定也有被监听的风险啊。对,没错,不过根据密文和公钥想还原出明文是非常非常非常非常非常非常困难的。
HTTPS加密方法
对称加密因为加解密使用的是同一密钥,相对非对称加解密使用不同密钥,对称加密对CPU资源的消耗会更少,速度会更快,更适合大量数据的加密。但是对称加密需要将密钥发送给通信的另一方,如果密钥在传输过程中被截获,那就白忙活了。所以这时候就需要非对称加密发挥优势了,非对称加密可以用来传输对称加密的密钥。
HTTPS当然使用复杂的混合加密机制(对称加密和非对称加密一起使用)。其实对称加密已经够用,只是有一个问题,对称加密的密钥如何发送给通信的另一方。我们把信息装进一个带锁的箱子了,并把箱子发送给另一方,对方如何打开箱子才是问题的关键。这一过程也叫做密钥交换。混合加密机制就是在密钥交换阶段使用非对称加密方式,之后使用对称加密方式进行传输。
证书
非对称密钥加密方式也存在一些问题,就是无法证明公开密钥就是接收方公开的那个密钥,而不是被攻击者调包的密钥。为了解决这个问题,可以使用由数字证书认证机构(CA,Certificate Authority)和其相关机构颁发的公开密钥证书。数字证书认证机构处于客户端与服务器双方都可信赖的第三方机构的立场上。这货就相当于是公证处,大家都相信公证处。以下是数字证书认证机构的业务流程:
1、服务器的运营人员向数字证书认证机构提出公钥申请。
2、认证机构在判明申请者的身份之后,对已申请的公钥做数字签名(非对称加密使用私钥进行加密叫数字签名)。
3、认证机构分配这个已签名的公钥,并将该公钥放入公钥证书后绑定在一起。
4、服务器将证书发送给客户端,客户端使用认证机构的公钥解开证书,进行验证。
HTTPS的通信过程
前置知识已经讲完了。我们回到刚才说要讲的SSL协议上来,HTTPS使用SSL和TLS这两个协议进行安全通信。TLS是以SSL为原型开发的协议,有时会统一称该协议为SSL。看下HTTPS的通信过程:
这个图有12个步骤,别慌,我先用通俗的话讲一下流程,后面再给出具体的每一步干什么。
通俗版:
客户端向一个需要HTTPS访问的网站发起请求,服务器将证书(包含公钥,非对称加密发送)发给客户端进行校验,客户端校验成功后,会给服务器发送一个使用公钥加密后的随机串,服务器使用私钥解密这个串,从此服务器使用这个随机串进行对称加密开始和客户端进行通信,客户端拿到消息使用随机串进行解密。
专业版:
- 步骤 1: 客户端通过发送 Client Hello 报文开始 SSL 通信。报文中包含客户端支持的 SSL 的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。
- 步骤 2: 服务器可进行 SSL 通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL 版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
- 步骤 3: 之后服务器发送 Certificate 报文。报文中包含公开密钥证书,非对称加密发送。
- 步骤 4: 最后服务器发送 Server Hello Done 报文通知客户端,最初阶段的 SSL 握手协商部分结束。
- 步骤 5: SSL 第一次握手结束之后,客户端以 Client Key Exchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-master secret 的随机密码串。该报文已用步骤 3 中的公开密钥进行加密。
- 步骤 6: 接着客户端继续发送 Change Cipher Spec 报文。该报文会提示服务器,在此报文之后的通信会采用 Pre-master secret 密钥加密。
- 步骤 7: 客户端发送 Finished 报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够成功,要以服务器是否能够正确解密该报文作为判定标准。
- 步骤 8: 服务器同样发送 Change Cipher Spec 报文。
- 步骤 9: 服务器同样发送 Finished 报文。
- 步骤 10: 服务器和客户端的 Finished 报文交换完毕之后,SSL 连接就算建立完成。当然,通信会受到 SSL 的保护。从此处开始进行应用层协议的通信,即发送 HTTP 请求。
- 步骤 11: 应用层协议通信,即发送 HTTP 响应。
- 步骤 12: 最后由客户端断开连接。断开连接时,发送 close_notify 报文。上图做了一些省略,这步之后再发送 TCP FIN 报文来关闭与 TCP的通信。
在以上流程中,应用层发送数据时会附加一种叫做 MAC(Message Authentication Code)的报文摘要。MAC 能够查知报文是否遭到篡改,从而保护报文的完整性。