关于HTTP--前端需要了解的知识

296 阅读17分钟

写这篇文章的初衷

其实作为前端来说,HTTP就是我们的一个工具,操作他的机会不多,更多的是在做一些项目优化的时候会去用到他,例如设置一些请求头,例如DNS,缓存,例如压缩。刚开始的时候学习他只是为了面试应付,很多概念性的东西都是硬背,往往只在面试前再啃一遍。后面我大佬说了我一个问题,现在业务代码已经难不倒你了,那关于前端生态你了解多少,浏览器,网络,部署等等,往往你的知识储备决定了你的生涯上限,这也是为什么会有30岁程序员的一个分水岭一说。趁着有时间,就系统的梳理一遍 更好的去理解一下网络层的协议概念吧。希望能帮助自己更好的去记忆它。

简单理解一下网络传输的各个节点

  1. 实体层:解决物理连接通过光缆
  2. 链路层:网卡与网卡之间的连接(Mac网卡地址)
  3. 网络层:通过ip地址找到对应计算机。
  4. 传输层:主要处理端口与端口之间的连接。(每个应用程序监听不同的端口)
  5. 应用层:数据传输到这里如果应用程序是网页就走HTTP协议、如果是文件客户端就走FTP协议。如果是邮箱客户端就SMTP协议

讲一下传输层TCP(三次握手四次挥手)

报文头部标志

  1. ACK: 标识确认序号是否有效
  2. PSH: 用来提示接收端应用程序立刻将数据从tcp缓冲区读走
  3. RST: 要求重新建立连接. 我们把含有RST标识的报文称为复位报文段
  4. SYN: 请求建立连接. 我们把含有SYN标识的报文称为同步报文段
  5. FIN: 通知对端, 本端即将关闭. 我们把含有FIN标识的报文称为结束报文段

三次握手

由客户端发起:

  1. 客户端发送一个带有SYN码(请求连接)报文,TCP会找到对应的服务端,此时服务端接收到这个请求连接的报文后,进入一个LISTEN状态
  2. 服务端也发送一个SYN码(请求连接)+ACK码(确认序号),找到对应的客户端请求连接
  3. 客户端得到这个请求后,同意建立请求,发送ACK码告诉服务端,然后双方达成数据传输的状态 白话版:
  4. 客户端想要和服务端发生点什么,就发微信问服务端可以吗
  5. 服务端收到这封信后就回微信,我方便啊,你还在吗
  6. 客户端收到这个之后就回那现在来吧 从此他们两个就达成了某种可以相互沟通的状态可以自由发生一些事了

四次挥手

由客户端发起:

  1. 客户端发起FIN码报文,然后进入FIN-WAIT状态
  2. 服务端收到FIN码后进入CLOSE-WAIT状态,如果还有数据需要发送则继续发送,发送完了之后,发送FIN码给客户端,并且处于半关闭状态
  3. 客户端收到FIN码后确定服务端不会再有东西发过来,此时发送确认带ACK码报文确认关闭,并在2MSL后关闭连接
  4. 服务端拿到确认报文后立即关闭连接 白话版:
  5. 客户端发微信给服务端,说分手
  6. 服务端收到后,看一下还有没有礼物没做好的,有的话做好了送给客户端,等都送完了,告诉客户端,好的我知道你要分手了,此时服务端处于半单身状态
  7. 客户端收完所有的礼物后,并且收到服务端说的我知道你要分手了,这时候再发一条消息给服务员端,我一定要和你分,并且过了一会单方面发朋友圈说宣布单身了
  8. 服务端收到客户端这么决绝的消息后,也发朋友圈宣布单身,此后两人再不联系

什么是Http

Http是一个互联网的基础协议,就相当于我们前端开发和和后台服务器交互的一个约定。每个版本都有不同的升级,不同的版本理解可以更好的理解我们和服务器交互的具体行为,方便我们去做问题定位和优化方向考虑

http0.9

基本概念

