【基础】HTTP、TCP/IP 协议的原理及应用

前言

本文将持续记录笔者在学习过程当中掌握的一些 HTTPTCP/IP 的原理,以及这些网络通讯技术的一些应用场景,文章会保持更新,至关于对这块知识的一个总结和概括。有不正确之处欢迎指出,及时改正~javascript

纲要

访问网页时发生了什么

当用户在浏览器地址栏输入地址,敲下回车键,直到看到网页界面,通常时间不过两三秒左右。然而在这瞬时间,计算机实际上已经完成了很是复杂的操做。这段过程当中发生的事情,其实有很大一部分就与 HTTP TCP/IP 有关,咱们能够简要的归纳一下大概的流程。css

第一步,找服务器 IP

当用户输入一个网址并按下回车键的时候,浏览器获得了一个域名。而在实际通讯过程当中,浏览器须要的是一个 IP 地址。为了得到 IP 地址,浏览器会作以下操做,通常咱们把浏览器经过域名查找对应 IP 的行为叫作 DNS 解析。html

  1. 先找浏览器的本地的缓存
  2. 再找电脑硬盘里的 host 文件,有没有记录这个域名和 IP 的映射关系
  3. 实在没找到,只好经过网络链路去域名供应商那里查询

第二步,创建 TCP/IP 链接

  1. 浏览器获取到了服务器对应 IP,就会向对应 IP 的服务器发送 TCP 链接请求。
  2. 服务器收到请求后回应,双方屡次确认后创建起 TCP 双向链接

从客户端发起链接请求一直到 TCP 链接创建,这个过程,叫作 三次握手前端

若是请求是 HTTPS 的,还须要在 TCP 链接上,再经过 SSLTLS 提供加密处理数据、验证对方身份以及数据完整性,来保证数据传输的安全。java

第三步,请求资源

  1. TCP 链接建立完成,浏览器开始向服务端发送正式的 HTTP 请求的数据包。
  2. 服务端接受请求,对请求进行解析,通过数据操做后,返回客户端须要的数据包。

第四步,浏览器渲染

浏览器获取到须要的数据之后,对数据进行拼接、解析、执行,最终将完整的网页绘制在页面上。webpack

第五步,浏览器缓存

浏览器拿到服务端返回的数据后,会根据必定的策略进行数据的缓存,这样在下一次请求一样数据的时候,就可直接到缓存拿取,再也不请求服务器。nginx

上述流程能够看做是一个应用在完整网络通讯过程当中的实践场景,其中带出了不少网络通讯的知识点,下面就以这条线为索引,对其中涉及到的知识碎片进行阐述和说明。web

经典网络五层模型

在每台计算机设备上,都有这么一套系统链路的关系,来保证网络传输的正常进行,由于统一集成了这么一套经典模型,因此本身使用的计算机也是能够做为一台服务器来提供网络服务的。面试

应用层:

应用层包含了咱们所说的 HTTP 协议,为各个应用软件提供了不少服务,常见的应用层服务有:HTTP 服务 、FTP 服务 、Email 服务等。应用层屏蔽了底层模型的相关细节,做为应用支持,只提供给使用者一些必要的使用方式。数据库

传输层

常见的传输层协议有 TCPUDP ,传输层做为为应用层的基础,定义了“端到端(end to end)”之间数据间的传输方式,好比:两台设备如何创建链接?设备之间须要以何种规范进行数据传输?须要以什么方式进行数据的分片、重组、拼接?这些都是传输层为咱们定义好的。

网络层

一般咱们常说的 IP 协议就位于这一层。网络层为数据在结点之间传输建立逻辑链路,当咱们在浏览器敲下域名,浏览器在网络里如何经过这个域名,找到对应的 IP 映射,这个查询的逻辑关系和链路,是网络层规范和定义的。

数据链路层

数据链路层在通讯实体间创建数据链路链接,物理设备链接完成之后,须要相应的软件和驱动来链接和打通这些物理设备,建立电路的链接。

物理层

定义物理设备如何传输数据,常见的物理层有网线,光缆,网卡,声卡等,物理层是一切软件的基础。

URI、URL 和 URN

对于 URL 咱们基本比较熟悉,然而对 URIURN 的了解可能比较少,URIURLURN 是识别、定位和命名互联网上的资源的标准途径。

当咱们在浏览器地址栏里输入域名的那一刻,其实已经和这三个概念牵扯上了联系。

URI

  • Uniform Resource Identifier,统一资源标识符,简称为 URI
  • 每一个 Web 服务器都有一个 URI 标识符,它在世界范围内惟一标识并定位信息资源,一个资源信息有了 URI 标识之后,在互联网上就能经过一个固定的地址访问到这个资源。
  • 它具备两种形式,URN (统一资源名)、URL(统一资源定位符),也就是说 URLURN 是它的子集。

URL

