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