客户端只有一个get命令请求数据,并且服务端只能回应HTML格式的字符串,不能回应别的格式,服务端发送完数据就关闭TCP连接

http1.0

基于0.9新增功能

  1. 任何格式的内容都可以发送,不仅可以传输文字,还能传输图像,视频,二进制文件
  2. 除了GET命令,还引入了POST命令和HEAD命令,丰富了浏览器与服务器的互动手段
  3. 新增了状态码(status code)、多字符集支持、多部分发送(multi-part type)、权限(authorization)、缓存(cache)、内容编码(content encoding)等

缺点

HTTP/1.0 版的主要缺点是,每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接。

TCP连接的新建成本很高,因为需要客户端和服务器三次握手,并且开始时发送速率较慢(slow start)。所以,HTTP 1.0版本的性能比较差。随着网页加载的外部资源越来越多,这个问题就愈发突出了。

为了解决这个问题,有些浏览器在请求时,用了一个非标准的Connection字段。

Connection: keep-alive

这个字段要求服务器不要关闭TCP连接,以便其他请求复用。服务器同样回应这个字段。

Connection: keep-alive

一个可以复用的TCP连接就建立了,直到客户端或服务器主动关闭连接。但是,这不是标准字段,不同实现的行为可能不一致,因此不是根本的解决办法。

请求格式