Uniform Resource Locator,统一资源定位符,简称 URL,下图是一个完整的 URL 组成。

一个完整的 URL 从左到右包含以下部分:

  1. schema 标识了这个资源地址所基于的访问协议,常见的好比:HTTPFTP
  2. user information 标识了用户信息(若是这个资源须要用户信息认证的话),不过通常如今的认证都不采用这种方式,一来输入很是麻烦,二来不安全。
  3. host 标识了资源的域信息,能够是域名,也能够是 IP ,这块的做用主要是找到资源所存放的物理服务器地址。
  4. port 端口号,一个物理服务器,经过开启不一样的端口,就同时能够运行多个 web 服务器,资源文件会部署在某一个 web 服务器的某一个地方,而端口号就是用来定位资源存在的 web 服务器的。
  5. path 路径,或者叫路由,一个 web 服务器下有许多目录,通常 path 就是用来定位到资源文件所存放的目录的。因为如今不少的 web 应用很是庞大,这个路径也不必定就是目录地址,也多是 web 服务器指定的静态资源文件的请求地址。
  6. query 查询字符串,通常用于 GET 查询,传递查询参数。
  7. fragment 片断,哈希,或者叫锚点,主要用于前端文档的定位,或者是前端渲染时控制路由跳转的手段。

这里须要注意将 URL 与网址区别开来。 URL 不只仅包含了网页的资源地址,还包含了组成网页所需的图片、视频等超文本资源,以及 css js 等资源地址。 网址本质上是 IP 地址的一个更有辨别度的映射,在经过 DNS 解析以后,浏览器最早拿到的是 html 文档的 URL 地址,根据浏览器对 Html 文档的解析,继续经过网页内其余资源文件的 URL 获取对应的资源文件。

URN

Uniform Resource Name,统一资源名称,简称 URN,它的用处简单说就是永久定位资源,由于同一个资源可能会更换存储位置,存储位置一旦更换,再访问原来的 url 确定是拿不到的,URN 就是解决这个问题的,无论资源位置怎么移动,只要访问同一个 URN 都能定位到。

TCP/IP 协议族

TCP 链接的创建,是网络通讯成功不可或缺的一步,所以,TCP/IP 也成为了一个十分重要的知识点。

TCP/IP 协议(传输控制协议/互联网协议)不是简单的一个协议,而是一组特别的协议,包括:TCP,IP,UDP,ARP等,这些被称为子协议。在这些协议中,最重要、最著名的就是 TCPIP。所以咱们习惯将整个协议族称为 TCP/IP

  • IP 协议
    • IP 协议使互联网成为一个容许链接不一样类型的计算机和不一样操做系统的网络。
    • IP 地址是 IP 协议提供的一种统一的地址格式,它为互联网上的每个网络和每一台主机分配一个逻辑地址,至关于这台机器的暂用名,别的机器能够经过这个名字找到它,进而能互相创建起链接进行通讯和交流。
  • TCP 协议
    • TCP 协议是面向链接的全双工协议,所以无论是客户端仍是服务端都能在 TCP 链接通道下向对端接收和发送数据。
    • TCP 相比于 UDP 的优点在于它的传输稳定性,在数据传输以前必须通过三次握手创建链接;在数据传输过程当中必须保证数据有序完整地传到对端。
    • TCP 相比于 UDP 的劣势在于它的复杂度,链接创建、断开都是比较大的性能开销,并且数据传输过程当中一旦卡住,则必须等前面的数据发送完毕之后,后续数据才能继续传输。
    • 每台服务器可提供支持的 TCP 链接数量是有限的,因此这也使得 TCP 链接变成了稀缺资源,经不起浪费。
  • UDP 协议
    • UDP 协议是面向无链接的,不须要在传输数据前先创建链接,想发就发想传就传。
    • UDP 作的工做只是报文搬运,不负责有序且不丢失地传递到对端,所以容易出现丢包的状况。
    • UDP 不只支持一对一的传输方式,还支持一对多、多对多、多对一的方式,也就是说 UPD 提供了单播、多播、广播的功能。
    • UDP 相比于 TCP 的优点在于它的轻量、高效和灵活,在一些对于实时性应用要求较高的场景下须要使用到 UDP,好比直播、视频会议、LOL等实时对战游戏。
    • UDP 相比于 TCP 的劣势在于它的不可靠性和不稳定性。

TCP 链接

在客户端发送正式的 HTTP 请求以前,须要先建立一个 TCP 链接,在建立的 TCP Connect 通道下,全部的 HTTP 请求和响应才能正常的发送和接受。

