如何用Node.js HTTP2模块在HEADERS帧前发送PRIORITY帧?
在Node.js HTTP2模块中发送PRIORITY帧的实现方案
我注意到Firefox、nghttp这类HTTP2客户端,在完成协议协商、发送HEADERS帧之前,会给流3、5、7、9、11发送5个PRIORITY帧,相关日志如下:
$ nghttpd -d ~/Proxy 8080 key.pem cert.pem -v Enter PEM pass phrase: IPv4: listen 0.0.0.0:8080 IPv6: listen :::8080 [ALPN] client offers: * h2 * spdy/3.1 * http/1.1 SSL/TLS handshake completed The negotiated protocol: h2 [id=1] [ 18.917] send SETTINGS frame <length=6, flags=0x00, stream_id=0> (niv=1) [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100] [id=1] [ 18.920] recv SETTINGS frame <length=12, flags=0x00, stream_id=0> (niv=2) [SETTINGS_INITIAL_WINDOW_SIZE(0x04):131072] [SETTINGS_MAX_FRAME_SIZE(0x05):16384] [id=1] [ 18.920] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0> (window_size_increment=12517377) [id=1] [ 18.920] recv PRIORITY frame <length=5, flags=0x00, stream_id=3> (dep_stream_id=0, weight=201, exclusive=0) [id=1] [ 18.920] recv PRIORITY frame <length=5, flags=0x00, stream_id=5> (dep_stream_id=0, weight=101, exclusive=0) [id=1] [ 18.920] recv PRIORITY frame <length=5, flags=0x00, stream_id=7> (dep_stream_id=0, weight=1, exclusive=0) [id=1] [ 18.920] recv PRIORITY frame <length=5, flags=0x00, stream_id=9> (dep_stream_id=7, weight=1, exclusive=0) [id=1] [ 18.920] recv PRIORITY frame <length=5, flags=0x00, stream_id=11> (dep_stream_id=3, weight=1, exclusive=0) [id=1] [ 18.920] send SETTINGS frame <length=0, flags=0x01, stream_id=0> ; ACK (niv=0) [id=1] [ 18.926] recv SETTINGS frame <length=0, flags=0x01, stream_id=0> ; ACK (niv=0) [id=1] [ 25.773] recv (stream_id=13) :method: GET [id=1] [ 25.773] recv (stream_id=13) :path: /plaintext.txt [id=1] [ 25.773] recv (stream_id=13) :authority: 127.0.0.1:8080 [id=1] [ 25.773] recv (stream_id=13) :scheme: https [id=1] [ 25.773] recv (stream_id=13) user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:45.0) Gecko/20100101 Firefox/45.0 [id=1] [ 25.773] recv (stream_id=13) accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 [id=1] [ 25.773] recv (stream_id=13) accept-language: en-US,en;q=0.5 [id=1] [ 25.773] recv (stream_id=13) accept-encoding: gzip, deflate, br [id=1] [ 25.773] recv (stream_id=13) if-modified-since: Wed, 27 Apr 2016 13:10:07 GMT [id=1] [ 25.773] recv HEADERS frame <length=196, flags=0x25, stream_id=13> ; END_STREAM | END_HEADERS | PRIORITY (padlen=0, dep_stream_id=11, weight=32, exclusive=0) ; Open new stream [id=1] [ 25.774] recv WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=13> (window_size_increment=12451840) [id=1] [ 25.774] send HEADERS frame <length=42, flags=0x05, stream_id=13> ; END_STREAM | END_HEADERS (padlen=0) ; First response header :status: 304 server: nghttpd nghttp2/1.8.0 date: Fri, 29 Apr 2016 08:07:25 GMT [id=1] [ 25.774] stream_id=13 closed
实现方案
Node.js HTTP2模块没有提供直接发送PRIORITY帧的高层API,但可以通过底层的Http2Session.frame()方法手动构造并发送符合HTTP2规范的PRIORITY帧,步骤如下:
1. 明确PRIORITY帧的格式
根据HTTP2协议规范,PRIORITY帧的核心参数:
- 帧类型:
0x02 - 长度:固定为5字节(payload大小)
- 标志位:
0(无特殊标志) - 流ID:目标流的ID(客户端流必须为奇数,如3、5等)
- Payload结构:
- 前4字节:依赖流ID(最高位为独占位,0表示非独占)
- 第5字节:权重(范围1-256)
2. 代码实现示例
在HTTP2客户端会话完成SETTINGS握手(收到服务器的SETTINGS ACK)后,构造并发送PRIORITY帧:
const http2 = require('http2'); const fs = require('fs'); // 连接到HTTP2服务器(自签名证书需指定CA) const client = http2.connect('https://localhost:8080', { ca: fs.readFileSync('cert.pem') }); // 在收到SETTINGS ACK后发送PRIORITY帧 client.on('settings', (settings, isAck) => { if (isAck) { // 封装发送PRIORITY帧的工具函数 function sendPriority(targetStreamId, depStreamId, weight, exclusive = 0) { // 构造payload:将独占位存入依赖流ID的最高位 const depIdWithExclusive = (exclusive << 31) | depStreamId; const payload = Buffer.alloc(5); payload.writeUInt32BE(depIdWithExclusive, 0); payload.writeUInt8(weight, 4); // 发送帧:类型0x02=PRIORITY,长度5,标志0,目标流ID,payload client.frame(0x02, 5, 0, targetStreamId, payload); } // 按照日志中的参数发送5个PRIORITY帧 sendPriority(3, 0, 201); sendPriority(5, 0, 101); sendPriority(7, 0, 1); sendPriority(9, 7, 1); sendPriority(11, 3, 1); // 发送业务请求(示例) const req = client.request({ ':path': '/plaintext.txt' }); req.on('response', (headers) => { console.log('响应头:', headers); }); req.end(); } }); client.on('error', (err) => console.error('客户端错误:', err));
3. 注意事项
- 必须在会话握手完成后发送:等待服务器的SETTINGS ACK,确保会话处于可用状态
- 流ID规则:客户端流ID必须是奇数,且未被使用(提前预分配3、5等ID符合HTTP2规范)
- 权重范围:权重值必须在1-256之间,日志中的数值均符合要求
- 版本兼容性:
frame()是底层API,建议使用Node.js LTS版本(16.x及以上),避免版本差异导致的问题
内容的提问来源于stack exchange,提问作者Just Intuition
相关产品推荐
相关产品推荐

