http2模块

要学习关于啥是http2,主要还是得带着 :point_down: 几个问题 :grey_question: 来熟悉熟悉该模块:

  1. 什么是http2协议?
  2. http2与之前习得的http1.0有什么区别?
  3. 为什么要使用http2模块?
  4. 如何来使用https模块?

引言

首先,想要学习什么是http2模块,需要先晓得关于http2协议的相关知识点: 超文本传输协议第二版,http2.0主要基于SPDY协议,通过对http头部字段进行数据压缩,对数据传输采用多路复用、增加服务端主动推送措施,来减少网络延迟,提高页面的加载速度,它并没有修改http的应用语义,仍然使用HTTP的请求方法、状态码等规则,主要修改了报文传输格式,引用二进制分帧技术来实现性能的提升!!!

:confused: 这里什么是SPDY?对头部数据是如何压缩的?多路复用又是如何实现的?服务端主动推送是如何实现的?什么是二进制分帧?

SPDY协议

SPeeDY协议,由谷歌开发的基于TCP的会话协议,用以最小化网络延迟,提升网络速度,优化用户的网络体验,对HTTP协议的增强,包括有数据流的多路复用,请求优先级以及http报头压缩,增加服务器启动流等特性! :stars: 也就是说http2.0其实就是由这个SPDY协议发展衍生而来的!!!

:warning: SPDY强制使用

二进制分帧

作为能够突破http1.x标准的性能限制,改进传输性能,实现低延迟何高吞吐量的关键所在!!! 帧(frame)包含有:

  1. 类型Type;
  2. 长度Length;
  3. 标记Flags;
  4. 流标识Stream;
  5. 有效载荷Payload

消息(Message)则包含有一个完整的请求/响应,一个消息由一个或者多个帧组成。 流(Stream)是链接中的一个虚拟通道,可以进行双向数据的传递,每个流都有唯一的整数标识符,一般为了防止两端流ID冲突,客户端发起的流具有奇数ID,服务端发起的流具有偶数ID! 关于二进制分帧的构成与它在http2.0中的一个通讯如下所示:

二进制分帧构成与通讯

:stars: 在二进制分帧层中,http2.0会将所有的传输信息分割称为更小的消息与帧,并对他们采用二进制格式编码并将其封装,新增的二进制分帧层能够保证原有的http现状,而将原本http1.x中的header封装到Headers帧中,而将请求/响应的body封装到Data帧中!过程如下所示: 二进制分帧的封装过程

http2.0关键特性

由于是从SPDY中衍生而来的,因此也“继承”了SPDY的相关特性:

  1. 对http头部字段进行数据压缩
  2. 多路复用/连接共享
  3. 请求优先级
  4. 服务端主动推送

对http头部字段进行数据压缩

http1.x :vs: htt2.0 :每次的请求头中都带有大量信息,而且每次都要进行重复发送!http2.0则使用encoder来减少需要传输的header大小,通讯双方各自缓存一份头部字段表,既避免了重复header的传输,又减少了需要传输的大小! :stars: 对于相同的数据,不再通过每次请求和响应来发送,通讯期间几乎不会改变通用键值对,只需发送一次。

:warning: http2.0所关注的压缩仅仅是header的压缩,与gzip的压缩body完全不冲突,两者配合使用的话,能够达到更好的压缩效果!

关于头部字段静态表的内容 :point_down: 所示:

索引 头部名称 头部值
1 :authority
2 :method GET
3 :method POST
4 :path /
5 :path /index.html
6 :scheme http
7 :scheme https
8 :status 200
9 :status 204
10 :status 206
11 :status 304
12 :status 400
13 :status 404
14 :status 500
15 accept-charset
16 accept-encoding gzip, deflate
17 accept-language
18 accept-ranges
19 accept
20 access-control-allow-origin
21 age
22 allow
23 authorization
24 cache-control
25 content-disposition
26 content-encoding
27 content-language
28 content-length
29 content-location
30 content-range
31 content-type
32 cookie
33 date
34 etag
35 expect
36 expires
37 from
38 host
39 if-match
40 if-modified-since
41 if-none-match
42 if-range
43 if-unmodified-since
44 last-modified
45 link
46 location
47 max-forwards
48 proxy-authenticate
49 proxy-authorization
50 range
51 referer
52 refresh
53 retry-after
54 server
55 set-cookie
56 strict-transport-security
57 transfer-encoding
58 user-agent
59 vary
60 via
61 www-authenticate

http2.0压缩、组合header的过程

多路复用