在不一样的 HTTP 协议版本里,这个 TCP 链接通道的建立和持续机制也有所不一样。

  • HTTP1.0 中,每一次 HTTP 请求都会建立一个 TCP 链接,在请求发送完成,服务器响应之后,这个 TCP 链接就自动断开了。
  • HTTP1.1 中,能够经过手动设置 Connection: keep-alive 请求头来创建 TCP 的持久链接,多个 HTTP 请求能够共用一个 TCP 链接。可是 TCP 链接存在线头阻塞,即若干个请求排队等待发送,一旦有某请求超时等,后续请求只能被阻塞。
  • HTTP2 中,采用了信道复用,使 TCP 链接支持并发请求,即多个请求可同时在一个链接上并行执行。某个请求任务耗时严重,不会影响到其它链接的正常执行吗,这样一来,大部分请求可使用一个 TCP 链接,而不用建立新的 TCP 链接通道,既节省了三次握手的开销,又节约了服务端维护 TCP 端口的成本。

TCP 的三次握手和四次挥手

三次握手

提示:关于 ACKFINSYN 状态码的含义

  1. ACK 用于确认,表示通知对方,我已经收到你发来的信息了。
  2. FIN 用于结束,表示告知对方,我这边已经结束,数据所有发送完毕,没有后续输出,请求终止链接。
  3. SYN 用于同步和创建链接,表示告知对方,我这边请求同步创建链接。
  1. 第一次握手:由客户端向服务端发送链接请求 SYN 报文,该报文段中包含自身的数据通信初始序号,请求发送后,客户端便进入 SYN-SENT 状态。
  2. 第二次握手:服务端收到链接请求报文段后,若是赞成链接,则会发送一个包含了 ACKSYN 报文信息的应答,该应答中也会包含自身的数据通信初始序号(在断开链接的“四次挥手”时,ACKSYN 这两个报文是做为两次应答,独立开来发送的,所以会有四次挥手),服务端发送完成后便进入 SYN-RECEIVED 状态。
  3. 第三次握手:当客户端收到链接赞成的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时链接创建成功。

面试时可能会问的一个问题就是,明明两次握手就能肯定的链接,为何须要三次握手? 由于因为不少不可控制的因素,例如网络缘由,可能会形成第一次请求隔了好久才到达服务端,这个时候客户端已经等待响应等了好久,以前发起的请求已超时,已经被客户端废弃掉再也不继续守着监听了。 然而服务端过了好久,收到了废弃的延迟请求,发起回应的同时又开启了一个新的 TCP 链接端口,在那里呆等客户端。 而服务端能维护的 TCP 链接是有限的,这种闲置的无用连接会形成服务端的资源浪费。 所以在服务端发送了 SYNACK 响应后,须要收到客户端接的再次确认,双方链接才能正式创建起来。三次握手就是为了规避这种因为网络延迟而致使服务器额外开销的问题。

四次挥手

和创建 TCP 链接相似,断开 TCP 链接也一样须要客户端于服务端的双向交流,由于整个断开动做须要双端共发送 4 个数据包才能完成,因此简称为“四次挥手”。

  1. 第一次挥手:客户端认为本身这边的数据已经所有发送完毕了,因而发送一个 FIN 用来关闭客户端到服务端的数据传输,发送完成之后,客户端进入 FIN_WAIT_1 状态。
  2. 第二次挥手:服务端收到客户端发送回来的 FIN 之后,会告诉应用层要释放 TCP 连接,而且发送一个 ACK 给客户端,代表已经收到客户端的释放请求了,不会再接受客户端发来的数据,自此,服务端进入 CLOSE_WAIT 的状态。
  3. 第三次挥手:服务端若是此时还有未发送完的数据能够继续发送,发送完毕后,服务端也会发送一个释放链接的 FIN 请求用来关闭服务端到客户端的数据传送,而后服务端进入 LAST_ACK 状态。
  4. 第四次挥手:客户端接收到服务端的 FIN 请求后,发送最后一个 ACK 给服务端,接着进入 TIME_WAIT_2 状态,该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,客户端就进入 CLOSED 状态.服务端在收到应答消息后,也会进入 CLOSED 状态,至此完成四次挥手的过程,双方正式断开链接。

上面的内容可能仍是有些不够直观,因此我还准备了一段人话来描述整个过程:

  1. 客户端:喂,我好了。
  2. 服务端:噢,你好了是吧,我知道了,我还没好,你等一哈。
  3. 服务端:OK,如今我也好了。
  4. 客户端:收到,此次玩的很开心,咱们下次再约。

可能有些面试中会问,为何创建链接有三次握手,而断开链接却有四次? 这是由于在创建链接过程当中,服务端在收到客户但创建链接请求的 SYN 报文后,会把 ACKSYN 放在一个报文里发送给客户端。 而关闭链接时,服务端收到客户端的 FIN 报文,只是表示客户端再也不发送数据了,可是还能接收数据,并且这会儿服务端可能还有数据没有发送完,不能立刻发送 FIN 报文,只能先发送 ACK 报文,先响应客户端,在确认本身这边全部数据发送完毕之后,才会发送 FIN。 因此,在断开链接时,服务器的 ACKFIN 通常都会单独发送,这就致使了断开链接比请求链接多了一次发送操做。

