grpc-libp2p-client 0.0.39 → 0.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dc-http2/frame.cjs.js +51 -19
- package/dist/dc-http2/frame.cjs.js.map +1 -1
- package/dist/dc-http2/frame.d.ts +1 -1
- package/dist/dc-http2/frame.esm.js +51 -19
- package/dist/dc-http2/frame.esm.js.map +1 -1
- package/dist/dc-http2/hpack.cjs.js +43 -13
- package/dist/dc-http2/hpack.cjs.js.map +1 -1
- package/dist/dc-http2/hpack.esm.js +43 -13
- package/dist/dc-http2/hpack.esm.js.map +1 -1
- package/dist/dc-http2/parser.cjs.js +281 -189
- package/dist/dc-http2/parser.cjs.js.map +1 -1
- package/dist/dc-http2/parser.d.ts +21 -2
- package/dist/dc-http2/parser.esm.js +281 -189
- package/dist/dc-http2/parser.esm.js.map +1 -1
- package/dist/dc-http2/stream.cjs.js +97 -70
- package/dist/dc-http2/stream.cjs.js.map +1 -1
- package/dist/dc-http2/stream.d.ts +2 -0
- package/dist/dc-http2/stream.esm.js +97 -70
- package/dist/dc-http2/stream.esm.js.map +1 -1
- package/dist/grpc.js +810 -579
- package/dist/grpc.js.map +1 -1
- package/dist/grpc.min.js +1 -1
- package/dist/grpc.min.js.map +1 -1
- package/dist/index.cjs.js +633 -411
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +633 -411
- package/dist/index.esm.js.map +1 -1
- package/dist/stats.html +1 -1
- package/package.json +1 -1
- package/src/dc-http2/frame.ts +11 -9
- package/src/dc-http2/hpack.ts +43 -13
- package/src/dc-http2/parser.ts +219 -196
- package/src/dc-http2/stream.ts +84 -79
- package/src/index.ts +240 -183
|
@@ -237,7 +237,6 @@ class HPACK {
|
|
|
237
237
|
this.dynamicTable.unshift([name, value]);
|
|
238
238
|
this.dynamicTableSize += size;
|
|
239
239
|
}
|
|
240
|
-
this.dynamicTable.push([name, value]);
|
|
241
240
|
}
|
|
242
241
|
// 获取索引的头部
|
|
243
242
|
getIndexedHeader(index) {
|
|
@@ -354,22 +353,29 @@ class HPACK {
|
|
|
354
353
|
// Huffman编码实现
|
|
355
354
|
huffmanEncode(bytes) {
|
|
356
355
|
const result = [];
|
|
356
|
+
// 使用高精度浮点数累积位,避免 JS 32-bit 有符号整数在位数 >31 时溢出。
|
|
357
|
+
// Huffman 码最长 30 bits,加上未输出的最多 7 bits = 37 bits,超过 32-bit 安全范围。
|
|
358
|
+
// Number 可精确表示 2^53 以内的整数,足够累积多个码字。
|
|
357
359
|
let current = 0;
|
|
358
360
|
let bits = 0;
|
|
359
361
|
for (let i = 0; i < bytes.length; i++) {
|
|
360
362
|
const b = bytes[i];
|
|
361
363
|
const code = this.huffmanTable.codes[b];
|
|
362
364
|
const length = this.huffmanTable.lengths[b];
|
|
365
|
+
// 用乘法左移替代 <<,避免 32-bit 截断
|
|
366
|
+
current = current * (1 << length) + code;
|
|
363
367
|
bits += length;
|
|
364
|
-
current = (current << length) | code;
|
|
365
368
|
while (bits >= 8) {
|
|
366
369
|
bits -= 8;
|
|
367
|
-
result.push((current
|
|
370
|
+
result.push(Math.floor(current / (1 << bits)) & 0xFF);
|
|
371
|
+
// 保留低 bits 位
|
|
372
|
+
current = current % (1 << bits);
|
|
368
373
|
}
|
|
369
374
|
}
|
|
370
|
-
//
|
|
375
|
+
// 处理剩余的位(用 EOS 填充 1)
|
|
371
376
|
if (bits > 0) {
|
|
372
|
-
|
|
377
|
+
const pad = 8 - bits;
|
|
378
|
+
current = current * (1 << pad) + ((1 << pad) - 1);
|
|
373
379
|
result.push(current & 0xFF);
|
|
374
380
|
}
|
|
375
381
|
return new Uint8Array(result);
|
|
@@ -392,8 +398,16 @@ class HPACK {
|
|
|
392
398
|
headers.set(name, value);
|
|
393
399
|
index = newIndex;
|
|
394
400
|
}
|
|
395
|
-
else if ((firstByte & 0x20) !== 0) { // 001xxxxx - Dynamic Table Size Update
|
|
396
|
-
index
|
|
401
|
+
else if ((firstByte & 0x20) !== 0) { // 001xxxxx - Dynamic Table Size Update (RFC 7541 §6.3)
|
|
402
|
+
const [newSize, newIndex] = this.decodeInteger(buffer, index, 5);
|
|
403
|
+
this.maxDynamicTableSize = newSize;
|
|
404
|
+
// evict entries that exceed the new limit
|
|
405
|
+
while (this.dynamicTableSize > this.maxDynamicTableSize && this.dynamicTable.length > 0) {
|
|
406
|
+
const entry = this.dynamicTable.pop();
|
|
407
|
+
if (entry)
|
|
408
|
+
this.dynamicTableSize -= entry[0].length + entry[1].length + 32;
|
|
409
|
+
}
|
|
410
|
+
index = newIndex;
|
|
397
411
|
}
|
|
398
412
|
else if ((firstByte & 0x10) !== 0) { // 0001xxxx - Literal Header Field Never Indexed
|
|
399
413
|
const [name, value, newIndex] = this.decodeLiteralHeaderWithoutIndexing(buffer, index);
|
|
@@ -465,18 +479,18 @@ class HPACK {
|
|
|
465
479
|
if (staticIndex <= 0) {
|
|
466
480
|
return ['', '', newIndex];
|
|
467
481
|
}
|
|
468
|
-
const headerField = this.
|
|
482
|
+
const headerField = this.getIndexedHeader(staticIndex);
|
|
469
483
|
if (!headerField) {
|
|
470
484
|
return ['', '', newIndex];
|
|
471
485
|
}
|
|
472
486
|
return [headerField[0], headerField[1], newIndex];
|
|
473
487
|
}
|
|
474
488
|
decodeLiteralHeaderWithIndexing(buffer, index) {
|
|
475
|
-
const [
|
|
476
|
-
index =
|
|
489
|
+
const [nameIndex, nextIndex] = this.decodeInteger(buffer, index, 6);
|
|
490
|
+
index = nextIndex;
|
|
477
491
|
let name;
|
|
478
|
-
if (
|
|
479
|
-
const headerField = this.
|
|
492
|
+
if (nameIndex > 0) {
|
|
493
|
+
const headerField = this.getIndexedHeader(nameIndex);
|
|
480
494
|
name = headerField ? headerField[0] : '';
|
|
481
495
|
}
|
|
482
496
|
else {
|
|
@@ -485,10 +499,26 @@ class HPACK {
|
|
|
485
499
|
index = newIndex;
|
|
486
500
|
}
|
|
487
501
|
const [value, finalIndex] = this.decodeLiteralString(buffer, index);
|
|
502
|
+
// RFC 7541 §6.2.1: Literal Header Field with Incremental Indexing must add to dynamic table
|
|
503
|
+
this.addToDynamicTable(name, value);
|
|
488
504
|
return [name, value, finalIndex];
|
|
489
505
|
}
|
|
490
506
|
decodeLiteralHeaderWithoutIndexing(buffer, index) {
|
|
491
|
-
|
|
507
|
+
// RFC 7541 §6.2.2 / §6.2.3: 4-bit prefix, do NOT add to dynamic table
|
|
508
|
+
const [nameIndex, nextIndex] = this.decodeInteger(buffer, index, 4);
|
|
509
|
+
index = nextIndex;
|
|
510
|
+
let name;
|
|
511
|
+
if (nameIndex > 0) {
|
|
512
|
+
const headerField = this.getIndexedHeader(nameIndex);
|
|
513
|
+
name = headerField ? headerField[0] : '';
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
const [decodedName, newIndex] = this.decodeLiteralString(buffer, index);
|
|
517
|
+
name = decodedName;
|
|
518
|
+
index = newIndex;
|
|
519
|
+
}
|
|
520
|
+
const [value, finalIndex] = this.decodeLiteralString(buffer, index);
|
|
521
|
+
return [name, value, finalIndex];
|
|
492
522
|
}
|
|
493
523
|
// 直接转换为字符串的方法
|
|
494
524
|
huffmanDecodeToString(bytes) {
|
|
@@ -576,9 +606,11 @@ const SETTINGS_PARAMETERS = {
|
|
|
576
606
|
};
|
|
577
607
|
const defaultSettings = {
|
|
578
608
|
[SETTINGS_PARAMETERS.HEADER_TABLE_SIZE]: 4096,
|
|
579
|
-
|
|
609
|
+
// gRPC 客户端不使用 Server Push,禁用以避免无效的 PUSH_PROMISE 处理
|
|
610
|
+
[SETTINGS_PARAMETERS.ENABLE_PUSH]: 0,
|
|
580
611
|
[SETTINGS_PARAMETERS.MAX_CONCURRENT_STREAMS]: 100,
|
|
581
|
-
|
|
612
|
+
// 匹配 parser 的实际接收缓冲区大小(4MB),避免服务端在单流上过早被限速
|
|
613
|
+
[SETTINGS_PARAMETERS.INITIAL_WINDOW_SIZE]: 4 << 20, // 4MB
|
|
582
614
|
[SETTINGS_PARAMETERS.MAX_FRAME_SIZE]: 16 << 10, // 16k
|
|
583
615
|
[SETTINGS_PARAMETERS.MAX_HEADER_LIST_SIZE]: 8192
|
|
584
616
|
};
|
|
@@ -639,8 +671,8 @@ class Http2Frame {
|
|
|
639
671
|
// Message-Data
|
|
640
672
|
grpcMessage.set(data, 5);
|
|
641
673
|
// 然后将完整的 gRPC 消息分割成多个 HTTP/2 DATA 帧
|
|
642
|
-
//
|
|
643
|
-
const maxDataPerFrame = maxFrameSize
|
|
674
|
+
// maxFrameSize 是 payload 上限(RFC 7540 §6.5.2 MAX_FRAME_SIZE),不含 9 字节帧头
|
|
675
|
+
const maxDataPerFrame = maxFrameSize;
|
|
644
676
|
for (let offset = 0; offset < grpcMessage.length; offset += maxDataPerFrame) {
|
|
645
677
|
const remaining = grpcMessage.length - offset;
|
|
646
678
|
const chunkSize = Math.min(maxDataPerFrame, remaining);
|
|
@@ -672,13 +704,13 @@ class Http2Frame {
|
|
|
672
704
|
const flags = endStream ? 0x01 : 0x0; // END_STREAM flag
|
|
673
705
|
return Http2Frame.createFrame(0x0, flags, streamId, framedData);
|
|
674
706
|
}
|
|
675
|
-
static createHeadersFrame(streamId, path, endHeaders = true, token) {
|
|
707
|
+
static createHeadersFrame(streamId, path, endHeaders = true, token, authority = 'localhost') {
|
|
676
708
|
// gRPC-Web 需要的标准 headers
|
|
677
709
|
const headersList = {
|
|
678
710
|
':path': path,
|
|
679
711
|
':method': 'POST',
|
|
680
712
|
':scheme': 'http',
|
|
681
|
-
':authority':
|
|
713
|
+
':authority': authority,
|
|
682
714
|
'content-type': 'application/grpc+proto',
|
|
683
715
|
'user-agent': 'grpc-web-client/0.1',
|
|
684
716
|
'accept': 'application/grpc+proto',
|
|
@@ -805,8 +837,15 @@ function _createPayload(settings) {
|
|
|
805
837
|
|
|
806
838
|
const HTTP2_PREFACE = new TextEncoder().encode("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
|
|
807
839
|
class HTTP2Parser {
|
|
840
|
+
/** 兼容旧代码读取 buffer —— 仅在必须全量访问时调用 _flattenBuffer() */
|
|
841
|
+
get buffer() { return this._flattenBuffer(); }
|
|
842
|
+
set buffer(v) { this.bufferChunks = v.length ? [v] : []; this.bufferTotalLength = v.length; }
|
|
808
843
|
constructor(writer, options) {
|
|
809
|
-
|
|
844
|
+
/** 分段缓冲:避免每次 chunk 到达时 O(n) 全量拷贝 */
|
|
845
|
+
this.bufferChunks = [];
|
|
846
|
+
this.bufferTotalLength = 0;
|
|
847
|
+
this.bufferChunks = [];
|
|
848
|
+
this.bufferTotalLength = 0;
|
|
810
849
|
this.settingsAckReceived = false;
|
|
811
850
|
this.peerSettingsReceived = false;
|
|
812
851
|
// 初始化连接级别的流控制窗口大小(默认值:65,535)
|
|
@@ -820,11 +859,38 @@ class HTTP2Parser {
|
|
|
820
859
|
this.sendStreamWindows = new Map();
|
|
821
860
|
this.peerInitialStreamWindow = 65535;
|
|
822
861
|
this.sendWindowWaiters = [];
|
|
862
|
+
this.settingsAckWaiters = [];
|
|
863
|
+
this.peerSettingsWaiters = [];
|
|
864
|
+
this.endOfStreamWaiters = [];
|
|
823
865
|
// 结束标志
|
|
824
866
|
this.endFlag = false;
|
|
825
867
|
this.writer = writer;
|
|
826
868
|
this.compatibilityMode = options?.compatibilityMode ?? false;
|
|
827
869
|
}
|
|
870
|
+
/** 将所有分段合并为一个连续 Uint8Array(仅在必要时调用)*/
|
|
871
|
+
_flattenBuffer() {
|
|
872
|
+
if (this.bufferChunks.length === 0)
|
|
873
|
+
return new Uint8Array(0);
|
|
874
|
+
if (this.bufferChunks.length === 1)
|
|
875
|
+
return this.bufferChunks[0];
|
|
876
|
+
const out = new Uint8Array(this.bufferTotalLength);
|
|
877
|
+
let off = 0;
|
|
878
|
+
for (const c of this.bufferChunks) {
|
|
879
|
+
out.set(c, off);
|
|
880
|
+
off += c.length;
|
|
881
|
+
}
|
|
882
|
+
return out;
|
|
883
|
+
}
|
|
884
|
+
/** 唤醒所有发送窗口等待者 */
|
|
885
|
+
_wakeWindowWaiters() {
|
|
886
|
+
const ws = this.sendWindowWaiters.splice(0);
|
|
887
|
+
for (const w of ws) {
|
|
888
|
+
try {
|
|
889
|
+
w.resolve();
|
|
890
|
+
}
|
|
891
|
+
catch { /* ignore */ }
|
|
892
|
+
}
|
|
893
|
+
}
|
|
828
894
|
// 持续处理流数据
|
|
829
895
|
async processStream(stream) {
|
|
830
896
|
try {
|
|
@@ -834,7 +900,6 @@ class HTTP2Parser {
|
|
|
834
900
|
}
|
|
835
901
|
// Stream 结束后的清理工作
|
|
836
902
|
if (!this.compatibilityMode && !this.endFlag) {
|
|
837
|
-
this.endFlag = true;
|
|
838
903
|
try {
|
|
839
904
|
this.onEnd?.();
|
|
840
905
|
}
|
|
@@ -842,51 +907,84 @@ class HTTP2Parser {
|
|
|
842
907
|
console.error("Error during onEnd callback:", err);
|
|
843
908
|
}
|
|
844
909
|
}
|
|
910
|
+
// 无论何种模式,stream 结束时都通知 waitForEndOfStream 等待者,
|
|
911
|
+
// 防止 compatibilityMode=true(server-streaming)时 waitForEndOfStream(0) 永久挂死
|
|
912
|
+
if (!this.endFlag) {
|
|
913
|
+
this._notifyEndOfStream();
|
|
914
|
+
}
|
|
845
915
|
}
|
|
846
916
|
catch (error) {
|
|
847
|
-
|
|
917
|
+
// abort() 触发的清理错误(如 'Call cleanup' / 'unaryCall cleanup')属于预期行为,降级为 debug 日志
|
|
918
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
919
|
+
const isAbortCleanup = /cleanup/i.test(errMsg) || /aborted/i.test(errMsg);
|
|
920
|
+
if (isAbortCleanup) {
|
|
921
|
+
console.debug("[processStream] stream aborted (expected):", errMsg);
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
console.error("Error processing stream:", error);
|
|
925
|
+
}
|
|
926
|
+
// 确保 waitForEndOfStream 等待者得到通知,防止 operationPromise 后台挂死
|
|
927
|
+
if (!this.endFlag) {
|
|
928
|
+
this._notifyEndOfStream();
|
|
929
|
+
}
|
|
848
930
|
throw error;
|
|
849
931
|
}
|
|
850
932
|
}
|
|
851
|
-
// 处理单个数据块
|
|
933
|
+
// 处理单个数据块 — 分段列表追加,避免每次 O(n) 全量拷贝
|
|
852
934
|
_processChunk(chunk) {
|
|
853
935
|
// chunk 是 Uint8ArrayList 或 Uint8Array
|
|
854
936
|
const newData = 'subarray' in chunk && typeof chunk.subarray === 'function'
|
|
855
937
|
? chunk.subarray()
|
|
856
938
|
: chunk;
|
|
857
|
-
//
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
939
|
+
// 追加到分段列表,O(1),不拷贝历史数据
|
|
940
|
+
if (newData.length > 0) {
|
|
941
|
+
this.bufferChunks.push(newData);
|
|
942
|
+
this.bufferTotalLength += newData.length;
|
|
943
|
+
}
|
|
944
|
+
// 将所有分段合并为一块后处理帧(只合并一次,后续 slice 替换)
|
|
945
|
+
// 仅在确实有完整帧时才触发合并,碎片仅 push 不合并
|
|
946
|
+
if (this.bufferTotalLength < 9)
|
|
947
|
+
return;
|
|
948
|
+
// 合并一次
|
|
949
|
+
const flat = this._flattenBuffer();
|
|
950
|
+
this.bufferChunks = [flat];
|
|
951
|
+
// bufferTotalLength 保持不变
|
|
862
952
|
// 持续处理所有完整的帧
|
|
863
953
|
let readOffset = 0;
|
|
864
|
-
while (
|
|
954
|
+
while (flat.length - readOffset >= 9) {
|
|
865
955
|
// 判断是否有HTTP/2前导
|
|
866
|
-
if (
|
|
956
|
+
if (flat.length - readOffset >= 24 && this.isHttp2Preface(flat.subarray(readOffset))) {
|
|
867
957
|
readOffset += 24;
|
|
868
958
|
// 发送SETTINGS帧
|
|
869
959
|
const settingFrame = Http2Frame.createSettingsFrame();
|
|
870
960
|
this.writer.write(settingFrame);
|
|
871
961
|
continue;
|
|
872
962
|
}
|
|
873
|
-
const frameHeader = this._parseFrameHeader(
|
|
963
|
+
const frameHeader = this._parseFrameHeader(flat.subarray(readOffset));
|
|
874
964
|
const totalFrameLength = 9 + frameHeader.length;
|
|
875
965
|
// 检查是否有完整的帧
|
|
876
|
-
if (
|
|
966
|
+
if (flat.length - readOffset < totalFrameLength) {
|
|
877
967
|
break;
|
|
878
968
|
}
|
|
879
|
-
//
|
|
880
|
-
const frameData =
|
|
969
|
+
// 获取完整帧数据(subarray 视图,零拷贝)
|
|
970
|
+
const frameData = flat.subarray(readOffset, readOffset + totalFrameLength);
|
|
881
971
|
// 处理不同类型的帧
|
|
882
972
|
this._handleFrame(frameHeader, frameData).catch((err) => {
|
|
883
973
|
console.error("Error handling frame:", err);
|
|
884
974
|
});
|
|
885
|
-
// 移动偏移量
|
|
886
975
|
readOffset += totalFrameLength;
|
|
887
976
|
}
|
|
977
|
+
// 保留未消费的尾部字节(slice 一次,后续仍分段追加)
|
|
888
978
|
if (readOffset > 0) {
|
|
889
|
-
|
|
979
|
+
if (readOffset >= flat.length) {
|
|
980
|
+
this.bufferChunks = [];
|
|
981
|
+
this.bufferTotalLength = 0;
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
const remaining = flat.slice(readOffset);
|
|
985
|
+
this.bufferChunks = [remaining];
|
|
986
|
+
this.bufferTotalLength = remaining.length;
|
|
987
|
+
}
|
|
890
988
|
}
|
|
891
989
|
}
|
|
892
990
|
isHttp2Preface(buffer) {
|
|
@@ -898,50 +996,67 @@ class HTTP2Parser {
|
|
|
898
996
|
}
|
|
899
997
|
return true;
|
|
900
998
|
}
|
|
901
|
-
//
|
|
902
|
-
_oldProcessStream_removed() {
|
|
903
|
-
// 这个方法已被上面的事件驱动实现替代
|
|
904
|
-
}
|
|
905
|
-
// 等待SETTINGS ACK
|
|
999
|
+
// 等待SETTINGS ACK — 事件驱动,无轮询
|
|
906
1000
|
waitForSettingsAck() {
|
|
907
1001
|
return new Promise((resolve, reject) => {
|
|
908
1002
|
if (this.settingsAckReceived) {
|
|
909
1003
|
resolve();
|
|
910
1004
|
return;
|
|
911
1005
|
}
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
clearInterval(interval);
|
|
915
|
-
clearTimeout(timeout);
|
|
916
|
-
resolve();
|
|
917
|
-
}
|
|
918
|
-
}, 100);
|
|
1006
|
+
const waiter = { resolve, reject };
|
|
1007
|
+
this.settingsAckWaiters.push(waiter);
|
|
919
1008
|
const timeout = setTimeout(() => {
|
|
920
|
-
|
|
1009
|
+
const idx = this.settingsAckWaiters.indexOf(waiter);
|
|
1010
|
+
if (idx >= 0)
|
|
1011
|
+
this.settingsAckWaiters.splice(idx, 1);
|
|
921
1012
|
reject(new Error("Settings ACK timeout"));
|
|
922
1013
|
}, 30000);
|
|
1014
|
+
// 覆盖 resolve 以便超时前自动清理定时器
|
|
1015
|
+
waiter.resolve = () => { clearTimeout(timeout); resolve(); };
|
|
1016
|
+
waiter.reject = (e) => { clearTimeout(timeout); reject(e); };
|
|
923
1017
|
});
|
|
924
1018
|
}
|
|
925
|
-
|
|
1019
|
+
/** 内部调用:SETTINGS ACK 收到时唤醒所有等待者 */
|
|
1020
|
+
_notifySettingsAck() {
|
|
1021
|
+
this.settingsAckReceived = true;
|
|
1022
|
+
const ws = this.settingsAckWaiters.splice(0);
|
|
1023
|
+
for (const w of ws) {
|
|
1024
|
+
try {
|
|
1025
|
+
w.resolve();
|
|
1026
|
+
}
|
|
1027
|
+
catch { /* ignore */ }
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
// 等待接收来自对端的 SETTINGS(非 ACK)— 事件驱动,无轮询
|
|
926
1031
|
waitForPeerSettings(timeoutMs = 30000) {
|
|
927
1032
|
return new Promise((resolve, reject) => {
|
|
928
1033
|
if (this.peerSettingsReceived) {
|
|
929
1034
|
resolve();
|
|
930
1035
|
return;
|
|
931
1036
|
}
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
clearInterval(interval);
|
|
935
|
-
clearTimeout(timeout);
|
|
936
|
-
resolve();
|
|
937
|
-
}
|
|
938
|
-
}, 100);
|
|
1037
|
+
const waiter = { resolve, reject };
|
|
1038
|
+
this.peerSettingsWaiters.push(waiter);
|
|
939
1039
|
const timeout = setTimeout(() => {
|
|
940
|
-
|
|
1040
|
+
const idx = this.peerSettingsWaiters.indexOf(waiter);
|
|
1041
|
+
if (idx >= 0)
|
|
1042
|
+
this.peerSettingsWaiters.splice(idx, 1);
|
|
941
1043
|
reject(new Error("Peer SETTINGS timeout"));
|
|
942
1044
|
}, timeoutMs);
|
|
1045
|
+
waiter.resolve = () => { clearTimeout(timeout); resolve(); };
|
|
1046
|
+
waiter.reject = (e) => { clearTimeout(timeout); reject(e); };
|
|
943
1047
|
});
|
|
944
1048
|
}
|
|
1049
|
+
/** 内部调用:收到对端 SETTINGS(非 ACK)时唤醒等待者 */
|
|
1050
|
+
_notifyPeerSettings() {
|
|
1051
|
+
this.peerSettingsReceived = true;
|
|
1052
|
+
const ws = this.peerSettingsWaiters.splice(0);
|
|
1053
|
+
for (const w of ws) {
|
|
1054
|
+
try {
|
|
1055
|
+
w.resolve();
|
|
1056
|
+
}
|
|
1057
|
+
catch { /* ignore */ }
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
945
1060
|
// 注册我们要发送数据的出站流(用于初始化该流的对端窗口)
|
|
946
1061
|
registerOutboundStream(streamId) {
|
|
947
1062
|
if (!this.sendStreamWindows.has(streamId)) {
|
|
@@ -968,54 +1083,51 @@ class HTTP2Parser {
|
|
|
968
1083
|
this.sendConnWindow = Math.min(0x7fffffff, this.sendConnWindow + bytes);
|
|
969
1084
|
const cur = this.sendStreamWindows.get(streamId) ?? 0;
|
|
970
1085
|
this.sendStreamWindows.set(streamId, Math.min(0x7fffffff, cur + bytes));
|
|
1086
|
+
// 窗口增大,唤醒等待者
|
|
1087
|
+
this._wakeWindowWaiters();
|
|
971
1088
|
}
|
|
972
|
-
//
|
|
973
|
-
|
|
974
|
-
const
|
|
1089
|
+
// 等待可用发送窗口 — 事件驱动,WINDOW_UPDATE/SETTINGS 收到时直接唤醒
|
|
1090
|
+
waitForSendWindow(streamId, minBytes = 1, timeoutMs = 30000) {
|
|
1091
|
+
const { conn, stream } = this.getSendWindows(streamId);
|
|
1092
|
+
if (conn >= minBytes && stream >= minBytes)
|
|
1093
|
+
return Promise.resolve();
|
|
975
1094
|
return new Promise((resolve, reject) => {
|
|
976
|
-
let interval = null;
|
|
977
1095
|
let settled = false;
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1096
|
+
const timeout = timeoutMs > 0
|
|
1097
|
+
? setTimeout(() => {
|
|
1098
|
+
if (settled)
|
|
1099
|
+
return;
|
|
1100
|
+
settled = true;
|
|
1101
|
+
const idx = this.sendWindowWaiters.findIndex(w => w.resolve === resolveWrap);
|
|
1102
|
+
if (idx >= 0)
|
|
1103
|
+
this.sendWindowWaiters.splice(idx, 1);
|
|
1104
|
+
reject(new Error('Send window wait timeout'));
|
|
1105
|
+
}, timeoutMs)
|
|
1106
|
+
: undefined;
|
|
1107
|
+
const resolveWrap = () => {
|
|
1108
|
+
if (settled)
|
|
1109
|
+
return;
|
|
1110
|
+
const { conn: c2, stream: s2 } = this.getSendWindows(streamId);
|
|
1111
|
+
if (c2 >= minBytes && s2 >= minBytes) {
|
|
1112
|
+
settled = true;
|
|
1113
|
+
if (timeout)
|
|
1114
|
+
clearTimeout(timeout);
|
|
1115
|
+
resolve();
|
|
990
1116
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
if (interval) {
|
|
995
|
-
clearInterval(interval);
|
|
996
|
-
interval = null;
|
|
997
|
-
}
|
|
998
|
-
reject(new Error('Send window wait timeout'));
|
|
999
|
-
}
|
|
1000
|
-
return true;
|
|
1117
|
+
else {
|
|
1118
|
+
// 窗口仍不够,重新入队等待下一次更新
|
|
1119
|
+
this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
|
|
1001
1120
|
}
|
|
1002
|
-
return false;
|
|
1003
1121
|
};
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1122
|
+
const rejectWrap = (e) => {
|
|
1123
|
+
if (settled)
|
|
1124
|
+
return;
|
|
1125
|
+
settled = true;
|
|
1126
|
+
if (timeout)
|
|
1127
|
+
clearTimeout(timeout);
|
|
1128
|
+
reject(e);
|
|
1008
1129
|
};
|
|
1009
|
-
|
|
1010
|
-
// 简单的等待模型:依赖 WINDOW_UPDATE 到达时调用 wake
|
|
1011
|
-
this.sendWindowWaiters.push(wake);
|
|
1012
|
-
// 同时做一个轻微的轮询,防止错过唤醒
|
|
1013
|
-
interval = setInterval(() => {
|
|
1014
|
-
if (check() && interval) {
|
|
1015
|
-
clearInterval(interval);
|
|
1016
|
-
interval = null;
|
|
1017
|
-
}
|
|
1018
|
-
}, 50);
|
|
1130
|
+
this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
|
|
1019
1131
|
});
|
|
1020
1132
|
}
|
|
1021
1133
|
// 处理单个帧
|
|
@@ -1023,7 +1135,7 @@ class HTTP2Parser {
|
|
|
1023
1135
|
switch (frameHeader.type) {
|
|
1024
1136
|
case FRAME_TYPES.SETTINGS:
|
|
1025
1137
|
if ((frameHeader.flags & FRAME_FLAGS.ACK) === FRAME_FLAGS.ACK) {
|
|
1026
|
-
this.
|
|
1138
|
+
this._notifySettingsAck();
|
|
1027
1139
|
}
|
|
1028
1140
|
else {
|
|
1029
1141
|
//接收到Setting请求,进行解析
|
|
@@ -1033,10 +1145,12 @@ class HTTP2Parser {
|
|
|
1033
1145
|
for (let i = 0; i < settingsPayload.length; i += 6) {
|
|
1034
1146
|
// 正确解析:2字节ID + 4字节值
|
|
1035
1147
|
const id = (settingsPayload[i] << 8) | settingsPayload[i + 1];
|
|
1036
|
-
|
|
1148
|
+
// >>> 0 将结果转为无符号 32 位整数,防止高位为 1 时(如 0xffffffff)
|
|
1149
|
+
// 被 JS 按有符号解读为负数,导致 maxConcurrentStreams 等字段为负值
|
|
1150
|
+
const value = ((settingsPayload[i + 2] << 24) |
|
|
1037
1151
|
(settingsPayload[i + 3] << 16) |
|
|
1038
1152
|
(settingsPayload[i + 4] << 8) |
|
|
1039
|
-
settingsPayload[i + 5];
|
|
1153
|
+
settingsPayload[i + 5]) >>> 0;
|
|
1040
1154
|
if (id === 4) {
|
|
1041
1155
|
// SETTINGS_INITIAL_WINDOW_SIZE
|
|
1042
1156
|
this.defaultStreamWindowSize = value; // 我方接收窗口(入站)
|
|
@@ -1073,47 +1187,47 @@ class HTTP2Parser {
|
|
|
1073
1187
|
if (this.onSettings) {
|
|
1074
1188
|
this.onSettings(frameHeader);
|
|
1075
1189
|
}
|
|
1076
|
-
// 标记已收到对端 SETTINGS
|
|
1077
|
-
this.
|
|
1078
|
-
//
|
|
1079
|
-
|
|
1080
|
-
waiters.forEach(fn => { try {
|
|
1081
|
-
fn();
|
|
1082
|
-
}
|
|
1083
|
-
catch (e) {
|
|
1084
|
-
console.debug('waiter error', e);
|
|
1085
|
-
} });
|
|
1190
|
+
// 标记已收到对端 SETTINGS 并唤醒等待者
|
|
1191
|
+
this._notifyPeerSettings();
|
|
1192
|
+
// 唤醒发送窗口等待者(以防部分实现通过 SETTINGS 改变有效窗口)
|
|
1193
|
+
this._wakeWindowWaiters();
|
|
1086
1194
|
}
|
|
1087
1195
|
break;
|
|
1088
|
-
case FRAME_TYPES.DATA:
|
|
1196
|
+
case FRAME_TYPES.DATA: {
|
|
1089
1197
|
// 处理数据帧
|
|
1090
1198
|
if (this.onData) {
|
|
1091
1199
|
this.onData(frameData.slice(9), frameHeader); // 跳过帧头
|
|
1092
1200
|
}
|
|
1093
1201
|
// 更新流窗口和连接窗口
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1202
|
+
// 仅在帧有实际数据时才发送 WINDOW_UPDATE:
|
|
1203
|
+
// RFC 7540 §6.9.1 明确禁止 increment=0 的 WINDOW_UPDATE,
|
|
1204
|
+
// 服务端必须以 PROTOCOL_ERROR 响应,会导致连接被强制关闭。
|
|
1205
|
+
// 空 DATA 帧(如纯 END_STREAM 帧)length=0,不需要归还窗口。
|
|
1206
|
+
const dataLength = frameHeader.length ?? 0;
|
|
1207
|
+
if (dataLength > 0) {
|
|
1208
|
+
try {
|
|
1209
|
+
// 更新流级别的窗口
|
|
1210
|
+
if (frameHeader.streamId !== 0) {
|
|
1211
|
+
const streamWindowUpdate = Http2Frame.createWindowUpdateFrame(frameHeader.streamId, dataLength);
|
|
1212
|
+
this.writer.write(streamWindowUpdate);
|
|
1213
|
+
}
|
|
1214
|
+
// 更新连接级别的窗口
|
|
1215
|
+
const connWindowUpdate = Http2Frame.createWindowUpdateFrame(0, dataLength);
|
|
1216
|
+
this.writer.write(connWindowUpdate);
|
|
1217
|
+
}
|
|
1218
|
+
catch (err) {
|
|
1219
|
+
console.error("[HTTP2] Error sending window update:", err);
|
|
1099
1220
|
}
|
|
1100
|
-
// 更新连接级别的窗口
|
|
1101
|
-
const connWindowUpdate = Http2Frame.createWindowUpdateFrame(0, frameHeader.length ?? 0);
|
|
1102
|
-
this.writer.write(connWindowUpdate);
|
|
1103
|
-
}
|
|
1104
|
-
catch (err) {
|
|
1105
|
-
console.error("[HTTP2] Error sending window update:", err);
|
|
1106
1221
|
}
|
|
1107
1222
|
//判断是否是最后一个帧
|
|
1108
1223
|
if ((frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
|
|
1109
1224
|
FRAME_FLAGS.END_STREAM) {
|
|
1110
|
-
this.
|
|
1111
|
-
|
|
1112
|
-
this.onEnd();
|
|
1113
|
-
}
|
|
1225
|
+
this.onEnd?.();
|
|
1226
|
+
this._notifyEndOfStream();
|
|
1114
1227
|
return;
|
|
1115
1228
|
}
|
|
1116
1229
|
break;
|
|
1230
|
+
}
|
|
1117
1231
|
case FRAME_TYPES.HEADERS:
|
|
1118
1232
|
// 处理头部帧
|
|
1119
1233
|
if (this.onHeaders) {
|
|
@@ -1122,35 +1236,26 @@ class HTTP2Parser {
|
|
|
1122
1236
|
//判断是否是最后一个帧
|
|
1123
1237
|
if ((frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
|
|
1124
1238
|
FRAME_FLAGS.END_STREAM) {
|
|
1125
|
-
this.
|
|
1126
|
-
|
|
1127
|
-
this.onEnd();
|
|
1128
|
-
}
|
|
1239
|
+
this.onEnd?.();
|
|
1240
|
+
this._notifyEndOfStream();
|
|
1129
1241
|
return;
|
|
1130
1242
|
}
|
|
1131
1243
|
break;
|
|
1132
1244
|
case FRAME_TYPES.WINDOW_UPDATE:
|
|
1133
|
-
//
|
|
1134
|
-
this.handleWindowUpdateFrame(frameHeader, frameData);
|
|
1135
|
-
// 更新发送窗口(对端接收窗口)
|
|
1245
|
+
// 处理窗口更新帧(同时更新接收侧诊断计数器和发送侧流控窗口,只解析一次)
|
|
1136
1246
|
try {
|
|
1137
|
-
const
|
|
1247
|
+
const result = this.handleWindowUpdateFrame(frameHeader, frameData);
|
|
1248
|
+
// 更新发送方向窗口(对端的接收窗口)
|
|
1138
1249
|
if (frameHeader.streamId === 0) {
|
|
1139
|
-
this.sendConnWindow +=
|
|
1250
|
+
this.sendConnWindow += result.windowSizeIncrement;
|
|
1140
1251
|
}
|
|
1141
1252
|
else {
|
|
1142
1253
|
const cur = this.sendStreamWindows.get(frameHeader.streamId) ?? this.peerInitialStreamWindow;
|
|
1143
|
-
this.sendStreamWindows.set(frameHeader.streamId, cur +
|
|
1144
|
-
}
|
|
1145
|
-
const waiters = this.sendWindowWaiters.splice(0);
|
|
1146
|
-
waiters.forEach(fn => { try {
|
|
1147
|
-
fn();
|
|
1254
|
+
this.sendStreamWindows.set(frameHeader.streamId, cur + result.windowSizeIncrement);
|
|
1148
1255
|
}
|
|
1149
|
-
|
|
1150
|
-
console.debug('waiter error', e);
|
|
1151
|
-
} });
|
|
1256
|
+
this._wakeWindowWaiters();
|
|
1152
1257
|
}
|
|
1153
|
-
catch { /* ignore WINDOW_UPDATE parse errors */ }
|
|
1258
|
+
catch { /* ignore WINDOW_UPDATE parse errors (e.g. increment=0 is RFC PROTOCOL_ERROR) */ }
|
|
1154
1259
|
break;
|
|
1155
1260
|
case FRAME_TYPES.PING:
|
|
1156
1261
|
// 处理PING帧
|
|
@@ -1179,13 +1284,13 @@ class HTTP2Parser {
|
|
|
1179
1284
|
catch (err) {
|
|
1180
1285
|
console.error('Error during GOAWAY callback:', err);
|
|
1181
1286
|
}
|
|
1182
|
-
this.endFlag = true;
|
|
1183
1287
|
try {
|
|
1184
1288
|
this.onEnd?.();
|
|
1185
1289
|
}
|
|
1186
1290
|
catch (err) {
|
|
1187
1291
|
console.error('Error during GOAWAY onEnd callback:', err);
|
|
1188
1292
|
}
|
|
1293
|
+
this._notifyEndOfStream();
|
|
1189
1294
|
break;
|
|
1190
1295
|
}
|
|
1191
1296
|
// case FRAME_TYPES.PUSH_PROMISE:
|
|
@@ -1193,10 +1298,8 @@ class HTTP2Parser {
|
|
|
1193
1298
|
// this.handlePushPromiseFrame(frameHeader, frameData);
|
|
1194
1299
|
// break;
|
|
1195
1300
|
case FRAME_TYPES.RST_STREAM:
|
|
1196
|
-
this.
|
|
1197
|
-
|
|
1198
|
-
this.onEnd();
|
|
1199
|
-
}
|
|
1301
|
+
this.onEnd?.();
|
|
1302
|
+
this._notifyEndOfStream();
|
|
1200
1303
|
break;
|
|
1201
1304
|
default:
|
|
1202
1305
|
console.debug("Unknown frame type:", frameHeader.type);
|
|
@@ -1206,7 +1309,8 @@ class HTTP2Parser {
|
|
|
1206
1309
|
const length = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
|
|
1207
1310
|
const type = buffer[3];
|
|
1208
1311
|
const flags = buffer[4];
|
|
1209
|
-
|
|
1312
|
+
// RFC 7540 §4.1: most significant bit is reserved and MUST be ignored on receipt
|
|
1313
|
+
const streamId = ((buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8]) & 0x7fffffff;
|
|
1210
1314
|
return {
|
|
1211
1315
|
length,
|
|
1212
1316
|
type,
|
|
@@ -1235,52 +1339,40 @@ class HTTP2Parser {
|
|
|
1235
1339
|
throw error;
|
|
1236
1340
|
}
|
|
1237
1341
|
}
|
|
1238
|
-
|
|
1342
|
+
// 等待流结束 — 事件驱动,onEnd 触发时直接唤醒,无 setInterval 轮询
|
|
1239
1343
|
waitForEndOfStream(waitTime) {
|
|
1240
1344
|
return new Promise((resolve, reject) => {
|
|
1241
|
-
// If the stream has already ended, resolve immediately
|
|
1242
1345
|
if (this.endFlag) {
|
|
1243
1346
|
resolve();
|
|
1244
1347
|
return;
|
|
1245
1348
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1349
|
+
const waiter = { resolve, reject };
|
|
1350
|
+
this.endOfStreamWaiters.push(waiter);
|
|
1351
|
+
const timeout = waitTime > 0
|
|
1352
|
+
? setTimeout(() => {
|
|
1353
|
+
const idx = this.endOfStreamWaiters.indexOf(waiter);
|
|
1354
|
+
if (idx >= 0)
|
|
1355
|
+
this.endOfStreamWaiters.splice(idx, 1);
|
|
1251
1356
|
reject(new Error("End of stream timeout"));
|
|
1252
|
-
}, waitTime)
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
if (this.endFlag) {
|
|
1259
|
-
if (timeout !== null) {
|
|
1260
|
-
clearTimeout(timeout);
|
|
1261
|
-
}
|
|
1262
|
-
clearInterval(interval);
|
|
1263
|
-
resolve();
|
|
1264
|
-
}
|
|
1265
|
-
}, checkInterval);
|
|
1266
|
-
// If the onEnd is triggered externally, it should now be marked manually
|
|
1267
|
-
const originalOnEnd = this.onEnd;
|
|
1268
|
-
this.onEnd = () => {
|
|
1269
|
-
if (!this.endFlag) {
|
|
1270
|
-
// The external trigger may set endFlag; if not, handle here
|
|
1271
|
-
this.endFlag = true;
|
|
1272
|
-
}
|
|
1273
|
-
if (timeout !== null) {
|
|
1274
|
-
clearTimeout(timeout);
|
|
1275
|
-
}
|
|
1276
|
-
clearInterval(interval);
|
|
1277
|
-
resolve();
|
|
1278
|
-
if (originalOnEnd) {
|
|
1279
|
-
originalOnEnd(); // Call the original onEnd function if set
|
|
1280
|
-
}
|
|
1281
|
-
};
|
|
1357
|
+
}, waitTime)
|
|
1358
|
+
: null;
|
|
1359
|
+
waiter.resolve = () => { if (timeout)
|
|
1360
|
+
clearTimeout(timeout); resolve(); };
|
|
1361
|
+
waiter.reject = (e) => { if (timeout)
|
|
1362
|
+
clearTimeout(timeout); reject(e); };
|
|
1282
1363
|
});
|
|
1283
1364
|
}
|
|
1365
|
+
/** 内部调用:流结束时唤醒所有 waitForEndOfStream 等待者 */
|
|
1366
|
+
_notifyEndOfStream() {
|
|
1367
|
+
this.endFlag = true;
|
|
1368
|
+
const ws = this.endOfStreamWaiters.splice(0);
|
|
1369
|
+
for (const w of ws) {
|
|
1370
|
+
try {
|
|
1371
|
+
w.resolve();
|
|
1372
|
+
}
|
|
1373
|
+
catch { /* ignore */ }
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1284
1376
|
// 解析 WINDOW_UPDATE 帧
|
|
1285
1377
|
parseWindowUpdateFrame(frameBuffer, frameHeader) {
|
|
1286
1378
|
// WINDOW_UPDATE帧的payload固定为4字节
|