http1.x :vs: http2.0:在http1.x中,浏览器客户端在同一时间,针对同一域名下的请求 :u6709: 一定数量的限制,超过限制树木的请求将会被阻塞,:point_right: 这也就是为何一些站点会有多个静态的CDN域名的原因之一,而http2.0中的多路复用则优化了这一性能,它允许同时通过单一的http/2连接发起多重的请求-响应消息,得益于二进制分帧机制,每个数据流都可以拆分称为互补以来的帧,而且这些帧可以乱序发送,最后在另一段把他们进行重新组合起来!! :stars: http2.0连接是持久化的,而且客户端与服务器之间也需要一个连接即可,可以承载数十或者数百个流的复用,多路复用意味着来自很多流的数据包能够混合在一起通过同一个连接来进行传输,当达到终点的时候,再根据不同的帧Headers中的流标识符重新连接将不同的数据进行组装!!过程如下 :point_down: 所示:

http2.0多传输数据流 :point_up: 中展示了一个连接上的多个传输数据流:客户端想服务端传输数据帧stream5,而服务端则向客户端乱序传输Stream1以及Stream3!!

http1.x与http2.0在传输数据时的区别

请求优先级

把http消息分为很多独立的帧之后,就可以通过优化这些帧的交错和传输进一步优化性能,每个流都带有一个31byte的优先值:0代表最高优先级,-1表示最低优先级,关于资源的优先级如下: html > css > js > 图片,对应在浏览器中访问的优先级体现如下: 资源请求的优先级

服务端主动推送

服务器可以对一个客户端请求发送多个响应,服务器向客户端推送资源无需客户端明确地请求。并且,服务端推送能把客户端所需要的资源伴随着index.html一起发送到客户端,省去了客户端重复请求的步骤。 正因为没有发起请求,建立连接等操作,所以静态资源通过服务端推送的方式可以极大地提升速度。Server Push 让 http1.x 时代使用内嵌资源的优化手段变得没有意义;如果一个请求是由你的主页发起的,服务器很可能会响应主页内容、logo 以及样式表,因为它知道客户端会用到这些东西,这相当于在一个 HTML 文档内集合了所有的资源。 http2.0服务端推送过程 当服务端需要主动推送某个资源时,便会发送一个 Frame Type 为 PUSH_PROMISE 的 Frame,里面带了 PUSH 需要新建的 Stream ID。意思是告诉客户端:接下来我要用这个 ID 向你发送东西,客户端准备好接着。客户端解析 Frame 时,发现它是一个 PUSH_PROMISE 类型,便会准备接收服务端要推送的流

实现http2.0 :vs: 未实现http2.0

未开启http2.0的个人网站 开启了http2.0的个人网站 :point_up: 的两张图对比了自己个人博客网站基于nginx配置实现了http2.0的一个效果对比图,发现其中一个比较明显的地方:

  1. ConnectionID: 在使用了http2.0了之后,有些资源请求用的是同一个ConnectionID,而一个新的连接将(可能)需要通过所谓要建立TCP握手.这对于性能原因很重要,因为TCP握手会导致相对较大的网络开销.我们正在创建一个新连接,因此获取HTTP响应需要更长的时间,对于看到相同ID的后续时间,将不会产生此开销.也就是说,浏览器不需要执行TCP握手并将重用相同的连接.这里我们说TCP连接仍然是"开放"的.已建立的连接可以更快地获取HTTP响应数据.

  2. 使用了http2.0了之后,protocal协议这里都是展示的h2

http2.0在node.js中的模块

源码中http2导出的方法与对象

一切从http2.createServer()方法出发,其本质是调用的new Http2Server()来创建一个server对象,那么new出一个Http2Server对象的过程是怎样的?Http2Server对象是怎样的一个对象呢?

Http2Server服务对象

继承于net.Server,基于TCP上的一个Server服务对象,然后调用其初始化方法,该方法中接收options中的Http1Incoming与Http1ServerResponse参数,分别来自于http1.x中的InComingMessage以及HttpServerResponse对象,这也就说明了创建出来的Server服务基础信息来自于http1.x红的信息,对其响应信息对象做了一层包装器,接着在其初始化方法中设置newListener以及request监听器,并且一旦有请求request事件发生了,则移除newListener事件监听,也就是这个事件是一次性的,相当于once动作!在new Server()的时候,传递的第二个参数connectionListener,用于监听每一个客户端连接的回调动作!:stars: 在该回调动作中,进行了ServerHttp2Session会话的创建,也就是针对每一个连接进行了一个会话的创建,且针对这个会话对象,设置了stream事件的监听,通过该事件,使得在底层接收到请求,可以监听到这个流的一系列操作!然后再触发session动作,然后当我们在session动作中进行了流的写入的时候,这个时候,就会自动的触发Server的stream事件! :stars: 在Server中设置相关的监听器的同时,:u6709: :one:关键方法:onServerStream,它主要是移除原newListener,并设置stream监听动作,当stream事件发生时,触发该方法,关于该方法的定义如下: function onServerStream(ServerRequest, ServerResponse, stream, headers, flags, rawHeaders) 针对Http2Session对象所发出的动作,对其请求以及响应进行一个包装,分别包装称为Http2ServerRequest以及Http2ServerResponse对象,然后出发request事件,并将这两个参数抛出去!