HTTP 定义

一旦端对端成功创建起了 TCP 链接,下一步就要开始发送正式的 HTTP 请求了。流淌在 TCP Connect 通道里的 HTTP 只负责传输数据包,并无链接的概念,所以 HTTP 也被叫作“无状态协议”。

HTTP 协议是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,它一般运行在 TCP 之上,经过浏览器和服务器进行数据交互,进行超文本(文本、图片、视频等)传输的规定。也就是说,HTTP 协议规定了超文本传输所要遵照的规则。

  1. HTTP 协议是无状态的。这意味着客户端和服务端之间没法知晓当前对方的状态信息,HTTP 请求自己是不带有任何状态存储的。但实际状况下,客户端和服务端必然须要状态的认证和交互,因此就引入了 Cookie, 用于存储当前浏览器的一些状态信息,每次经过独立的 HTTP 请求进行收发,从而解决这个问题。
  2. HTTP 请求互相独立HTTP 互相之间都是一个独立的个体请求,在客户端请求网页时多数状况下并非一次请求就能成功的,服务端首先是响应 HTML 页面,而后浏览器收到响应以后发现页面还引用了其余的资源,例如,CSS,JS文件,图片等等,还会自动发送 HTTP 请求获取这些须要的资源。
  3. HTTP 协议基于 TCP 协议HTTP 协议目的是规定客户端和服务端数据传输的格式和数据交互行为,并不负责数据传输的细节,底层是基于 TCP 实现的。如今使用的版本当中是默认持久链接的,也就是屡次 HTTP 请求使用一个 TCP 链接。

注意:HTTP 请求和 TCP 链接是不同的,HTTP 是在 TCP 链接创建的基础上而发起的传输请求,在同一个 TCP 链接通道下,能够发送多个 HTTP 请求,举个例子的话就是高速公路和车子的关系。

HTTP 发展历史

HTTP 0.9 版本

  • 只有一个 GET 命令。
  • 没有请求头和响应头来描述传输相关的数据信息。
  • 服务器发送完数据后,直接关闭 TCP 链接,不支持 TCP 持久化链接。

HTTP 1.0 版本

  • 增长了不少命令,HEADPOSTPUTDELETE 等。
  • 增设了 status code 状态码和 header 请求头和响应头。
  • 增长了多字符集支持、多部分发送、权限、缓存等。
  • 可经过开启 Connection: keep-alive 来指定使用 TCP 长链接

HTTP 1.1 (目前广泛使用)

  • 默认支持持久链接
  • 默认支持长链接(PersistentConnection),即默认开启 Connection: keep-alive
  • 支持请求的流水线(Pipelining)处理,即在一个 TCP 链接上能够传送多个 HTTP 请求和响应。
  • 增长了 host 请求头字段,经过对 host 解析,就可以容许在同一台物理服务器上运行多个软件服务,极大提升了服务器的使用率。目前的 nginx 反向代理就是根据 HTTP 请求头中的 host 来分辨不一样的请求,从而将这些请求代理到同一台服务器不一样的软件服务上。

HTTP 2.0

  • HTTP1.x 的解析是基于文本,存在解析上的缺陷;而 HTTP2.0 直接使用二进制的解析方式来替代 HTTP 1.X 的字符串解析,更为高效和健壮。
  • HTTP2.0 全部数据以“帧”的方式进行传输,所以同一个链接中发送的多个请求再也不须要按照顺序进行返回处理,能够达到并行的数据传输。
  • HTTP2.0 压缩头信息进行传输数据量的优化。HTTP1.x 的请求头带有大量信息,并且每次都要重复发送,HTTP2.0 使用 encoder 来减小须要传输的请求头大小,通信双方各自缓存一份 header fields 表,既避免了重复的传输,又减少了传输信息的大小。
  • HTTP2.0 新增了 server push(服务端推送) 的概念,服务端能够主动发起一些数据推送。好比,服务端在接收到浏览器发来的 HTML 请求的同时,能够主动推送相关的资源文件(js/css)给客户端,并行发送,提升网页的传输和渲染效率。
  • 目前若是要使用 HTTP2 须要首先使用 HTTPS 在这基础上,才能使用 HTTP2

HTTP 2.0 相比于 HTTP 1 最直观的图片加载性能提高,能够看 HTTP 2 性能提高的官方演示

HTTPS

咱们常常会在有些网页上看到悬浮的弹窗或者广告,有的时候甚至会在本身编写的上线网页上也看到这些垃圾广告,然而开发者明明没有写过这些东西,但是这种垃圾信息是怎么上去的呢? 究其根本缘由就在于各类代理服务,当咱们从客户端发起一个 HTTP 请求,并非直接就能传递到目标服务器的,期间会通过层层的代理服务,咱们经常使用的 nginx ,以及在 DNS 解析过程当中要通过的宽带运营商,都是一种代理服务。 因为 HTTP 时使用明文字符串来传递数据的,那么这些数据就能很轻易地被中间服务读取甚至篡改,那么中间服务拿到了原始的 HTML 数据,想插入点小广告进去天然不是难事。

