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.
@@ -11,7 +11,12 @@ type ParserOptions = {
11
11
  const HTTP2_PREFACE = new TextEncoder().encode("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
12
12
 
13
13
  export class HTTP2Parser {
14
- buffer: Uint8Array;
14
+ /** 分段缓冲:避免每次 chunk 到达时 O(n) 全量拷贝 */
15
+ private bufferChunks: Uint8Array[] = [];
16
+ private bufferTotalLength = 0;
17
+ /** 兼容旧代码读取 buffer —— 仅在必须全量访问时调用 _flattenBuffer() */
18
+ get buffer(): Uint8Array { return this._flattenBuffer(); }
19
+ set buffer(v: Uint8Array) { this.bufferChunks = v.length ? [v] : []; this.bufferTotalLength = v.length; }
15
20
  settingsAckReceived: boolean;
16
21
  peerSettingsReceived: boolean;
17
22
  connectionWindowSize: number;
@@ -21,7 +26,11 @@ export class HTTP2Parser {
21
26
  sendConnWindow: number;
22
27
  sendStreamWindows: Map<number, number>;
23
28
  peerInitialStreamWindow: number;
24
- private sendWindowWaiters: Array<() => void>;
29
+ private sendWindowWaiters: Array<{ resolve: () => void; reject: (e: Error) => void; cleanup?: () => void }>;
30
+ // 事件驱动等待器
31
+ private settingsAckWaiters: Array<{ resolve: () => void; reject: (e: Error) => void }>;
32
+ private peerSettingsWaiters: Array<{ resolve: () => void; reject: (e: Error) => void }>;
33
+ private endOfStreamWaiters: Array<{ resolve: () => void; reject: (e: Error) => void }>;
25
34
  onSettings?: (frameHeader: Frame) => void;
26
35
  onData?: (payload: Uint8Array, frameHeader: Frame) => void;
27
36
  onEnd?: () => void;
@@ -33,7 +42,8 @@ export class HTTP2Parser {
33
42
  private readonly compatibilityMode: boolean;
34
43
 
35
44
  constructor(writer: StreamWriter, options?: ParserOptions) {
36
- this.buffer = new Uint8Array(0);
45
+ this.bufferChunks = [];
46
+ this.bufferTotalLength = 0;
37
47
  this.settingsAckReceived = false;
38
48
  this.peerSettingsReceived = false;
39
49
  // 初始化连接级别的流控制窗口大小(默认值:65,535)
@@ -47,6 +57,9 @@ export class HTTP2Parser {
47
57
  this.sendStreamWindows = new Map();
48
58
  this.peerInitialStreamWindow = 65535;
49
59
  this.sendWindowWaiters = [];
60
+ this.settingsAckWaiters = [];
61
+ this.peerSettingsWaiters = [];
62
+ this.endOfStreamWaiters = [];
50
63
  // 结束标志
51
64
  this.endFlag = false;
52
65
 
@@ -54,6 +67,22 @@ export class HTTP2Parser {
54
67
  this.compatibilityMode = options?.compatibilityMode ?? false;
55
68
  }
56
69
 
70
+ /** 将所有分段合并为一个连续 Uint8Array(仅在必要时调用)*/
71
+ private _flattenBuffer(): Uint8Array {
72
+ if (this.bufferChunks.length === 0) return new Uint8Array(0);
73
+ if (this.bufferChunks.length === 1) return this.bufferChunks[0];
74
+ const out = new Uint8Array(this.bufferTotalLength);
75
+ let off = 0;
76
+ for (const c of this.bufferChunks) { out.set(c, off); off += c.length; }
77
+ return out;
78
+ }
79
+
80
+ /** 唤醒所有发送窗口等待者 */
81
+ private _wakeWindowWaiters() {
82
+ const ws = this.sendWindowWaiters.splice(0);
83
+ for (const w of ws) { try { w.resolve(); } catch { /* ignore */ } }
84
+ }
85
+
57
86
  // 持续处理流数据
58
87
  async processStream(stream: Stream) {
59
88
  try {
@@ -61,68 +90,97 @@ export class HTTP2Parser {
61
90
  for await (const chunk of stream) {
62
91
  this._processChunk(chunk);
63
92
  }
64
-
93
+
65
94
  // Stream 结束后的清理工作
66
95
  if (!this.compatibilityMode && !this.endFlag) {
67
- this.endFlag = true;
68
- try {
69
- this.onEnd?.();
70
- } catch (err) {
71
- console.error("Error during onEnd callback:", err);
72
- }
96
+ try { this.onEnd?.(); } catch (err) { console.error("Error during onEnd callback:", err); }
97
+ }
98
+ // 无论何种模式,stream 结束时都通知 waitForEndOfStream 等待者,
99
+ // 防止 compatibilityMode=true(server-streaming)时 waitForEndOfStream(0) 永久挂死
100
+ if (!this.endFlag) {
101
+ this._notifyEndOfStream();
73
102
  }
74
103
  } catch (error) {
104
+ // 确保 waitForEndOfStream 等待者得到通知,防止 operationPromise 后台挂死
105
+ if (!this.endFlag) {
106
+ this._notifyEndOfStream();
107
+ }
108
+ // 仅过滤我们自己主动触发的清理错误(reason 固定为 'Call cleanup' / 'unaryCall cleanup')。
109
+ // 不使用 /aborted/i,因为 libp2p / 浏览器网络层可能抛出含 "aborted" 字样的真实错误
110
+ // (如 "AbortError: The operation was aborted", "Connection aborted by peer"),
111
+ // 若误匹配会导致 reportError 不被调用,onErrorCallback 丢失,调用方误认为成功。
112
+ const errMsg = error instanceof Error ? error.message : String(error);
113
+ if (/cleanup/i.test(errMsg)) {
114
+ // 预期的主动清理,无需 re-throw,.catch in index.ts 不需要处理它
115
+ return;
116
+ }
75
117
  console.error("Error processing stream:", error);
76
118
  throw error;
77
119
  }
78
120
  }
79
121
 
80
- // 处理单个数据块
122
+ // 处理单个数据块 — 分段列表追加,避免每次 O(n) 全量拷贝
81
123
  private _processChunk(chunk: Uint8Array | { subarray(): Uint8Array }): void {
82
124
  // chunk 是 Uint8ArrayList 或 Uint8Array
83
125
  const newData: Uint8Array = 'subarray' in chunk && typeof chunk.subarray === 'function'
84
126
  ? chunk.subarray()
85
127
  : (chunk as Uint8Array);
86
-
87
- // 原作者之前的 O(N) 内存拷贝优化被保留,去掉了存在 onEnd 竞态的 setTimeout
88
- const newBuffer = new Uint8Array(this.buffer.length + newData.length);
89
- newBuffer.set(this.buffer);
90
- newBuffer.set(newData, this.buffer.length);
91
- this.buffer = newBuffer;
128
+
129
+ // 追加到分段列表,O(1),不拷贝历史数据
130
+ if (newData.length > 0) {
131
+ this.bufferChunks.push(newData);
132
+ this.bufferTotalLength += newData.length;
133
+ }
134
+
135
+ // 将所有分段合并为一块后处理帧(只合并一次,后续 slice 替换)
136
+ // 仅在确实有完整帧时才触发合并,碎片仅 push 不合并
137
+ if (this.bufferTotalLength < 9) return;
138
+
139
+ // 合并一次
140
+ const flat = this._flattenBuffer();
141
+ this.bufferChunks = [flat];
142
+ // bufferTotalLength 保持不变
92
143
 
93
144
  // 持续处理所有完整的帧
94
145
  let readOffset = 0;
95
- while (this.buffer.length - readOffset >= 9) {
146
+ while (flat.length - readOffset >= 9) {
96
147
  // 判断是否有HTTP/2前导
97
- if (this.buffer.length - readOffset >= 24 && this.isHttp2Preface(this.buffer.subarray(readOffset))) {
148
+ if (flat.length - readOffset >= 24 && this.isHttp2Preface(flat.subarray(readOffset))) {
98
149
  readOffset += 24;
99
150
  // 发送SETTINGS帧
100
151
  const settingFrame = Http2Frame.createSettingsFrame();
101
152
  this.writer.write(settingFrame);
102
153
  continue;
103
154
  }
104
-
105
- const frameHeader = this._parseFrameHeader(this.buffer.subarray(readOffset));
155
+
156
+ const frameHeader = this._parseFrameHeader(flat.subarray(readOffset));
106
157
  const totalFrameLength = 9 + frameHeader.length;
107
158
 
108
159
  // 检查是否有完整的帧
109
- if (this.buffer.length - readOffset < totalFrameLength) {
160
+ if (flat.length - readOffset < totalFrameLength) {
110
161
  break;
111
162
  }
112
- // 获取完整帧数据
113
- const frameData = this.buffer.subarray(readOffset, readOffset + totalFrameLength);
163
+ // 获取完整帧数据(subarray 视图,零拷贝)
164
+ const frameData = flat.subarray(readOffset, readOffset + totalFrameLength);
114
165
 
115
166
  // 处理不同类型的帧
116
167
  this._handleFrame(frameHeader, frameData).catch((err) => {
117
168
  console.error("Error handling frame:", err);
118
169
  });
119
170
 
120
- // 移动偏移量
121
171
  readOffset += totalFrameLength;
122
172
  }
123
-
173
+
174
+ // 保留未消费的尾部字节(slice 一次,后续仍分段追加)
124
175
  if (readOffset > 0) {
125
- this.buffer = this.buffer.slice(readOffset);
176
+ if (readOffset >= flat.length) {
177
+ this.bufferChunks = [];
178
+ this.bufferTotalLength = 0;
179
+ } else {
180
+ const remaining = flat.slice(readOffset);
181
+ this.bufferChunks = [remaining];
182
+ this.bufferTotalLength = remaining.length;
183
+ }
126
184
  }
127
185
  }
128
186
 
@@ -134,55 +192,53 @@ export class HTTP2Parser {
134
192
  return true;
135
193
  }
136
194
 
137
- // 移除之前的 for await 循环代码
138
- private _oldProcessStream_removed() {
139
- // 这个方法已被上面的事件驱动实现替代
140
- }
141
-
142
- // 等待SETTINGS ACK
195
+ // 等待SETTINGS ACK 事件驱动,无轮询
143
196
  waitForSettingsAck(): Promise<void> {
144
197
  return new Promise((resolve, reject) => {
145
- if (this.settingsAckReceived) {
146
- resolve();
147
- return;
148
- }
149
- const interval = setInterval(() => {
150
- if (this.settingsAckReceived) {
151
- clearInterval(interval);
152
- clearTimeout(timeout);
153
- resolve();
154
- }
155
- }, 100);
156
-
198
+ if (this.settingsAckReceived) { resolve(); return; }
199
+ const waiter = { resolve, reject };
200
+ this.settingsAckWaiters.push(waiter);
157
201
  const timeout = setTimeout(() => {
158
- clearInterval(interval);
202
+ const idx = this.settingsAckWaiters.indexOf(waiter);
203
+ if (idx >= 0) this.settingsAckWaiters.splice(idx, 1);
159
204
  reject(new Error("Settings ACK timeout"));
160
205
  }, 30000);
206
+ // 覆盖 resolve 以便超时前自动清理定时器
207
+ waiter.resolve = () => { clearTimeout(timeout); resolve(); };
208
+ waiter.reject = (e: Error) => { clearTimeout(timeout); reject(e); };
161
209
  });
162
210
  }
163
211
 
164
- // 等待接收来自对端的 SETTINGS(非 ACK
212
+ /** 内部调用:SETTINGS ACK 收到时唤醒所有等待者 */
213
+ private _notifySettingsAck() {
214
+ this.settingsAckReceived = true;
215
+ const ws = this.settingsAckWaiters.splice(0);
216
+ for (const w of ws) { try { w.resolve(); } catch { /* ignore */ } }
217
+ }
218
+
219
+ // 等待接收来自对端的 SETTINGS(非 ACK)— 事件驱动,无轮询
165
220
  waitForPeerSettings(timeoutMs: number = 30000): Promise<void> {
166
221
  return new Promise((resolve, reject) => {
167
- if (this.peerSettingsReceived) {
168
- resolve();
169
- return;
170
- }
171
- const interval = setInterval(() => {
172
- if (this.peerSettingsReceived) {
173
- clearInterval(interval);
174
- clearTimeout(timeout);
175
- resolve();
176
- }
177
- }, 100);
178
-
222
+ if (this.peerSettingsReceived) { resolve(); return; }
223
+ const waiter = { resolve, reject };
224
+ this.peerSettingsWaiters.push(waiter);
179
225
  const timeout = setTimeout(() => {
180
- clearInterval(interval);
226
+ const idx = this.peerSettingsWaiters.indexOf(waiter);
227
+ if (idx >= 0) this.peerSettingsWaiters.splice(idx, 1);
181
228
  reject(new Error("Peer SETTINGS timeout"));
182
229
  }, timeoutMs);
230
+ waiter.resolve = () => { clearTimeout(timeout); resolve(); };
231
+ waiter.reject = (e: Error) => { clearTimeout(timeout); reject(e); };
183
232
  });
184
233
  }
185
234
 
235
+ /** 内部调用:收到对端 SETTINGS(非 ACK)时唤醒等待者 */
236
+ private _notifyPeerSettings() {
237
+ this.peerSettingsReceived = true;
238
+ const ws = this.peerSettingsWaiters.splice(0);
239
+ for (const w of ws) { try { w.resolve(); } catch { /* ignore */ } }
240
+ }
241
+
186
242
  // 注册我们要发送数据的出站流(用于初始化该流的对端窗口)
187
243
  registerOutboundStream(streamId: number) {
188
244
  if (!this.sendStreamWindows.has(streamId)) {
@@ -210,56 +266,46 @@ export class HTTP2Parser {
210
266
  this.sendConnWindow = Math.min(0x7fffffff, this.sendConnWindow + bytes);
211
267
  const cur = this.sendStreamWindows.get(streamId) ?? 0;
212
268
  this.sendStreamWindows.set(streamId, Math.min(0x7fffffff, cur + bytes));
269
+ // 窗口增大,唤醒等待者
270
+ this._wakeWindowWaiters();
213
271
  }
214
272
 
215
- // 等待可用发送窗口(两个窗口都需要 >0)
216
- async waitForSendWindow(streamId: number, minBytes: number = 1, timeoutMs: number = 30000): Promise<void> {
217
- const start = Date.now();
273
+ // 等待可用发送窗口 — 事件驱动,WINDOW_UPDATE/SETTINGS 收到时直接唤醒
274
+ waitForSendWindow(streamId: number, minBytes: number = 1, timeoutMs: number = 30000): Promise<void> {
275
+ const { conn, stream } = this.getSendWindows(streamId);
276
+ if (conn >= minBytes && stream >= minBytes) return Promise.resolve();
277
+
218
278
  return new Promise((resolve, reject) => {
219
- let interval: ReturnType<typeof setInterval> | null = null;
220
279
  let settled = false;
221
- const check = () => {
222
- const { conn, stream } = this.getSendWindows(streamId);
223
- if (conn >= minBytes && stream >= minBytes) {
224
- if (!settled) {
280
+ const timeout = timeoutMs > 0
281
+ ? setTimeout(() => {
282
+ if (settled) return;
225
283
  settled = true;
226
- if (interval) {
227
- clearInterval(interval);
228
- interval = null;
229
- }
230
- resolve();
231
- }
232
- return true;
233
- }
234
- if (Date.now() - start > timeoutMs) {
235
- if (!settled) {
236
- settled = true;
237
- if (interval) {
238
- clearInterval(interval);
239
- interval = null;
240
- }
284
+ const idx = this.sendWindowWaiters.findIndex(w => w.resolve === resolveWrap);
285
+ if (idx >= 0) this.sendWindowWaiters.splice(idx, 1);
241
286
  reject(new Error('Send window wait timeout'));
242
- }
243
- return true;
287
+ }, timeoutMs)
288
+ : undefined;
289
+
290
+ const resolveWrap = () => {
291
+ if (settled) return;
292
+ const { conn: c2, stream: s2 } = this.getSendWindows(streamId);
293
+ if (c2 >= minBytes && s2 >= minBytes) {
294
+ settled = true;
295
+ if (timeout) clearTimeout(timeout);
296
+ resolve();
297
+ } else {
298
+ // 窗口仍不够,重新入队等待下一次更新
299
+ this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
244
300
  }
245
- return false;
246
301
  };
247
- if (check()) return;
248
- const tick = () => {
249
- if (!check()) {
250
- // 继续等待
251
- }
302
+ const rejectWrap = (e: Error) => {
303
+ if (settled) return;
304
+ settled = true;
305
+ if (timeout) clearTimeout(timeout);
306
+ reject(e);
252
307
  };
253
- const wake = () => { tick(); };
254
- // 简单的等待模型:依赖 WINDOW_UPDATE 到达时调用 wake
255
- this.sendWindowWaiters.push(wake);
256
- // 同时做一个轻微的轮询,防止错过唤醒
257
- interval = setInterval(() => {
258
- if (check() && interval) {
259
- clearInterval(interval);
260
- interval = null;
261
- }
262
- }, 50);
308
+ this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
263
309
  });
264
310
  }
265
311
 
@@ -268,7 +314,7 @@ export class HTTP2Parser {
268
314
  switch (frameHeader.type) {
269
315
  case FRAME_TYPES.SETTINGS:
270
316
  if ((frameHeader.flags & FRAME_FLAGS.ACK) === FRAME_FLAGS.ACK) {
271
- this.settingsAckReceived = true;
317
+ this._notifySettingsAck();
272
318
  } else {
273
319
  //接收到Setting请求,进行解析
274
320
  const settingsPayload = frameData.slice(9);
@@ -278,11 +324,14 @@ export class HTTP2Parser {
278
324
  for (let i = 0; i < settingsPayload.length; i += 6) {
279
325
  // 正确解析:2字节ID + 4字节值
280
326
  const id = (settingsPayload[i] << 8) | settingsPayload[i + 1];
281
- const value =
327
+ // >>> 0 将结果转为无符号 32 位整数,防止高位为 1 时(如 0xffffffff)
328
+ // 被 JS 按有符号解读为负数,导致 maxConcurrentStreams 等字段为负值
329
+ const value = (
282
330
  (settingsPayload[i + 2] << 24) |
283
331
  (settingsPayload[i + 3] << 16) |
284
332
  (settingsPayload[i + 4] << 8) |
285
- settingsPayload[i + 5];
333
+ settingsPayload[i + 5]
334
+ ) >>> 0;
286
335
 
287
336
  settings[id] = value;
288
337
  if (id === 4) {
@@ -322,51 +371,56 @@ export class HTTP2Parser {
322
371
  if (this.onSettings) {
323
372
  this.onSettings(frameHeader);
324
373
  }
325
- // 标记已收到对端 SETTINGS
326
- this.peerSettingsReceived = true;
327
- // 唤醒等待窗口(以防部分实现通过 SETTINGS 改变有效窗口)
328
- const waiters = this.sendWindowWaiters.splice(0);
329
- waiters.forEach(fn => { try { fn(); } catch (e) { console.debug('waiter error', e); } });
374
+ // 标记已收到对端 SETTINGS 并唤醒等待者
375
+ this._notifyPeerSettings();
376
+ // 唤醒发送窗口等待者(以防部分实现通过 SETTINGS 改变有效窗口)
377
+ this._wakeWindowWaiters();
330
378
  }
331
379
  break;
332
380
 
333
- case FRAME_TYPES.DATA:
381
+ case FRAME_TYPES.DATA: {
334
382
  // 处理数据帧
335
383
  if (this.onData) {
336
384
  this.onData(frameData.slice(9), frameHeader); // 跳过帧头
337
385
  }
338
386
  // 更新流窗口和连接窗口
339
- try {
340
- // 更新流级别的窗口
341
- if (frameHeader.streamId !== 0) {
342
- const streamWindowUpdate = Http2Frame.createWindowUpdateFrame(
343
- frameHeader.streamId,
344
- frameHeader.length ?? 0
387
+ // 仅在帧有实际数据时才发送 WINDOW_UPDATE:
388
+ // RFC 7540 §6.9.1 明确禁止 increment=0 的 WINDOW_UPDATE,
389
+ // 服务端必须以 PROTOCOL_ERROR 响应,会导致连接被强制关闭。
390
+ // DATA 帧(如纯 END_STREAM 帧)length=0,不需要归还窗口。
391
+ const dataLength = frameHeader.length ?? 0;
392
+ if (dataLength > 0) {
393
+ try {
394
+ // 更新流级别的窗口
395
+ if (frameHeader.streamId !== 0) {
396
+ const streamWindowUpdate = Http2Frame.createWindowUpdateFrame(
397
+ frameHeader.streamId,
398
+ dataLength
399
+ );
400
+ this.writer.write(streamWindowUpdate);
401
+ }
402
+
403
+ // 更新连接级别的窗口
404
+ const connWindowUpdate = Http2Frame.createWindowUpdateFrame(
405
+ 0,
406
+ dataLength
345
407
  );
346
- this.writer.write(streamWindowUpdate);
408
+ this.writer.write(connWindowUpdate);
409
+ } catch (err) {
410
+ console.error("[HTTP2] Error sending window update:", err);
347
411
  }
348
-
349
- // 更新连接级别的窗口
350
- const connWindowUpdate = Http2Frame.createWindowUpdateFrame(
351
- 0,
352
- frameHeader.length ?? 0
353
- );
354
- this.writer.write(connWindowUpdate);
355
- } catch (err) {
356
- console.error("[HTTP2] Error sending window update:", err);
357
412
  }
358
413
  //判断是否是最后一个帧
359
414
  if (
360
415
  (frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
361
416
  FRAME_FLAGS.END_STREAM
362
417
  ) {
363
- this.endFlag = true;
364
- if (this.onEnd) {
365
- this.onEnd();
366
- }
418
+ this.onEnd?.();
419
+ this._notifyEndOfStream();
367
420
  return;
368
421
  }
369
422
  break;
423
+ }
370
424
  case FRAME_TYPES.HEADERS:
371
425
  // 处理头部帧
372
426
  if (this.onHeaders) {
@@ -377,31 +431,24 @@ export class HTTP2Parser {
377
431
  (frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
378
432
  FRAME_FLAGS.END_STREAM
379
433
  ) {
380
- this.endFlag = true;
381
- if (this.onEnd) {
382
- this.onEnd();
383
- }
434
+ this.onEnd?.();
435
+ this._notifyEndOfStream();
384
436
  return;
385
437
  }
386
438
  break;
387
439
  case FRAME_TYPES.WINDOW_UPDATE:
388
- // 处理窗口更新帧
389
- this.handleWindowUpdateFrame(
390
- frameHeader,
391
- frameData
392
- );
393
- // 更新发送窗口(对端接收窗口)
440
+ // 处理窗口更新帧(同时更新接收侧诊断计数器和发送侧流控窗口,只解析一次)
394
441
  try {
395
- const inc = this.parseWindowUpdateFrame(frameData, frameHeader).windowSizeIncrement;
442
+ const result = this.handleWindowUpdateFrame(frameHeader, frameData);
443
+ // 更新发送方向窗口(对端的接收窗口)
396
444
  if (frameHeader.streamId === 0) {
397
- this.sendConnWindow += inc;
445
+ this.sendConnWindow += result.windowSizeIncrement;
398
446
  } else {
399
447
  const cur = this.sendStreamWindows.get(frameHeader.streamId) ?? this.peerInitialStreamWindow;
400
- this.sendStreamWindows.set(frameHeader.streamId, cur + inc);
448
+ this.sendStreamWindows.set(frameHeader.streamId, cur + result.windowSizeIncrement);
401
449
  }
402
- const waiters = this.sendWindowWaiters.splice(0);
403
- waiters.forEach(fn => { try { fn(); } catch (e) { console.debug('waiter error', e); } });
404
- } catch { /* ignore WINDOW_UPDATE parse errors */ }
450
+ this._wakeWindowWaiters();
451
+ } catch { /* ignore WINDOW_UPDATE parse errors (e.g. increment=0 is RFC PROTOCOL_ERROR) */ }
405
452
  break;
406
453
  case FRAME_TYPES.PING:
407
454
  // 处理PING帧
@@ -427,12 +474,12 @@ export class HTTP2Parser {
427
474
  } catch (err) {
428
475
  console.error('Error during GOAWAY callback:', err);
429
476
  }
430
- this.endFlag = true;
431
477
  try {
432
478
  this.onEnd?.();
433
479
  } catch (err) {
434
480
  console.error('Error during GOAWAY onEnd callback:', err);
435
481
  }
482
+ this._notifyEndOfStream();
436
483
  break;
437
484
  }
438
485
 
@@ -441,10 +488,8 @@ export class HTTP2Parser {
441
488
  // this.handlePushPromiseFrame(frameHeader, frameData);
442
489
  // break;
443
490
  case FRAME_TYPES.RST_STREAM:
444
- this.endFlag = true;
445
- if (this.onEnd) {
446
- this.onEnd();
447
- }
491
+ this.onEnd?.();
492
+ this._notifyEndOfStream();
448
493
  break;
449
494
  default:
450
495
  console.debug("Unknown frame type:", frameHeader.type);
@@ -455,8 +500,9 @@ export class HTTP2Parser {
455
500
  const length = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
456
501
  const type = buffer[3];
457
502
  const flags = buffer[4];
503
+ // RFC 7540 §4.1: most significant bit is reserved and MUST be ignored on receipt
458
504
  const streamId =
459
- (buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8];
505
+ ((buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8]) & 0x7fffffff;
460
506
 
461
507
  return {
462
508
  length,
@@ -486,55 +532,34 @@ export class HTTP2Parser {
486
532
  }
487
533
  }
488
534
 
489
- //等待流结束
535
+ // 等待流结束 — 事件驱动,onEnd 触发时直接唤醒,无 setInterval 轮询
490
536
  waitForEndOfStream(waitTime: number): Promise<void> {
491
537
  return new Promise((resolve, reject) => {
492
- // If the stream has already ended, resolve immediately
493
- if (this.endFlag) {
494
- resolve();
495
- return;
496
- }
497
- // 如果是0 ,则不设置超时
498
- let timeout: ReturnType<typeof setTimeout> | null = null;
499
- if (waitTime > 0) {
500
- timeout = setTimeout(() => {
501
- clearInterval(interval);
502
- reject(new Error("End of stream timeout"));
503
- }, waitTime);
504
- }
538
+ if (this.endFlag) { resolve(); return; }
505
539
 
506
- // Check interval for real-time endFlag monitoring
507
- const checkInterval = 100; // Check every 100 milliseconds
508
- // Set an interval to check the endFlag regularly
509
- const interval = setInterval(() => {
510
- if (this.endFlag) {
511
- if (timeout !== null) {
512
- clearTimeout(timeout);
513
- }
514
- clearInterval(interval);
515
- resolve();
516
- }
517
- }, checkInterval);
518
-
519
- // If the onEnd is triggered externally, it should now be marked manually
520
- const originalOnEnd = this.onEnd;
521
- this.onEnd = () => {
522
- if (!this.endFlag) {
523
- // The external trigger may set endFlag; if not, handle here
524
- this.endFlag = true;
525
- }
526
- if (timeout !== null) {
527
- clearTimeout(timeout);
528
- }
529
- clearInterval(interval);
530
- resolve();
531
- if (originalOnEnd) {
532
- originalOnEnd(); // Call the original onEnd function if set
533
- }
534
- };
540
+ const waiter = { resolve, reject };
541
+ this.endOfStreamWaiters.push(waiter);
542
+
543
+ const timeout = waitTime > 0
544
+ ? setTimeout(() => {
545
+ const idx = this.endOfStreamWaiters.indexOf(waiter);
546
+ if (idx >= 0) this.endOfStreamWaiters.splice(idx, 1);
547
+ reject(new Error("End of stream timeout"));
548
+ }, waitTime)
549
+ : null;
550
+
551
+ waiter.resolve = () => { if (timeout) clearTimeout(timeout); resolve(); };
552
+ waiter.reject = (e: Error) => { if (timeout) clearTimeout(timeout); reject(e); };
535
553
  });
536
554
  }
537
555
 
556
+ /** 内部调用:流结束时唤醒所有 waitForEndOfStream 等待者 */
557
+ private _notifyEndOfStream() {
558
+ this.endFlag = true;
559
+ const ws = this.endOfStreamWaiters.splice(0);
560
+ for (const w of ws) { try { w.resolve(); } catch { /* ignore */ } }
561
+ }
562
+
538
563
  // 解析 WINDOW_UPDATE 帧
539
564
  parseWindowUpdateFrame(frameBuffer: Uint8Array, frameHeader: Frame) {
540
565
  // WINDOW_UPDATE帧的payload固定为4字节