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