HTTPS 是为了解决 HTTP 明文传输而出现的安全问题而出现的一种解决机制 ———— 对 HTTP 请求中的信息进行加密以后传输,从而有效地防止了中间代理服务截获或篡改信息的问题。

HTTPS 其实就是一个安全增强版的 HTTP 1.1 ,有几点须要注意的是:

  1. HTTPS 协议须要到 CA 申请证书,通常免费证书不多,须要交费
  2. HTTP 协议运行在 TCP 之上,全部传输的内容都是明文,HTTPS 运行在 SSL/TLS 之上,SSL/TLS 运行在 TCP 之上,全部传输的内容都通过加密的。
  3. HTTPHTTPS 使用的是彻底不一样的链接方式,用的端口也不同,前者是 80,后者是 443
  4. HTTPS 能够有效的防止运营商劫持,解决了防劫持的一个大问题。

HTTP 的报文组成

HTTP 是以请求和响应的形式存在的,因为发起方主动发起一个 HTTP 请求,而后由响应方回应,双方按照必定的报文格式进行数据的互传,一个完整的 HTTP 报文一般由 首行首部主体 构成。

首行

首行并不属于 Http Headers ,它包含了:

  1. HTTP MethodGETPOSTPUTDELETE 等 ),不一样的 HTTP Method 有不一样的语意。

    HTTP Method 对应予以
    GET 通常用于获取服务器资源
    POST 通常用于传输实体主体
    PUT 通常用于传输文件
    DELETE 用于删除文件
    HEAD 用于获取报文首部,不返回报文主体
    OPTIONS 用于预检请求中,询问请求URI资源支持的方法

    HTTP Method 只是 HTTP 协议推崇的一种规范,就像 ESLint,你能够选择遵循,也能够选择不遵循,它们所做的事情实质上没有差异,只是语义化更明确。

  2. URL请求资源的地址,这个地址只会包含请求的路由地址。

  3. 协议的版本HTTP 1.0 / HTTP 1.1 / HTTP 2

  4. HTTP 返回状态码(响应报文首行包含)

    HTTP 定义了40个标准状态代码,可用于传递客户端请求的结果,状态代码分为如下五类,关于各个分段下的返回状态码信息能够参考 HTTP 响应码

这边须要注意的一点是,一个好的 HTTP 应用服务应该是有完善的 HTTP status code 的返回信息的,即访问者单从 HTTP status > code 上就能得知当前 HTTP 请求的状态信息。 而目前咱们大部分的开发模式下的 HTTP 返回码,只有 200500。服务端的同窗会先把 200 返回过来,而后再告诉你出了什么 “没登陆” / “没认证” / “没权限” 这一类的问题。 业界也有一句戏言:又不是不能用,其实这种开发方式是不正确的,无论从代码的维护性仍是我的自身发展角度,咱们都须要> 尽可能避免这种问题。

HTTP 头信息

HTTP 头信息,即 HTTP Header,首行换行后的信息都是 HTTP HeaderHTTP header 里通常存放了客户端和服务端之间交互的非业务信息,例如:本次请求的数据类型、请求日期、字符集支持、自定义头部字段、一些通讯凭证和缓存支持等。 HTTP Header 完整字段列表:传送门

主体

主体,即 HTTP bodyHTTP Header 信息和主体信息间以一个空行 + 一个换行来区分。HTTP body 里通常会放置请求的一些具体业务信息

HTTP 数据协商

在 HTTP 协议中,数据协商是这样一种机制,客户端经过请求头告知服务端本次请求但愿获取的数据格式、展示形式、以及数据的压缩方式等。常见的数据协商例如,文档使用的天然语言,图片的格式,或者内容编码形式。 服务端能够对请求头中携带的数据协商字段进行解析,而后在返回客户端数据的时候,也会用相对字段来通知客户端:本次返回的数据格式、压缩方式等信息。这样浏览器就可使用特定的解析方式,来对这些资源进行解析、处理和渲染。

下面简单列举一些经常使用的数据协商字段,完整的数据协商信息传送门

  • Accept 请求头字段,指按期望得到的数据类型
  • Accept-Encoding 请求头字段,指按期望得到的数据须要以什么样的编码方式进行传输,经常使用于限制服务端对数据的压缩方式,常见的 JS 文件包大小优化的 GZIP 压缩,就使用了这个方法
  • Accept-Language 请求头字段,指按期望得到的数据语言类型:中文、英语、仍是其余语言,这个头信息字段,通常是浏览器自动加上的
  • User-Agent 请求头字段,指定本次请求的浏览器信息,服务端可根据此信息选择不一样兼容性的页面返回给用户,或者是作用户使用浏览器信息、操做系统等数据的统计
  • Content-Type 响应头字段,请求头里的 Accept 字段可能会指定好几种能够接受的数据格式,服务端最终会返回一种数据格式给客户端
  • Content-Encoding 响应头字段,对应 Accept-Encoding
  • Content-Language 响应头字段,对应 Accept-Language