GET / HTTP/1.0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5)
Accept: */*

第一行是请求命令,必须在尾部加协议版本,下面的每一行就是对应的多行请求头的信息,用于描述客户端的情况

回应格式

HTTP/1.0 200 OK 
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84

<html>
  <body>Hello World</body>
</html>

回应的格式是"头信息 + 一个空行(\r\n) + 数据"。其中,第一行是"协议版本 + 状态码(status code) + 状态描述"。

content-type

关于字符的编码,1.0版规定,头信息必须是 ASCII 码,后面的数据可以是任何格式。因此,服务器回应的时候,必须告诉客户端,数据是什么格式,这就是Content-Type字段的作用。

http1.1

基于1.0新增功能

  1. 引入了持久性连接,也就是不用再像1.0那样设置Connection: keep-alive来保持每次请求相同的服务器不用进行TCP三次握手四次挥手连接, 一段时间不进行请求后,会自动断开连接,但是标准的做法是要再最后的一个请求声明Connection:close来断开连接
  2. 管道:原本在1.0中,一个TCP连接只能是发送一个请求,1.1升级之后一个TCP连接可以发送多个请求。(刚开始的时候我就觉得这不就是持久性连接吗,但是不是的,管道的意思是我发送一个请求包,但是这个请求包里面可能包含多个请求,这样就减少了上发的压力,就好像客户端要从服务端拿AB两个东西,持久化指的是客户端和服务端不断开连接,而管道指的是客户端一次性告诉服务端请给我A和B)
  3. content-length,在1.0中这个是非必须的,因为一个tcp关闭连接就意味着接收的东西结束了。content-length语义上理解为内容长度,实际上呢这个是为了配合管道多工来使用的。参考管道的理解,可以知道我们一个请求包可以包含多个请求,所以当我们发送一个ABC的请求包时,content-length就相当于是一个分割符,当内容长度和接收到的长度一致时,证明下次传过来的东西为下一个请求的东西了。
  4. 分块传输编码:其实有时候我们未必知道一个数据的内容长度,所以content-length在1.1中也不是必须的,但是针对管道的特性要怎么去做请求内容区分呢?Transfer-Encoding: chunked设置了这个头部信息,则意味着接下来的传输内容是未知长度的,一块一块传过来的,每一块都有一个块长度,当接收到一个块长度为0的,那么意味着这个请求的东西结束了
  5. 新增了很多动态词方法PUTPATCHHEAD、 OPTIONSDELETE

不足

其实上文中很多东西都是基于持久化连接和管道进行描述,包括分块传输编码也好,content-length也好,都是为管道传输服务的,但是你有没有发现,他们的实现都是依赖于按顺序来区分多个请求的数据,也就是你一个TCP请求去拿ABC这三个东西,他一定是按照ABC的顺序返回的,这样你才能确定ABC的下行包对应的是谁的东西,那这样的话是不是就意味着如果我A的东西很多,那么B和C将会等的很蛋疼,这个现象叫做队头阻塞

针对不足的优化

为了避免队头阻塞,我们常用的方法是减少请求数,多开TCP连接

  1. 合并脚本和样式表(减少请求)
  2. 将图片嵌入css代码(减少请求)
  3. 精灵图(减少请求)
  4. 域名分片也就是CDN(多开TCP连接)

但是如果http如果做的足够好的话,我们可以减少甚至不做这些优化

http2

SPDY协议

2009年,谷歌公开了自行研发的 SPDY 协议,主要解决 HTTP/1.1 效率不高的问题。

这个协议在Chrome浏览器上证明可行以后,就被当作 HTTP/2 的基础,主要特性都在 HTTP/2 之中得到继承。

2015年,HTTP/2 发布。它不叫 HTTP/2.0,是因为标准委员会不打算再发布子版本了,下一个新版本将是 HTTP/3。

基于1.1新增功能

  1. 二进制协议:

    在http1.1中,头部信息肯定是文本(ASCII编码),数据体可以是文本,也可以是二进制,而http2则是一个测地的二进制协议,头部信息和数据体都是二进制,统称为:头信息帧和数据帧。

    二进制协议的一个好处是,可以定义额外的帧,为将来的高级应用打好基础。如果使用文本去实现这些功能,解析将会变得很麻烦

  2. 多工:

    在一个连接里,客户端和浏览器都可以同时发送多个请求或者回应,而且不用按照顺序一一对应

    在一个TCP连接里面,同时收到了AB两个请求,于是先回应A请求,结果发现A特别的耗时,于是发送A请求已经处理好的一部分,接着回应B请求,完成后继续发送A的部分

  3. 数据流

    在1.1中是通过按顺序发送和接收,通过content-length或者数据包大小为0来做请求数据的区分,那多工之后顺序已经是不一定的了,怎么去做区分呢?

    http2将每个请求或者回应的所有数据包称为一个数据流。每个数据流都有一个独一无二的编码。数据包发送的时候都必须标记数据流ID,用于区分他属于哪个数据流。另外还规定,客户端发车的数据流ID一律为奇数,服务端发出的,ID一律为偶数。

    数据流发送到一半的时候,客户端和服务器都可以发送信号RST_STREAM取消这个数据流。1.1取消数据流的唯一方法就是关闭TCP连接,也就是说HTTP2可以关闭单次请求,并且保持TCP连接不会被断开

    客户端还可以指定数据流的优先级,优先级越高服务器就越早回应

  4. 头部信息压缩

    Http协议不带有状态,每次请求都会附上所有的信息,所以很多字段都是重复的,例如cookieUser Agent,一模一样的内容每次都会附带,浪费很多带宽也影响速度

    Http2对这点做了优化,引入头部压缩机制。一方面,头部信息使用gzip或者compress压缩后再发送;

    另一方面,客户端和服务端同时维护一张表头信息表,所有字段都会存入这个表,生成一个索引号,也就是发送过一次的信息下次发送就只发送索引号就可以了,提高上下行的速度

5.服务器推送

HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送(server push)。

常见场景是客户端请求一个网页,这个网页里面包含很多静态资源。正常情况下,客户端必须收到网页后,解析HTML源码,发现有静态资源,再发出静态资源请求。其实,服务器可以预期到客户端请求网页后,很可能会再请求静态资源,所以就主动把这些静态资源随着网页一起发给客户端了。

如何快速记住各个版本之间的差异

以外卖配送为例

http0.9

骑手只能配送快餐(HTML)

http1.0

骑手能配送除了快餐之外的其他东西,但是每次点单只能点一个,同一个商家你点了一个烤鱼,一个米饭,这个时候你需要打电话去下两单,如果你确定要和这家商家保持联系,你需要告诉他connetion:keep-alive,打电话的时候先告诉他我先别挂,我还有需求

http1.1

(持久性连接)升级到网络下单了,这个时候你需要下单的时候和商家发起聊天窗口,当你一段时间不给商家发消息的时候,商家默认不需要再服务你了,断开连接

(管道)你同个商家下单了几个外卖(2个米饭,1个烤鱼,1个青菜),商家给你送了1个米饭告诉你个数是2个,你接到第一个外卖ok是米饭,接到第二个数量已经是2了 那ok第二个还是米饭,第三个应该就是烤鱼了,但是烤鱼特别的久,所以你只能等第三个烤鱼,做完了给你送来了,接下来才能给你送青菜(队头阻塞)

(分块编码传输)商家还能不通过告诉你数量,先给你送米饭,你先别管有几个 送完了我会排个人不带任何东西的跑过来告诉你,米饭送完了,接下来就是新的东西了 按顺序,下一个就是烤鱼了

http2

(二进制协议)相当于你和商家原本下单只能通过文本描述,现在你发送了一个摩斯密码就行了,商家会根据这个莫斯密码去解密成你描述的话或者转换成你发送给商家的东西,商家送给你的也是一个摩斯密码,你根据摩斯密码变成你想要的东西

(多工)原本商家只有一个比较傻的员工,你下了什么他就按顺序给你做,现在找了个聪明的。同样的你下了(2个米饭,1个烤鱼,1个青菜)他送完米饭之后发现烤鱼要弄好久,后面你还点了个青菜,那就先给你送青菜

(数据流)由于现在你下单的东西不按顺序发送了,所以你和商家约定好了1代表米饭3代表烤鱼5代表青菜这样商家给你送过来的东西就通过一个246编码告诉你 我送过来的是什么。同时原本你不想要青菜了 你只能和商家说我什么都不要了,然后再重新找商家说我要米饭和烤鱼,现在你可以和商家说我不要青菜了 在青菜这个单上发一个RST_STREAM,这样你就还和商家保持联系,并且商家不会给你送青菜

(头部信息编码表)你点一个烤鱼,不加香菜,这个时候不加香菜作为头部信息传给商家,于是你就标注了一个表格,编码1代表了不加香菜,那你再点一个砂锅粥在头部中带了这个1,那商家就知道了,砂锅粥也不加香菜,这样你和商家之间就不要每次都打一大段备注,只需要写123就知道代表了什么

(服务端主动发起连接)就是商家可以感觉你需要什么,提前给你送过去,看你需不需要

https

HTTPS = HTTP+SSL/TLS

HTTPS的安全基础是SSL的对称加密,也就是只有公钥才能解密私钥加密后的东西,只有私钥能解密公钥加密的东西

流程

image.png

  1. 客户端发送连接请求到服务端
  2. 服务端收到连接请求之后明文返回SSH证书(带有公钥)
  3. 客户端验证证书是否合法(颁发机构信息、公钥、公司信息、域名、有效期...)
  4. 客户端如果验证合法,生成一个随机数,否则抛出警告
  5. 针对生成的随机数使用公钥进行加密,加密后的随机数发送给服务端
  6. 服务端接收到这个随机数使用私钥进行解密,得到随机数

此时客户端手握公钥和随机数 服务端手握私钥和随机数,双方达成了可以对称加密的条件

  1. 客户端接收服务端的数据包时,只有通过随机数和公钥解析出来的,才是明文数据
  2. 服务端接收到的请求包,只有通过随机数和私钥解析出来的,才是未被篡改的明文请求

其实Https防止的就是我们在数据传输过程中是否被截取了包造成信息不安全,以及恶意修改了请求造成的数据不安全。 实现的原理就是服务端有解析公钥加密的唯一私钥,而客户端有生成的随机数+公钥变成解析由随机数和私钥加密的唯一秘钥,也就是在没有私钥和这个随机数的情况下,即使获取了包也无法解析包的内容