HTTP/2
node:http2 模块提供了 HTTP/2 协议的实现,与 http API 相比,http2 核心 API 在客户端和服务器之间更加对称。例如,大多数事件,如 'error'、'connect' 和 'stream',可以由客户端代码或服务器端代码触发。
生成证书和密钥
bash
openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
-keyout localhost-privkey.pem -out localhost-cert.pem
1
2
2
服务端
由于没有已知的浏览器支持 未加密的 HTTP/2,因此在与浏览器客户端通信时必须使用 http2.createSecureServer()。
js
const http2 = require('node:http2');
const fs = require('node:fs');
/** Http2Server & Http2SecureServer
*@close([callback])
@setTimeout([msecs][, callback])
@timeout
@updateSettings([settings])
*/
const server = http2.createSecureServer({
key: fs.readFileSync('localhost-privkey.pem'),
cert: fs.readFileSync('localhost-cert.pem'),
origins: ['https://example.com', 'https://example.org']
});
server.on('checkContinue', () => console.log());
server.on('connection', () => console.log('当建立新的 TCP 流时会触发此事件'));
server.on('request', () => console.log('每次有请求时触发'));
server.on('stream', () => console.log(''));
server.on('timeout', () => console.log());
server.on('error', (err) => console.error(err));
server.on('session', (session) => {
console.log('当 Http2Server 创建新的 Http2Session 时')
// 向连接的客户端提交 ALTSVC 帧
session.altsvc('h2=":8000"', 'https://example.org:80');
/**向连接的客户端提交 ORIGIN 帧,以通告服务器能够提供权威响应的源集。 */
session.origin('https://example.com', 'https://example.org');
});
// 当与服务器关联的 Http2Session 触发 'stream' 事件时
server.on('stream', (stream, headers) => {
/**
* ServerHttp2Stream 类是 Http2Stream 的扩展,专门用于 HTTP/2 服务器
* @additionalHeaders(headers) 向连接的 HTTP/2 对等方发送额外的信息性 HEADERS 帧。
* @pushStream(headers[, options], callback) 启动推送流
* @headersSent
* @pushAllowed
*/
stream.respond({
'content-type': 'text/html; charset=utf-8',
':status': 200,
},{
endStream:false, //true 表示响应将不包含有效负载数据
waitForTrailers:true,//传输完最后的 DATA 帧时,Http2Stream 不会自动关闭
}
);
stream.pushStream({ ':path': '/' }, (err, pushStream, headers) => {
if (err) throw err;
pushStream.respond({ ':status': 200 });
pushStream.end('some pushed data');
});
stream.on('wantTrailers', () => {
stream.sendTrailers({ ABC: 'some value to send' });
});
const stat = fs.fstatSync(fd);
const headers = {
'content-length': stat.size,
'last-modified': stat.mtime.toUTCString(),
'content-type': 'text/plain; charset=utf-8',
};
// 启动响应,其数据从给定的文件描述符中读取。
stream.respondWithFD(fd, headers);
stream.respondWithFile('/some/file',
{ 'content-type': 'text/plain; charset=utf-8' },
{ function statCheck(stat, headers) {
headers['last-modified'] = stat.mtime.toUTCString();
}, onError(error){} });
stream.end('<h1>Hello World</h1>');
});
server.listen(8443);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
客户端
以下说明了 HTTP/2 客户端:
js
const http2 = require('node:http2');
const fs = require('node:fs');
// Http2Session 类的实例表示 HTTP/2 客户端和服务器之间的活动通信会话
/** http2session 属性
* @alpnProtocal 返回已连接的 TLSSocket 自己的 alpnProtocol 属性的值
* @closed
* @connecting
* @destroyed
* @encrypted TLSSocket 连接则为true
* @localSettings 当前本地设置的无原型对象
* @remoteSettings 当前远程设置的无原型对象
* @originSet 连接到 TLSSocket,则 originSet 属性将返回 Array 的起源
* @pendingSettingsAck 是否正在等待已发送的 SETTINGS 帧的确认
* @socket 返回 Proxy 对象,它充当 net.Socket(或 tls.TLSSocket)
* @state 提供有关 Http2Session 当前状态的其他信息。
* @type 是客户端还是服务器
* @destroyed
* @settings([settings][, callback]) 更新此 Http2Session 的当前本地设置
* @close([callback])
* @destroy([error][, code]) 立即终止关联的 net.Socket 或 tls.TLSSocket。
* @goaway([code[, lastStreamID[, opaqueData]]]) 将 GOAWAY 帧传输到连接的对等方
* @ping([payload, ]callback) 向连接的 HTTP/2 对等方发送 PING 帧
* @ref() 底层 net.Socket 上调用 ref()。
* @unref() 底层 net.Socket 上调用 unref()。
*/
const session = http2.connect('https://localhost:8443', {
ca: fs.readFileSync('localhost-cert.pem'),
});
session.on('close', () => console.log("'close' 事件在 Http2Session 被销毁后触发。其监听器不需要任何参数。"));
session.on('connect', () => console.log('成功连接到远程对等方并且通信可以开始'));
session.on('frameError', () => console.log('在会话上发送帧时发生错误时'));
session.on('goaway', () => console.log('接收到 GOAWAY 帧时触发 'goaway' 事件。'));
session.settings({ enablePush: false });
session.on('localSettings', () => console.log('当接收到确认 SETTINGS 帧时触发 'localSettings' 事件。'));
session.on('ping', () => console.log('每当从连接的对等方接收到 PING 帧时'));
session.on('remoteSettings', (settings) => console.log('当从连接的对等方接收到新的 SETTINGS 帧时'));
// 创建新的 Http2Stream 时会触发
session.on('stream', (stream, headers, flags) => {
const method = headers[':method'];
const path = headers[':path'];
stream.respond({
':status': 200,
'content-type': 'text/plain; charset=utf-8',
});
stream.write('hello ');
stream.end('world');
});
session.setTimeout(2000,callback(){});
session.on('timeout', () => { /* .. */ });
session.on('error', (err) => console.error('在处理 Http2Session 期间发生错误时触发',err));
const http2 = require('node:http2');
const server = http2.createServer();
server.on('session', (session) => {
// 设置本地端点的窗口大小。windowSize 是要设置的总窗口大小,而不是增量
session.setLocalWindowSize(2 ** 20);
});
session.ping(Buffer.from('abcdefgh'), (err, duration, payload) => {
if (!err) {
console.log(`Ping acknowledged in ${duration} milliseconds`);
console.log(`With payload '${payload.toString()}'`);
}
});
/**
* ClientHttp2Session属性和方法
*/
client.on('altsvc', (alt, origin, streamId) => {
console.log(alt,'每当客户端接收到 ALTSVC 帧时');
console.log(origin);
console.log(streamId);
});
client.on('origin', (origins) => {
for (let n = 0; n < origins.length; n++)
console.log(origins[n],'每当客户端接收到 ORIGIN 帧时');
});
/** Http2Stream:代表一个通过 Http2Session 实例的双向 HTTP/2 通信流
* Http2Stream 类是 ServerHttp2Stream 和 ClientHttp2Stream 类的基础,
* 每个类分别由服务器端或客户端专门使用
* @aborted
* @bufferSize
* @closed
* @destroyed
* @endAfterHeaders
* @id
* @pending
* @rstCode
* @sentHeaders 包含为此 Http2Stream 发送的出站标头的对象。
* @sentInfoHeaders
* @sentTrailers 为此 HttpStream 发送的出站尾随标头的对象。
* @session 对拥有此 Http2Stream 的 Http2Session 实例的引用
* @state
* @sentInfoHeaders
* @sentInfoHeaders
* @sentInfoHeaders
* @close(code[, callback])
* @priority(options) 更新此 Http2Stream 实例的优先级。
* @setTimeout(msecs, callback)
* @sendTrailers(headers) 向连接的 HTTP/2 对等端发送尾随的 HEADERS 帧
*/
req.on('abort', () => { console.log(' Http2Stream 被销毁时触发')});
req.on('error', () => { console.log(' 处理 Http2Stream 期间发生错误时触发。')});
req.on('frameError', () => { console.log(' 发送帧时发生错误')});
req.on('ready', () => { console.log(' Http2Stream 已打开、已分配 id 且可以使用时触发。监听器不需要任何参数。')});
req.on('timeout', () => { console.log(' 设置的毫秒数内没有收到此 Http2Stream 的活动后')});
req.on('trailers', () => { console.log(' 当接收到与尾随标头字段关联的标头块')});
req.on('wantTrailers', () => { console.log('已将要在帧上发送的最后 DATA 帧排队')});
/**
* ClientHttp2Stream属性和方法
*/
const req = session.request({ ':path': '/' },{
endStream:true,
exclusive:false,
parent:1,
weight:12,
waitForTrailers:true,
// signal:abortSignal.signal
});
req.respond({
'content-type': 'text/html; charset=utf-8',
':status': 200,
});
req.setEncoding('utf8');
let data = '';
req.on('data', (chunk) => { data += chunk; });
req.on('continue', () => { console.log('当服务器发送 100 Continue 状态时触发')});
req.on('headers', (headers,flags) => { console.log('当接收到流的附加标头块时')});
req.on('push', (headers,flags) => { console.log('当接收到服务器推送流的响应头时')});
req.on('continue', () => { console.log('当服务器发送 100 Continue 状态时触发')});
req.on('continue', () => { console.log('当服务器发送 100 Continue 状态时触发')});
req.on('response', (headers, flags) => {
for (const name in headers) {
console.log(`${name}: ${headers[name]}`);
console.log('收到此流的响应 HEADERS 帧时')
}
});
req.on('end', () => {
console.log(`\n${data}`);
session.close();
});
req.end();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155