HTTP 长链接

每个 HTTP 请求都须要在 TCP 链接通道里才能完成发送和接受。在 HTTP 协议的早期版本里,每一条 HTTP 请求发送以前,都会建立一条新的 TCP 链接通道,在这个请求完成之后,该条 TCP 通道就会自动关闭。 这样带来的问题就是,单条 TCP 链接没有办法复用,形成很大的新能浪费。好在这一问题随着 HTTP 协议的逐步完善已经获得解决。

HTTP 1.0 中引入的 Connection 头字段,容许对其设置 Keep-Alive 或者是 Close 来决定是否须要复用 TCP 链接,仍是说在一次请求完成以后直接关闭。而在 HTTP 1.1 中默认双端都会默认开启这个字段,即默认支持 HTTP 的长链接。

须要注意的是:Connection: Keep-Alive 须要双端同时开启才能启动 HTTP 长链接,若是任何一段手动设置 ConnectionClose,长链接都没法位置,由于 TCP 链接的创建和持久保持是一个双端交互的过程。

那么咱们在本地如何看到 TCP 的链接 ID 呢,能够打开 Chrome 的调试工具来查看:

图上能够看到有不一样的 Connection ID,这就表明着本次请求其实是开启了一个新的 TCP 链接,最下面的请求的 Connection ID 都是相同的,表明着多个 HTTP 请求复用了同一个 TCP 链接。

Chrome 浏览器所可以支持的最大并发 TCP 链接数是 6个,而且在 HTTP 2.0 如下的 HTTP 版本中,请求是阻塞的。也就是说,一旦六个链接开满,前面的请求未完成,那么后续请求就会被阻塞,直到前面的请求返回,后续才能继续发送。

HTTP 缓存

虽然 HTTP 缓存不是必须的,但重用缓存的资源一般是必要的。然而常见的 HTTP 缓存只能存储 GET 响应,对于其余类型的响应则无能为力。缓存的关键主要包括 request method 和目标 URI(通常只有 GET 请求才会被缓存)。

缓存读取策略

前端环境下的文件缓存,分为几个不一样的位置。当咱们打开 Chrome 控制台,查看 Network 下每条请求记录的 size 选项,会发现很是丰富的来源信息。

对于前端浏览器环境来讲,缓存读取位置是由前后顺序的,顺序分别是(由上到下寻找,找到即返回;找不到则继续)

  • Service Worker
  • Memory Cache
  • Disk Cache
  • 网络请求

Service Worker

Service Worker 的缓存与浏览器其余内建的缓存机制不一样,它可让咱们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,而且缓存是持续性的。

  • 浏览器优先查找。
  • 持久存储。
  • 能够更加灵活地控制存储的内容,能够选择缓存哪些文件、定义缓存文件的路由匹配规则等。
  • 能够从 Chrome 的 F12 中,Application -> Cache Storage 查看。

Memory Cache

  • memory cache 是内存中的缓存存储。
  • 读取速度快。
  • 存储空间较小。
  • 存储时间短,当浏览器的 tab 页被关闭,内存资源即被释放。
  • 若是明确指定了 Cache-Controlno-store,浏览器则不会使用 memory-cache

Disk Cache

  • Disk Cache 是硬盘中的缓存存储。
  • 读取速度慢于 Memory Cache ,快于网络请求。
  • 存储空间较大。
  • 持久存储。
  • Disk Cache 严格依照 HTTP 头信息中的字段来判断资源是否可缓存、是否要认证等。
  • 常常听到的“强制缓存”,“对比缓存”,以及 Cache-Control 等,归于此类。

网络请求

若是一个请求的资源文件均未命中上述缓存策略,那么就会发起网络请求。浏览器拿到资源后,会把这个新资源加入缓存。

Cache-Control

HTTP/1.1定义的 Cache-Control 头用来区分对缓存机制的支持状况, 请求头和响应头都支持这个属性。经过它提供的不一样的值来定义缓存策略。须要注意的是,数据变化频率很快的场景并不适合开启 Cache-Control

