grpc-libp2p-client 0.0.39 → 0.0.41
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 -188
- 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 -188
- 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 +820 -582
- 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 +646 -414
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +646 -414
- 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 +220 -195
- package/src/dc-http2/stream.ts +84 -79
- package/src/index.ts +253 -187
|
@@ -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,85 @@ 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) {
|
|
917
|
+
// 确保 waitForEndOfStream 等待者得到通知,防止 operationPromise 后台挂死
|
|
918
|
+
if (!this.endFlag) {
|
|
919
|
+
this._notifyEndOfStream();
|
|
920
|
+
}
|
|
921
|
+
// 仅过滤我们自己主动触发的清理错误(reason 固定为 'Call cleanup' / 'unaryCall cleanup')。
|
|
922
|
+
// 不使用 /aborted/i,因为 libp2p / 浏览器网络层可能抛出含 "aborted" 字样的真实错误
|
|
923
|
+
// (如 "AbortError: The operation was aborted", "Connection aborted by peer"),
|
|
924
|
+
// 若误匹配会导致 reportError 不被调用,onErrorCallback 丢失,调用方误认为成功。
|
|
925
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
926
|
+
if (/cleanup/i.test(errMsg)) {
|
|
927
|
+
// 预期的主动清理,无需 re-throw,.catch in index.ts 不需要处理它
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
847
930
|
console.error("Error processing stream:", error);
|
|
848
931
|
throw error;
|
|
849
932
|
}
|
|
850
933
|
}
|
|
851
|
-
// 处理单个数据块
|
|
934
|
+
// 处理单个数据块 — 分段列表追加,避免每次 O(n) 全量拷贝
|
|
852
935
|
_processChunk(chunk) {
|
|
853
936
|
// chunk 是 Uint8ArrayList 或 Uint8Array
|
|
854
937
|
const newData = 'subarray' in chunk && typeof chunk.subarray === 'function'
|
|
855
938
|
? chunk.subarray()
|
|
856
939
|
: chunk;
|
|
857
|
-
//
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
940
|
+
// 追加到分段列表,O(1),不拷贝历史数据
|
|
941
|
+
if (newData.length > 0) {
|
|
942
|
+
this.bufferChunks.push(newData);
|
|
943
|
+
this.bufferTotalLength += newData.length;
|
|
944
|
+
}
|
|
945
|
+
// 将所有分段合并为一块后处理帧(只合并一次,后续 slice 替换)
|
|
946
|
+
// 仅在确实有完整帧时才触发合并,碎片仅 push 不合并
|
|
947
|
+
if (this.bufferTotalLength < 9)
|
|
948
|
+
return;
|
|
949
|
+
// 合并一次
|
|
950
|
+
const flat = this._flattenBuffer();
|
|
951
|
+
this.bufferChunks = [flat];
|
|
952
|
+
// bufferTotalLength 保持不变
|
|
862
953
|
// 持续处理所有完整的帧
|
|
863
954
|
let readOffset = 0;
|
|
864
|
-
while (
|
|
955
|
+
while (flat.length - readOffset >= 9) {
|
|
865
956
|
// 判断是否有HTTP/2前导
|
|
866
|
-
if (
|
|
957
|
+
if (flat.length - readOffset >= 24 && this.isHttp2Preface(flat.subarray(readOffset))) {
|
|
867
958
|
readOffset += 24;
|
|
868
959
|
// 发送SETTINGS帧
|
|
869
960
|
const settingFrame = Http2Frame.createSettingsFrame();
|
|
870
961
|
this.writer.write(settingFrame);
|
|
871
962
|
continue;
|
|
872
963
|
}
|
|
873
|
-
const frameHeader = this._parseFrameHeader(
|
|
964
|
+
const frameHeader = this._parseFrameHeader(flat.subarray(readOffset));
|
|
874
965
|
const totalFrameLength = 9 + frameHeader.length;
|
|
875
966
|
// 检查是否有完整的帧
|
|
876
|
-
if (
|
|
967
|
+
if (flat.length - readOffset < totalFrameLength) {
|
|
877
968
|
break;
|
|
878
969
|
}
|
|
879
|
-
//
|
|
880
|
-
const frameData =
|
|
970
|
+
// 获取完整帧数据(subarray 视图,零拷贝)
|
|
971
|
+
const frameData = flat.subarray(readOffset, readOffset + totalFrameLength);
|
|
881
972
|
// 处理不同类型的帧
|
|
882
973
|
this._handleFrame(frameHeader, frameData).catch((err) => {
|
|
883
974
|
console.error("Error handling frame:", err);
|
|
884
975
|
});
|
|
885
|
-
// 移动偏移量
|
|
886
976
|
readOffset += totalFrameLength;
|
|
887
977
|
}
|
|
978
|
+
// 保留未消费的尾部字节(slice 一次,后续仍分段追加)
|
|
888
979
|
if (readOffset > 0) {
|
|
889
|
-
|
|
980
|
+
if (readOffset >= flat.length) {
|
|
981
|
+
this.bufferChunks = [];
|
|
982
|
+
this.bufferTotalLength = 0;
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
const remaining = flat.slice(readOffset);
|
|
986
|
+
this.bufferChunks = [remaining];
|
|
987
|
+
this.bufferTotalLength = remaining.length;
|
|
988
|
+
}
|
|
890
989
|
}
|
|
891
990
|
}
|
|
892
991
|
isHttp2Preface(buffer) {
|
|
@@ -898,50 +997,67 @@ class HTTP2Parser {
|
|
|
898
997
|
}
|
|
899
998
|
return true;
|
|
900
999
|
}
|
|
901
|
-
//
|
|
902
|
-
_oldProcessStream_removed() {
|
|
903
|
-
// 这个方法已被上面的事件驱动实现替代
|
|
904
|
-
}
|
|
905
|
-
// 等待SETTINGS ACK
|
|
1000
|
+
// 等待SETTINGS ACK — 事件驱动,无轮询
|
|
906
1001
|
waitForSettingsAck() {
|
|
907
1002
|
return new Promise((resolve, reject) => {
|
|
908
1003
|
if (this.settingsAckReceived) {
|
|
909
1004
|
resolve();
|
|
910
1005
|
return;
|
|
911
1006
|
}
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
clearInterval(interval);
|
|
915
|
-
clearTimeout(timeout);
|
|
916
|
-
resolve();
|
|
917
|
-
}
|
|
918
|
-
}, 100);
|
|
1007
|
+
const waiter = { resolve, reject };
|
|
1008
|
+
this.settingsAckWaiters.push(waiter);
|
|
919
1009
|
const timeout = setTimeout(() => {
|
|
920
|
-
|
|
1010
|
+
const idx = this.settingsAckWaiters.indexOf(waiter);
|
|
1011
|
+
if (idx >= 0)
|
|
1012
|
+
this.settingsAckWaiters.splice(idx, 1);
|
|
921
1013
|
reject(new Error("Settings ACK timeout"));
|
|
922
1014
|
}, 30000);
|
|
1015
|
+
// 覆盖 resolve 以便超时前自动清理定时器
|
|
1016
|
+
waiter.resolve = () => { clearTimeout(timeout); resolve(); };
|
|
1017
|
+
waiter.reject = (e) => { clearTimeout(timeout); reject(e); };
|
|
923
1018
|
});
|
|
924
1019
|
}
|
|
925
|
-
|
|
1020
|
+
/** 内部调用:SETTINGS ACK 收到时唤醒所有等待者 */
|
|
1021
|
+
_notifySettingsAck() {
|
|
1022
|
+
this.settingsAckReceived = true;
|
|
1023
|
+
const ws = this.settingsAckWaiters.splice(0);
|
|
1024
|
+
for (const w of ws) {
|
|
1025
|
+
try {
|
|
1026
|
+
w.resolve();
|
|
1027
|
+
}
|
|
1028
|
+
catch { /* ignore */ }
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
// 等待接收来自对端的 SETTINGS(非 ACK)— 事件驱动,无轮询
|
|
926
1032
|
waitForPeerSettings(timeoutMs = 30000) {
|
|
927
1033
|
return new Promise((resolve, reject) => {
|
|
928
1034
|
if (this.peerSettingsReceived) {
|
|
929
1035
|
resolve();
|
|
930
1036
|
return;
|
|
931
1037
|
}
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
clearInterval(interval);
|
|
935
|
-
clearTimeout(timeout);
|
|
936
|
-
resolve();
|
|
937
|
-
}
|
|
938
|
-
}, 100);
|
|
1038
|
+
const waiter = { resolve, reject };
|
|
1039
|
+
this.peerSettingsWaiters.push(waiter);
|
|
939
1040
|
const timeout = setTimeout(() => {
|
|
940
|
-
|
|
1041
|
+
const idx = this.peerSettingsWaiters.indexOf(waiter);
|
|
1042
|
+
if (idx >= 0)
|
|
1043
|
+
this.peerSettingsWaiters.splice(idx, 1);
|
|
941
1044
|
reject(new Error("Peer SETTINGS timeout"));
|
|
942
1045
|
}, timeoutMs);
|
|
1046
|
+
waiter.resolve = () => { clearTimeout(timeout); resolve(); };
|
|
1047
|
+
waiter.reject = (e) => { clearTimeout(timeout); reject(e); };
|
|
943
1048
|
});
|
|
944
1049
|
}
|
|
1050
|
+
/** 内部调用:收到对端 SETTINGS(非 ACK)时唤醒等待者 */
|
|
1051
|
+
_notifyPeerSettings() {
|
|
1052
|
+
this.peerSettingsReceived = true;
|
|
1053
|
+
const ws = this.peerSettingsWaiters.splice(0);
|
|
1054
|
+
for (const w of ws) {
|
|
1055
|
+
try {
|
|
1056
|
+
w.resolve();
|
|
1057
|
+
}
|
|
1058
|
+
catch { /* ignore */ }
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
945
1061
|
// 注册我们要发送数据的出站流(用于初始化该流的对端窗口)
|
|
946
1062
|
registerOutboundStream(streamId) {
|
|
947
1063
|
if (!this.sendStreamWindows.has(streamId)) {
|
|
@@ -968,54 +1084,51 @@ class HTTP2Parser {
|
|
|
968
1084
|
this.sendConnWindow = Math.min(0x7fffffff, this.sendConnWindow + bytes);
|
|
969
1085
|
const cur = this.sendStreamWindows.get(streamId) ?? 0;
|
|
970
1086
|
this.sendStreamWindows.set(streamId, Math.min(0x7fffffff, cur + bytes));
|
|
1087
|
+
// 窗口增大,唤醒等待者
|
|
1088
|
+
this._wakeWindowWaiters();
|
|
971
1089
|
}
|
|
972
|
-
//
|
|
973
|
-
|
|
974
|
-
const
|
|
1090
|
+
// 等待可用发送窗口 — 事件驱动,WINDOW_UPDATE/SETTINGS 收到时直接唤醒
|
|
1091
|
+
waitForSendWindow(streamId, minBytes = 1, timeoutMs = 30000) {
|
|
1092
|
+
const { conn, stream } = this.getSendWindows(streamId);
|
|
1093
|
+
if (conn >= minBytes && stream >= minBytes)
|
|
1094
|
+
return Promise.resolve();
|
|
975
1095
|
return new Promise((resolve, reject) => {
|
|
976
|
-
let interval = null;
|
|
977
1096
|
let settled = false;
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1097
|
+
const timeout = timeoutMs > 0
|
|
1098
|
+
? setTimeout(() => {
|
|
1099
|
+
if (settled)
|
|
1100
|
+
return;
|
|
1101
|
+
settled = true;
|
|
1102
|
+
const idx = this.sendWindowWaiters.findIndex(w => w.resolve === resolveWrap);
|
|
1103
|
+
if (idx >= 0)
|
|
1104
|
+
this.sendWindowWaiters.splice(idx, 1);
|
|
1105
|
+
reject(new Error('Send window wait timeout'));
|
|
1106
|
+
}, timeoutMs)
|
|
1107
|
+
: undefined;
|
|
1108
|
+
const resolveWrap = () => {
|
|
1109
|
+
if (settled)
|
|
1110
|
+
return;
|
|
1111
|
+
const { conn: c2, stream: s2 } = this.getSendWindows(streamId);
|
|
1112
|
+
if (c2 >= minBytes && s2 >= minBytes) {
|
|
1113
|
+
settled = true;
|
|
1114
|
+
if (timeout)
|
|
1115
|
+
clearTimeout(timeout);
|
|
1116
|
+
resolve();
|
|
990
1117
|
}
|
|
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;
|
|
1118
|
+
else {
|
|
1119
|
+
// 窗口仍不够,重新入队等待下一次更新
|
|
1120
|
+
this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
|
|
1001
1121
|
}
|
|
1002
|
-
return false;
|
|
1003
1122
|
};
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1123
|
+
const rejectWrap = (e) => {
|
|
1124
|
+
if (settled)
|
|
1125
|
+
return;
|
|
1126
|
+
settled = true;
|
|
1127
|
+
if (timeout)
|
|
1128
|
+
clearTimeout(timeout);
|
|
1129
|
+
reject(e);
|
|
1008
1130
|
};
|
|
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);
|
|
1131
|
+
this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
|
|
1019
1132
|
});
|
|
1020
1133
|
}
|
|
1021
1134
|
// 处理单个帧
|
|
@@ -1023,7 +1136,7 @@ class HTTP2Parser {
|
|
|
1023
1136
|
switch (frameHeader.type) {
|
|
1024
1137
|
case FRAME_TYPES.SETTINGS:
|
|
1025
1138
|
if ((frameHeader.flags & FRAME_FLAGS.ACK) === FRAME_FLAGS.ACK) {
|
|
1026
|
-
this.
|
|
1139
|
+
this._notifySettingsAck();
|
|
1027
1140
|
}
|
|
1028
1141
|
else {
|
|
1029
1142
|
//接收到Setting请求,进行解析
|
|
@@ -1033,10 +1146,12 @@ class HTTP2Parser {
|
|
|
1033
1146
|
for (let i = 0; i < settingsPayload.length; i += 6) {
|
|
1034
1147
|
// 正确解析:2字节ID + 4字节值
|
|
1035
1148
|
const id = (settingsPayload[i] << 8) | settingsPayload[i + 1];
|
|
1036
|
-
|
|
1149
|
+
// >>> 0 将结果转为无符号 32 位整数,防止高位为 1 时(如 0xffffffff)
|
|
1150
|
+
// 被 JS 按有符号解读为负数,导致 maxConcurrentStreams 等字段为负值
|
|
1151
|
+
const value = ((settingsPayload[i + 2] << 24) |
|
|
1037
1152
|
(settingsPayload[i + 3] << 16) |
|
|
1038
1153
|
(settingsPayload[i + 4] << 8) |
|
|
1039
|
-
settingsPayload[i + 5];
|
|
1154
|
+
settingsPayload[i + 5]) >>> 0;
|
|
1040
1155
|
if (id === 4) {
|
|
1041
1156
|
// SETTINGS_INITIAL_WINDOW_SIZE
|
|
1042
1157
|
this.defaultStreamWindowSize = value; // 我方接收窗口(入站)
|
|
@@ -1073,47 +1188,47 @@ class HTTP2Parser {
|
|
|
1073
1188
|
if (this.onSettings) {
|
|
1074
1189
|
this.onSettings(frameHeader);
|
|
1075
1190
|
}
|
|
1076
|
-
// 标记已收到对端 SETTINGS
|
|
1077
|
-
this.
|
|
1078
|
-
//
|
|
1079
|
-
|
|
1080
|
-
waiters.forEach(fn => { try {
|
|
1081
|
-
fn();
|
|
1082
|
-
}
|
|
1083
|
-
catch (e) {
|
|
1084
|
-
console.debug('waiter error', e);
|
|
1085
|
-
} });
|
|
1191
|
+
// 标记已收到对端 SETTINGS 并唤醒等待者
|
|
1192
|
+
this._notifyPeerSettings();
|
|
1193
|
+
// 唤醒发送窗口等待者(以防部分实现通过 SETTINGS 改变有效窗口)
|
|
1194
|
+
this._wakeWindowWaiters();
|
|
1086
1195
|
}
|
|
1087
1196
|
break;
|
|
1088
|
-
case FRAME_TYPES.DATA:
|
|
1197
|
+
case FRAME_TYPES.DATA: {
|
|
1089
1198
|
// 处理数据帧
|
|
1090
1199
|
if (this.onData) {
|
|
1091
1200
|
this.onData(frameData.slice(9), frameHeader); // 跳过帧头
|
|
1092
1201
|
}
|
|
1093
1202
|
// 更新流窗口和连接窗口
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1203
|
+
// 仅在帧有实际数据时才发送 WINDOW_UPDATE:
|
|
1204
|
+
// RFC 7540 §6.9.1 明确禁止 increment=0 的 WINDOW_UPDATE,
|
|
1205
|
+
// 服务端必须以 PROTOCOL_ERROR 响应,会导致连接被强制关闭。
|
|
1206
|
+
// 空 DATA 帧(如纯 END_STREAM 帧)length=0,不需要归还窗口。
|
|
1207
|
+
const dataLength = frameHeader.length ?? 0;
|
|
1208
|
+
if (dataLength > 0) {
|
|
1209
|
+
try {
|
|
1210
|
+
// 更新流级别的窗口
|
|
1211
|
+
if (frameHeader.streamId !== 0) {
|
|
1212
|
+
const streamWindowUpdate = Http2Frame.createWindowUpdateFrame(frameHeader.streamId, dataLength);
|
|
1213
|
+
this.writer.write(streamWindowUpdate);
|
|
1214
|
+
}
|
|
1215
|
+
// 更新连接级别的窗口
|
|
1216
|
+
const connWindowUpdate = Http2Frame.createWindowUpdateFrame(0, dataLength);
|
|
1217
|
+
this.writer.write(connWindowUpdate);
|
|
1218
|
+
}
|
|
1219
|
+
catch (err) {
|
|
1220
|
+
console.error("[HTTP2] Error sending window update:", err);
|
|
1099
1221
|
}
|
|
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
1222
|
}
|
|
1107
1223
|
//判断是否是最后一个帧
|
|
1108
1224
|
if ((frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
|
|
1109
1225
|
FRAME_FLAGS.END_STREAM) {
|
|
1110
|
-
this.
|
|
1111
|
-
|
|
1112
|
-
this.onEnd();
|
|
1113
|
-
}
|
|
1226
|
+
this.onEnd?.();
|
|
1227
|
+
this._notifyEndOfStream();
|
|
1114
1228
|
return;
|
|
1115
1229
|
}
|
|
1116
1230
|
break;
|
|
1231
|
+
}
|
|
1117
1232
|
case FRAME_TYPES.HEADERS:
|
|
1118
1233
|
// 处理头部帧
|
|
1119
1234
|
if (this.onHeaders) {
|
|
@@ -1122,35 +1237,26 @@ class HTTP2Parser {
|
|
|
1122
1237
|
//判断是否是最后一个帧
|
|
1123
1238
|
if ((frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
|
|
1124
1239
|
FRAME_FLAGS.END_STREAM) {
|
|
1125
|
-
this.
|
|
1126
|
-
|
|
1127
|
-
this.onEnd();
|
|
1128
|
-
}
|
|
1240
|
+
this.onEnd?.();
|
|
1241
|
+
this._notifyEndOfStream();
|
|
1129
1242
|
return;
|
|
1130
1243
|
}
|
|
1131
1244
|
break;
|
|
1132
1245
|
case FRAME_TYPES.WINDOW_UPDATE:
|
|
1133
|
-
//
|
|
1134
|
-
this.handleWindowUpdateFrame(frameHeader, frameData);
|
|
1135
|
-
// 更新发送窗口(对端接收窗口)
|
|
1246
|
+
// 处理窗口更新帧(同时更新接收侧诊断计数器和发送侧流控窗口,只解析一次)
|
|
1136
1247
|
try {
|
|
1137
|
-
const
|
|
1248
|
+
const result = this.handleWindowUpdateFrame(frameHeader, frameData);
|
|
1249
|
+
// 更新发送方向窗口(对端的接收窗口)
|
|
1138
1250
|
if (frameHeader.streamId === 0) {
|
|
1139
|
-
this.sendConnWindow +=
|
|
1251
|
+
this.sendConnWindow += result.windowSizeIncrement;
|
|
1140
1252
|
}
|
|
1141
1253
|
else {
|
|
1142
1254
|
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();
|
|
1255
|
+
this.sendStreamWindows.set(frameHeader.streamId, cur + result.windowSizeIncrement);
|
|
1148
1256
|
}
|
|
1149
|
-
|
|
1150
|
-
console.debug('waiter error', e);
|
|
1151
|
-
} });
|
|
1257
|
+
this._wakeWindowWaiters();
|
|
1152
1258
|
}
|
|
1153
|
-
catch { /* ignore WINDOW_UPDATE parse errors */ }
|
|
1259
|
+
catch { /* ignore WINDOW_UPDATE parse errors (e.g. increment=0 is RFC PROTOCOL_ERROR) */ }
|
|
1154
1260
|
break;
|
|
1155
1261
|
case FRAME_TYPES.PING:
|
|
1156
1262
|
// 处理PING帧
|
|
@@ -1179,13 +1285,13 @@ class HTTP2Parser {
|
|
|
1179
1285
|
catch (err) {
|
|
1180
1286
|
console.error('Error during GOAWAY callback:', err);
|
|
1181
1287
|
}
|
|
1182
|
-
this.endFlag = true;
|
|
1183
1288
|
try {
|
|
1184
1289
|
this.onEnd?.();
|
|
1185
1290
|
}
|
|
1186
1291
|
catch (err) {
|
|
1187
1292
|
console.error('Error during GOAWAY onEnd callback:', err);
|
|
1188
1293
|
}
|
|
1294
|
+
this._notifyEndOfStream();
|
|
1189
1295
|
break;
|
|
1190
1296
|
}
|
|
1191
1297
|
// case FRAME_TYPES.PUSH_PROMISE:
|
|
@@ -1193,10 +1299,8 @@ class HTTP2Parser {
|
|
|
1193
1299
|
// this.handlePushPromiseFrame(frameHeader, frameData);
|
|
1194
1300
|
// break;
|
|
1195
1301
|
case FRAME_TYPES.RST_STREAM:
|
|
1196
|
-
this.
|
|
1197
|
-
|
|
1198
|
-
this.onEnd();
|
|
1199
|
-
}
|
|
1302
|
+
this.onEnd?.();
|
|
1303
|
+
this._notifyEndOfStream();
|
|
1200
1304
|
break;
|
|
1201
1305
|
default:
|
|
1202
1306
|
console.debug("Unknown frame type:", frameHeader.type);
|
|
@@ -1206,7 +1310,8 @@ class HTTP2Parser {
|
|
|
1206
1310
|
const length = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
|
|
1207
1311
|
const type = buffer[3];
|
|
1208
1312
|
const flags = buffer[4];
|
|
1209
|
-
|
|
1313
|
+
// RFC 7540 §4.1: most significant bit is reserved and MUST be ignored on receipt
|
|
1314
|
+
const streamId = ((buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8]) & 0x7fffffff;
|
|
1210
1315
|
return {
|
|
1211
1316
|
length,
|
|
1212
1317
|
type,
|
|
@@ -1235,52 +1340,40 @@ class HTTP2Parser {
|
|
|
1235
1340
|
throw error;
|
|
1236
1341
|
}
|
|
1237
1342
|
}
|
|
1238
|
-
|
|
1343
|
+
// 等待流结束 — 事件驱动,onEnd 触发时直接唤醒,无 setInterval 轮询
|
|
1239
1344
|
waitForEndOfStream(waitTime) {
|
|
1240
1345
|
return new Promise((resolve, reject) => {
|
|
1241
|
-
// If the stream has already ended, resolve immediately
|
|
1242
1346
|
if (this.endFlag) {
|
|
1243
1347
|
resolve();
|
|
1244
1348
|
return;
|
|
1245
1349
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1350
|
+
const waiter = { resolve, reject };
|
|
1351
|
+
this.endOfStreamWaiters.push(waiter);
|
|
1352
|
+
const timeout = waitTime > 0
|
|
1353
|
+
? setTimeout(() => {
|
|
1354
|
+
const idx = this.endOfStreamWaiters.indexOf(waiter);
|
|
1355
|
+
if (idx >= 0)
|
|
1356
|
+
this.endOfStreamWaiters.splice(idx, 1);
|
|
1251
1357
|
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
|
-
};
|
|
1358
|
+
}, waitTime)
|
|
1359
|
+
: null;
|
|
1360
|
+
waiter.resolve = () => { if (timeout)
|
|
1361
|
+
clearTimeout(timeout); resolve(); };
|
|
1362
|
+
waiter.reject = (e) => { if (timeout)
|
|
1363
|
+
clearTimeout(timeout); reject(e); };
|
|
1282
1364
|
});
|
|
1283
1365
|
}
|
|
1366
|
+
/** 内部调用:流结束时唤醒所有 waitForEndOfStream 等待者 */
|
|
1367
|
+
_notifyEndOfStream() {
|
|
1368
|
+
this.endFlag = true;
|
|
1369
|
+
const ws = this.endOfStreamWaiters.splice(0);
|
|
1370
|
+
for (const w of ws) {
|
|
1371
|
+
try {
|
|
1372
|
+
w.resolve();
|
|
1373
|
+
}
|
|
1374
|
+
catch { /* ignore */ }
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1284
1377
|
// 解析 WINDOW_UPDATE 帧
|
|
1285
1378
|
parseWindowUpdateFrame(frameBuffer, frameHeader) {
|
|
1286
1379
|
// WINDOW_UPDATE帧的payload固定为4字节
|