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.
@@ -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 >> bits) & 0xFF);
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
- current = (current << (8 - bits)) | ((1 << (8 - bits)) - 1);
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.staticTable[staticIndex];
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 [staticIndex, nameIndex] = this.decodeInteger(buffer, index, 6);
478
- index = nameIndex;
491
+ const [nameIndex, nextIndex] = this.decodeInteger(buffer, index, 6);
492
+ index = nextIndex;
479
493
  let name;
480
- if (staticIndex > 0) {
481
- const headerField = this.staticTable[staticIndex];
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
- return this.decodeLiteralHeaderWithIndexing(buffer, index);
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
- [SETTINGS_PARAMETERS.ENABLE_PUSH]: 1,
611
+ // gRPC 客户端不使用 Server Push,禁用以避免无效的 PUSH_PROMISE 处理
612
+ [SETTINGS_PARAMETERS.ENABLE_PUSH]: 0,
582
613
  [SETTINGS_PARAMETERS.MAX_CONCURRENT_STREAMS]: 100,
583
- [SETTINGS_PARAMETERS.INITIAL_WINDOW_SIZE]: 16 << 10, // 16k
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
- // HTTP/2 帧头为 9 字节
645
- const maxDataPerFrame = maxFrameSize - 9;
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': 'localhost',
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
- this.buffer = new Uint8Array(0);
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
- // 原作者之前的 O(N) 内存拷贝优化被保留,去掉了存在 onEnd 竞态的 setTimeout
860
- const newBuffer = new Uint8Array(this.buffer.length + newData.length);
861
- newBuffer.set(this.buffer);
862
- newBuffer.set(newData, this.buffer.length);
863
- this.buffer = newBuffer;
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 (this.buffer.length - readOffset >= 9) {
957
+ while (flat.length - readOffset >= 9) {
867
958
  // 判断是否有HTTP/2前导
868
- if (this.buffer.length - readOffset >= 24 && this.isHttp2Preface(this.buffer.subarray(readOffset))) {
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(this.buffer.subarray(readOffset));
966
+ const frameHeader = this._parseFrameHeader(flat.subarray(readOffset));
876
967
  const totalFrameLength = 9 + frameHeader.length;
877
968
  // 检查是否有完整的帧
878
- if (this.buffer.length - readOffset < totalFrameLength) {
969
+ if (flat.length - readOffset < totalFrameLength) {
879
970
  break;
880
971
  }
881
- // 获取完整帧数据
882
- const frameData = this.buffer.subarray(readOffset, readOffset + totalFrameLength);
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
- this.buffer = this.buffer.slice(readOffset);
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
- // 移除之前的 for await 循环代码
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 interval = setInterval(() => {
915
- if (this.settingsAckReceived) {
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
- clearInterval(interval);
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
- // 等待接收来自对端的 SETTINGS(非 ACK
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 interval = setInterval(() => {
935
- if (this.peerSettingsReceived) {
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
- clearInterval(interval);
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
- // 等待可用发送窗口(两个窗口都需要 >0)
975
- async waitForSendWindow(streamId, minBytes = 1, timeoutMs = 30000) {
976
- const start = Date.now();
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 check = () => {
981
- const { conn, stream } = this.getSendWindows(streamId);
982
- if (conn >= minBytes && stream >= minBytes) {
983
- if (!settled) {
984
- settled = true;
985
- if (interval) {
986
- clearInterval(interval);
987
- interval = null;
988
- }
989
- resolve();
990
- }
991
- return true;
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
- if (Date.now() - start > timeoutMs) {
994
- if (!settled) {
995
- settled = true;
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
- if (check())
1007
- return;
1008
- const tick = () => {
1009
- if (!check()) ;
1125
+ const rejectWrap = (e) => {
1126
+ if (settled)
1127
+ return;
1128
+ settled = true;
1129
+ if (timeout)
1130
+ clearTimeout(timeout);
1131
+ reject(e);
1010
1132
  };
1011
- const wake = () => { tick(); };
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.settingsAckReceived = true;
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
- const value = (settingsPayload[i + 2] << 24) |
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.peerSettingsReceived = true;
1080
- // 唤醒等待窗口(以防部分实现通过 SETTINGS 改变有效窗口)
1081
- const waiters = this.sendWindowWaiters.splice(0);
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
- try {
1097
- // 更新流级别的窗口
1098
- if (frameHeader.streamId !== 0) {
1099
- const streamWindowUpdate = Http2Frame.createWindowUpdateFrame(frameHeader.streamId, frameHeader.length ?? 0);
1100
- this.writer.write(streamWindowUpdate);
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.endFlag = true;
1113
- if (this.onEnd) {
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.endFlag = true;
1128
- if (this.onEnd) {
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 inc = this.parseWindowUpdateFrame(frameData, frameHeader).windowSizeIncrement;
1250
+ const result = this.handleWindowUpdateFrame(frameHeader, frameData);
1251
+ // 更新发送方向窗口(对端的接收窗口)
1140
1252
  if (frameHeader.streamId === 0) {
1141
- this.sendConnWindow += inc;
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 + inc);
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
- catch (e) {
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.endFlag = true;
1199
- if (this.onEnd) {
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
- const streamId = (buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8];
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
- // 如果是0 ,则不设置超时
1249
- let timeout = null;
1250
- if (waitTime > 0) {
1251
- timeout = setTimeout(() => {
1252
- clearInterval(interval);
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
- // Check interval for real-time endFlag monitoring
1257
- const checkInterval = 100; // Check every 100 milliseconds
1258
- // Set an interval to check the endFlag regularly
1259
- const interval = setInterval(() => {
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字节