指令 做用
public 公共缓存:表示该响应能够被任何中间人(好比中间代理、CDN等)缓存。
private 私有缓存:表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。
max-age (单位/秒)设置缓存的过时时间,过时须要从新请求,不然就读取本地缓存,并不实际发送请求
s-maxage (单位/秒)覆盖 max-age,做用同样,只在代理服务器中生效
max-stale (单位/秒)表示即便缓存过时,也使用这个过时缓存
no-store 禁止进行缓存
no-transform 不得对资源进行转换或压缩等操做,Content-Encoding、Content-Range、Content-Type 等 HTTP 头不能由代理修改(有时候资源比较大的状况下,代理服务器可能会自行作压缩处理,这个指令就是为了防止这种状况)。
no-cache 强制确认缓存:即每次使用本地缓存以前,须要请求服务器,查看缓存是否失效,若未过时(注:实际就是返回304),则缓存才使用本地缓存副本。
must-revalidate 缓存验证确认:意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过时的缓存将不被使用
proxy-revalidate 与 must-revalidate 做用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。

缓存校验

在浏览器使用缓存的过程当中,为了配合 Cache-Control 中 no-cache ,咱们还须要一个机制来验证缓存是否有效。好比服务器的资源更新了,客户端须要及时刷新缓存;又或者客户端的资源过了有效期,但服务器上的资源仍是旧的,此时并不须要从新发送。 缓存校验就是用来解决这些问题的,在http 1.1 中,咱们主要关注下 Last-ModifiedETag 这两个字段。

Last-Modified

顾名思义,就是资源的最新一次修改时间。当客户端访问服务端的资源,服务端会将这个 Last-Modified 值返回给客户端,客户端收到以后,下次发送请求就会将服务端返回回来的 Last-Modified 值装在 If-Modified-Since 或者 If-Unmodified-Since 里,发送给服务端进行缓存校验。

这样服务器就能够经过读取 If-Modified-Since (较经常使用)或 If-UnModified-Since 的值,和本地的 Last-Modified 值作对比校验。若是校验发现这两个值是同样的,就表明本次请求的资源文件没有被修改过,那么服务器就会告诉浏览器,资源有效,能够继续使用,不然就须要使用最新的资源。

来看一下下面的两张图:

当请求服务端的 script.js 的脚本资源时,能够看到服务端返回了 Last-Modified,里面记录了该资源最后一次的修改时间

当客户端下次再次发起请求,会携带上这个过时时间给服务端进行验证

来看下服务端的部分代码:

const http = require('http');
const fs = require('fs');
http.createServer((request, response) => {
   const ifModifiedSince = request.headers['If-Modified-Since'];
   const lastModified = 'Web Aug 19 2019 19:01:15 GMT+0800 (China Standard Time)';
    
   if (request.url === '/') {
       const html = fs.readFileSync('test.html', 'utf-8');
       
       response.writeHead(200, {
           'Content-Type': 'text/html'
       });
       response.end(html);
   }
   if (request.url === '/script.js') {
       const js = fs.readFileSync('script.js', 'utf-8');
       let status = 200;
       // 若是读取到的 If-Modified-Since 和 lastModified 相同,则设置头部 304 表示可以使用缓存
       if (ifModifiedSince === lastModified) {
           status = 304;
           response.end('');
       }
       response.writeHead(status, {
           'Content-Type': 'text/javascript',
           'Cache-Control': 'no-cache,max-age=2000',
           'Last-Modified': lastModified
       });
       response.end(js);
   }
});

复制代码

ETag

Etag 的做用本质上和 Last-Modified 差异不大。相比于 Last-Modified 使用最后修改日期来比较资源是否失效的缓存校验策略,ETag 则是经过数据签名来作一个更加严格的缓存验证。

所谓数据签名,其实就是经过对资源内容进行一个惟一的签名标记,一旦资源内容改变,那么签名必将改变,服务端就以此签名做为暗号,来标记缓存的有效性。典型的作法是针对资源内容进行一个 hash 计算,相似于 webpack 打包线上资源所加的 hash 标识

Last-Modified 对应 If-Modified-Since 相同,ETag 也会对应 If-Match 或者 If-None-MatchIf-None-Match 比较经常使用),若是先后的签名相同,则不须要返回新的资源内容。

缓存校验的合理使用

Last-ModifiedETag 只是给服务端提供了一个控制缓存有效期的手段,并无任何强制缓存的做用,最终决定是否使用缓存、仍是使用新的资源文件,仍是须要靠服务端指定对应的 http code 来决定。 对于保存在服务器上的文件,都有最后修改日期的属性,当使用 Last-Modified 能够利用这个有效的属性进行数据缓存验证;或者在数据库存入一个 updatetime 字段来标识具体的修改日期,从而判断缓存是否有效。 具体如何构建一个可以合理使用缓存的服务器,就比较涉及后端知识了,这里不作具体描述。

浏览器的同源策略

浏览器的同源限制:当浏览器访问 URL 地址的协议(schema)/ 端口(port)/ 域名(host),三者中有任何一个与当前的 URL 片断信息不匹配的时候,便存在跨域问题。

当前地址 请求地址 请求是否成功
www.juejin.com:80 www.juejin.com:80 跨域(协议不一样)
www.juejin.com:80 www.juejin.cn:80 跨域(域名不一样)
www.juejin.com:80 www.juejin.com:90 跨域(端口不一样)

