grpc-libp2p-client 0.0.38 → 0.0.40

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