:stars: 这里分析了关于Http2Server服务对象的一个工作过程,:point_down: 整理出关于这个对象它的一个组成结构

Http2Server

:stars: 通过 :point_up: 的组成结构图我们可以看出这个Http2Server对象就是主要提供服务监听的作用!

:point_up: 提及到的几个对象:ServerHttp2SessionHttp2ServerRequestHttp2ServerResponse对象,都是用来做甚的呢?

Http2ServerRequest

  1. 模块组成 Http2ServerRequest :point_up: 由此可见,这里的Http2ServerRequest对象可以说是客户端请求的一个包装器,包装了客户端请求的所有信息
  2. 定义与使用 由createServer方法创建,并由request监听器作为第一个参数响应返回执行,可用来访问请求资源的状态、请求头、请求的数据等等
  3. 源码实现 继承于Readable可读流对象,其核心成员变量为stream,实现对流的监听,将所有相关的属性都放到kStream唯一属性变量中,以便于后续过程中使用

Http2ServerResponse

  1. 模块组成 Http2ServerResponse

  2. 定义与使用

    服务器响应对象描述,一般由 :point_up:Http2Server内部来创建的,且可借由request回调方法第二个参数来回调出去,代表着对服务端响应资源的监听,可监听到请求源对象、响应头、响应内容等资源,也可直接做响应动作,比如writeHeader、write等操作!

  3. 源码实现

    继承于Stream对象,其构造方法以一流对象作为参数,来进行包裹,且其req属性变量以这个流来作为参照,拥有关键属性kStream、kHeader、kResponse等关键对象属性! :warning: 这里的kStream变量,指向的是ServerHttp2Stream,所有的与流相关的操作,都借由它来实现的!!

  4. 关键方法与属性 :one: writeHead: 往请求资源中写入响应头信息,一般可以是响应code、响应信息、响应头三者一起写,也可以是仅写响应code信息!:warning: 该方法必须是在response.write/end方法调用之前调用,代表告知流要以什么解析协议来解析所传输的资源!

    const body = 'hello world';
    response.writeHead(200, {
    'Content-Length': Buffer.byteLength(body),
    'Content-Type': 'text/plain; charset=utf-8'
    });
    

    :point_right: 关于该方法的一个伪代码描述如 :point_down:

根据传入的参数进行header的组装,并存储在当前对象中的kHeader变量中 :point_right: 调用其成员属性kStreamresponse方法,由这个response方法来发起响应动作 :point_right: 调用底层AsyncWrap对象的response方法完成最终的写入header动作!

:confused: 关于 :point_up: 中的kStream对象,究竟是怎样的一个对象呢?通过调试我们可以得知,其实这里的kStream对象是一个ServerHttp2Stream,那么关于这个ServerHttp2Stream它又是怎样的一个对象呢?具体可以看下方的介绍

:two: write/end: 与http1.x模块中的write/end方法相类似,定义如下: http2中Http2ServerResponse的write与end方法

:stars: 不管是write或者是end方法,其底层各自调用的都是stream.Duplex的write/end方法,且都在调用对应方法之前,如果还没有设置header的话,则根据待写入的数据格式,来生成对应的header信息

:three: createPushResponse: 往请求资源来创建推送资源信息,实现向请求客户端资源的主动推送功能,然后当请求的资源如果被关闭的话,那么这时主动推送将抛出ERR_HTTP2_INVALID_STREAM异常,而且这里第二个参数callback都会在推送动作完成时回调到!这里主要是从请求原始会话session中捞出源会话,借助于ServerHttp2Stream对象来完成这个push动作! :warning: 这里所推送的资源是headers类型的资源!一般仅需要在对应的header中指明对应的:path属性代表对应的资源文件即可,也就是说,我们可以将这个资源通过这个headers来实现服务端的推送,但是,:u6709: 一个关键点的地方需要注意的是,因为实现了服务端的推送,因此,假如推送的是与当前资源同源的情况下的话,需要针对这个客户端的请求进行资源的响应处理操作,否则就会出现忽略这个资源的响应处理动作!!