对于跨域的几点须要明确:

  1. 跨域,是浏览器提供的一种保护手段,服务端是不存在跨域这一说的。这也就是为何如今先后端分离的开发模式下,前端比较依赖 webpack-dev-server 启动代理服务来中转和代理后台接口的缘由,由于两个服务器之间相互通讯是没有跨域障碍的。
  2. 跨域,是对于 XMLHttpRequest 来讲的,浏览器获取不一样源服务器下的静态资源,是没有跨域限制的,这也是 JSONP 跨域请求得以实现的本质。
  3. 不一样于 XMLHttpRequest 的是,经过 src 属性加载的脚本资源,浏览器限制了 Javascript 的权限,使其不能读写、返回内容
  4. 对于浏览器来讲,除了 DOM 、Cookie、XMLHttpRequest 会收到同源策略限制之外,一些常见的插件,好比 Flash、Java Applet 、Silverlight、Google Gears 等也都有本身的控制策略。

当浏览器向不一样域的服务器发送请求时,请求是真能发出去,对方服务端也是真能接收到请求,而且真能给你的浏览器响应,浏览器也真能接收到有效数据。 可是,若是在跨域的状况下、服务端返回数据的响应头里的 Access-Control-Allow-Origin 字段,没有把当前域名列进白名单,那么浏览器会把服务端返回的数据给藏起来,不告诉你,而后给你抛个 Access-Control-Allow-Origin 的错误。

至于为何资源文件不受同源策略限制呢?能够试想一下,若是资源文件也被限制跨域,那么如今大量使用的 CDN 缓存策略基本就没办法用了。并且如今不少网站的资源文件,都会放到云服务器的 OSS 上,OSS 资源对应的 url 地址确定是不一样域的,那这些资源也不能使用了。

Access-Control-Allow-Origin

Access-Control-Allow-Origin 标识了服务器容许的跨域白名单,它有如下几种设置方法:

  1. 直接设置 * 通配符,简单粗暴,可是这么作等于把服务器的全部接口资源对外彻底暴露,是不安全的。
  2. 设置制定域,好比 Access-Control-Allow-Origin: https://www.baidu.com ,这样只会容许指定域的请求进行跨域访问。
  3. 由后端动态设置。Access-Control-Allow-Origin 限制只能写一个白名单,可是当咱们有多个域都须要跨域请求怎么呢?这个时候,这时能够由服务端本身维护一套白名单列表,在请求进来的时候对请求的源 host 进行白名单比对,若是在白名单中,就将这个 Access-Control-Allow-Origin 动态设置上去,而后返回响应。

CORS 的预请求

若是咱们像上面同样,只设置的 Access-Control-Allow-Origin 白名单,是否就能够彻底畅通无阻地进行跨域了呢?并非。 就算对端开启了域名白名单认证,然鹅有一些操做仍然是须要进一步认证的,这种进一步的认证操做,就是 CORS 预请求

预请求触发过程

浏览器预请求的触发条件,是判断本次请求是否属于一个简单请求。 若是本次请求属于一个复杂请求,那么在发送正式的跨域请求以前,浏览器会先准备一个名为 OPTIONSHTTP Method ,做为预请求发送。 在服务器经过预请求后,下面浏览器才会发生正式的数据请求。整个请求过程实际上是发生了两次请求:一个预检请求,以及后续的实际数据请求。

简单请求

  1. 请求方式只能是 GET POST HEAD
  2. 请求头字段只容许:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
  3. Content-Type 的值仅限于:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  4. XMLHttpRequestUpload 对象均没有注册任何事件监听器(了解就好)。
  5. 请求中没有使用 ReadableStream 对象(了解就好)。

复杂请求

除了简单请求里定义的,都是复杂请求,通通须要预请求。

预请求的验证

那么怎样使预检请求成功认证呢?仍是须要服务端继续帮忙设置请求头的白名单:

  1. Access-Control-Allow-Headers,设置容许的额外请求头字段。
  2. Access-Control-Allow-Methods,设置容许的额外请求方法。
  3. Access-Control-Max-Age (单位/秒),指定了预请求的结果可以被缓存多久,在这个时间范围内,再次发送跨域请求不会被预检。

更多、更具体的跨域限制策略能够点击这里查看更多

HTTP 性能优化方案

  1. 合理使用 HTTP 的缓存策略,避免同一资源屡次请求服务端而致使的额外性能开销
  2. 尽可能使用 HTTP 长链接,避免每次重建 TCP 链接带来的时间损耗
  3. 尽可能使用 HTTPS 来保证网络传输的安全性。
  4. 可使用 HTTP2 来大幅提升数据传输的效率,使用 server push 开启 HTTP2 的服务端推送功能
  5. 客户端开启 Accept-Encoding 压缩方式的支持,服务端传输压缩后的文件,减小传输数据的大小
相关文章
相关标签/搜索