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.
@@ -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 >> bits) & 0xFF);
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
- current = (current << (8 - bits)) | ((1 << (8 - bits)) - 1);
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.staticTable[staticIndex];
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 [staticIndex, nameIndex] = this.decodeInteger(buffer, index, 6);
476
- index = nameIndex;
489
+ const [nameIndex, nextIndex] = this.decodeInteger(buffer, index, 6);
490
+ index = nextIndex;
477
491
  let name;
478
- if (staticIndex > 0) {
479
- const headerField = this.staticTable[staticIndex];
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
- return this.decodeLiteralHeaderWithIndexing(buffer, index);
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
- [SETTINGS_PARAMETERS.ENABLE_PUSH]: 1,
609
+ // gRPC 客户端不使用 Server Push,禁用以避免无效的 PUSH_PROMISE 处理
610
+ [SETTINGS_PARAMETERS.ENABLE_PUSH]: 0,
580
611
  [SETTINGS_PARAMETERS.MAX_CONCURRENT_STREAMS]: 100,
581
- [SETTINGS_PARAMETERS.INITIAL_WINDOW_SIZE]: 16 << 10, // 16k
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
- // HTTP/2 帧头为 9 字节
643
- const maxDataPerFrame = maxFrameSize - 9;
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': 'localhost',
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
- this.buffer = new Uint8Array(0);
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
- // 原作者之前的 O(N) 内存拷贝优化被保留,去掉了存在 onEnd 竞态的 setTimeout
858
- const newBuffer = new Uint8Array(this.buffer.length + newData.length);
859
- newBuffer.set(this.buffer);
860
- newBuffer.set(newData, this.buffer.length);
861
- this.buffer = newBuffer;
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 (this.buffer.length - readOffset >= 9) {
955
+ while (flat.length - readOffset >= 9) {
865
956
  // 判断是否有HTTP/2前导
866
- if (this.buffer.length - readOffset >= 24 && this.isHttp2Preface(this.buffer.subarray(readOffset))) {
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(this.buffer.subarray(readOffset));
964
+ const frameHeader = this._parseFrameHeader(flat.subarray(readOffset));
874
965
  const totalFrameLength = 9 + frameHeader.length;
875
966
  // 检查是否有完整的帧
876
- if (this.buffer.length - readOffset < totalFrameLength) {
967
+ if (flat.length - readOffset < totalFrameLength) {
877
968
  break;
878
969
  }
879
- // 获取完整帧数据
880
- const frameData = this.buffer.subarray(readOffset, readOffset + totalFrameLength);
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
- this.buffer = this.buffer.slice(readOffset);
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
- // 移除之前的 for await 循环代码
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 interval = setInterval(() => {
913
- if (this.settingsAckReceived) {
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
- clearInterval(interval);
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
- // 等待接收来自对端的 SETTINGS(非 ACK
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 interval = setInterval(() => {
933
- if (this.peerSettingsReceived) {
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
- clearInterval(interval);
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
- // 等待可用发送窗口(两个窗口都需要 >0)
973
- async waitForSendWindow(streamId, minBytes = 1, timeoutMs = 30000) {
974
- const start = Date.now();
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 check = () => {
979
- const { conn, stream } = this.getSendWindows(streamId);
980
- if (conn >= minBytes && stream >= minBytes) {
981
- if (!settled) {
982
- settled = true;
983
- if (interval) {
984
- clearInterval(interval);
985
- interval = null;
986
- }
987
- resolve();
988
- }
989
- return true;
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
- if (Date.now() - start > timeoutMs) {
992
- if (!settled) {
993
- settled = true;
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
- if (check())
1005
- return;
1006
- const tick = () => {
1007
- if (!check()) ;
1123
+ const rejectWrap = (e) => {
1124
+ if (settled)
1125
+ return;
1126
+ settled = true;
1127
+ if (timeout)
1128
+ clearTimeout(timeout);
1129
+ reject(e);
1008
1130
  };
1009
- const wake = () => { tick(); };
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.settingsAckReceived = true;
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
- const value = (settingsPayload[i + 2] << 24) |
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.peerSettingsReceived = true;
1078
- // 唤醒等待窗口(以防部分实现通过 SETTINGS 改变有效窗口)
1079
- const waiters = this.sendWindowWaiters.splice(0);
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
- try {
1095
- // 更新流级别的窗口
1096
- if (frameHeader.streamId !== 0) {
1097
- const streamWindowUpdate = Http2Frame.createWindowUpdateFrame(frameHeader.streamId, frameHeader.length ?? 0);
1098
- this.writer.write(streamWindowUpdate);
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.endFlag = true;
1111
- if (this.onEnd) {
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.endFlag = true;
1126
- if (this.onEnd) {
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 inc = this.parseWindowUpdateFrame(frameData, frameHeader).windowSizeIncrement;
1248
+ const result = this.handleWindowUpdateFrame(frameHeader, frameData);
1249
+ // 更新发送方向窗口(对端的接收窗口)
1138
1250
  if (frameHeader.streamId === 0) {
1139
- this.sendConnWindow += inc;
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 + inc);
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
- catch (e) {
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.endFlag = true;
1197
- if (this.onEnd) {
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
- const streamId = (buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8];
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
- // 如果是0 ,则不设置超时
1247
- let timeout = null;
1248
- if (waitTime > 0) {
1249
- timeout = setTimeout(() => {
1250
- clearInterval(interval);
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
- // Check interval for real-time endFlag monitoring
1255
- const checkInterval = 100; // Check every 100 milliseconds
1256
- // Set an interval to check the endFlag regularly
1257
- const interval = setInterval(() => {
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字节