const http = require('node:http2');
const fs = require('node:fs');
const server = http.createSecureServer({
  key: fs.readFileSync('localhost-privkey.pem'),
  cert: fs.readFileSync('localhost-cert.pem')
});
server.on('stream', (stream, headers, flags, rowsHeader) => {
  stream.respond({
    'content-type': 'text/html',
    ':status': 200
  });
  stream.pushStream({ ':path': 'https://lib.baomitu.com/zepto/0.8/zepto.min.js' }, (error, pushStream) => {
    if (error) throw error;
    pushStream.respond({
      'content-type': 'text/javascript',
      ':status': 200
    })
    pushStream.end(`alert('you win')`)
  })
  stream.end('<script src="https://lib.baomitu.com/zepto/0.8/zepto.min.js"><h1>Hello World</h1>');
});
server.listen(8443, () => {
  console.info('server listen 8443 port');
});

服务端推送结果 :stars::point_up: 的访问输出结果,我们可以看出客户端正常请求并无请求额外的js资源,但是,我们可以在响应的内容中,追加一js标签,实现服务端推送的机制,其实,这里与普通的html没有什么太多的区别,只不过是通过追加的标签,来让浏览器识别到对应的标签之后,自动发起资源的加载!!!

ServerHttp2Session

  1. 模块组成

    继承于Http2Session对象,主要是利用继承而来的属性+api来实现会话的管理的!

  2. 定义与使用 :point_up: 在分析Http2Server的时候,就提及到这个ServerHttp2Session对象,那么这个对象 :u6709: 怎样的一个作用呢? :stars: 代表着一个连接请求会话,每次有新的一个连接请求过来时,都在Http2Server中创建一个session会话,代表每一个客户端连接会话,在会话中所产生的基于流的操作,都将会触发server的stream动作(本质上是调用的emit('stream')动作)
  3. 源码实现 在createServer中的connectListener创建一session :point_up: 在每一个客户端连接监听中,都创建了一个会话session,并设置相应的监听器(stream、error、priority),并触发session事件,也就是Http2Server被触发的原因

Http2Session

  1. 模块组成 Http2Session
  2. 定义与使用 const session = new ServerHttp2Session(options, socket, this); 代表http2的客户端与服务器之间的活动通信会话!每个Http2Session实例都表现出不同的行为,这取决于它是作为服务器运行还是客户端运行的,这可凭借它的type属性来确定! :stars: 每一个Http2Session实例都与其对应的socket对象关联,因为它正是以这个socket对象来作为参数来创建的对象,所有的基于该会话上的操作,都是用socket来完成的,一旦创建时候,就触发对应的connect方法,当销毁时,也跟着一通销毁。 :warning: 由于spdy协议特定序列化和处理要求,不建议往这个socket对象上来写入数据,因为这会导致不确定的因素发生!
  3. 源码实现
  4. 关键方法与属性 :one: goaway(code[, lastStreamID[, opaqueData]]): 启动连接关闭动作,GOAWAY允许端点正常停止接收新的流,同时仍然完成对先前建立的流的处理,这可以实现管理操作,比如服务器维护!

ServerHttp2Stream

  1. 模块组成
  2. 定义与使用 :stars: 继承于Http2Stream
  3. 源码实现
  4. 关键方法与属性

Http2Stream

  1. 模块组成
  2. 定义与使用 :stars: 继承于Duplex,全双工的流对象,Http2Stream 类的每个实例都代表一个通过 Http2Session 实例的双向 HTTP/2 通信流。任何单个 Http2Session 在其生命周期内最多可以有 231-1 个 Http2Stream 实例!

:stars: 用户不会直接构造 Http2Stream 实例。相反,这些是通过 Http2Session 实例创建、管理和提供给用户代码的。在服务器上,创建 Http2Stream 实例以响应传入的 HTTP 请求(并通过 'stream' 事件传递给用户),或响应对 http2stream.pushStream() 方法的调用。在客户端,当调用 http2session.request() 方法或响应传入的“推送”事件时,会创建并返回 Http2Stream 实例。

:stars: Http2Stream作为ServerHttp2Stream以及ClientHttp2Stream的父类,默认文本字符编码是 UTF-8。使用 Http2Stream 发送文本时,使用 'content-type' 标头设置字符编码。

stream.respond({
  'content-type': 'text/html; charset=utf-8',
  ':status': 200
});
  1. 源码实现
  2. 关键方法与属性

http2各个模块的协作流程

:confused: 瞅着http2模块中这么多的模块,以及 :u6709: 一些隐藏性的模块其中,那么他们之间是如何协同工作的呢???

http2服务端的工作流程

http2的服务端工作过程 http2服务端相关模块之间的协作关系图

results matching ""

    No results matching ""