grpc-libp2p-client 0.0.39 → 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.
- package/dist/dc-http2/frame.cjs.js +51 -19
- package/dist/dc-http2/frame.cjs.js.map +1 -1
- package/dist/dc-http2/frame.d.ts +1 -1
- package/dist/dc-http2/frame.esm.js +51 -19
- package/dist/dc-http2/frame.esm.js.map +1 -1
- package/dist/dc-http2/hpack.cjs.js +43 -13
- package/dist/dc-http2/hpack.cjs.js.map +1 -1
- package/dist/dc-http2/hpack.esm.js +43 -13
- package/dist/dc-http2/hpack.esm.js.map +1 -1
- package/dist/dc-http2/parser.cjs.js +281 -189
- package/dist/dc-http2/parser.cjs.js.map +1 -1
- package/dist/dc-http2/parser.d.ts +21 -2
- package/dist/dc-http2/parser.esm.js +281 -189
- package/dist/dc-http2/parser.esm.js.map +1 -1
- package/dist/dc-http2/stream.cjs.js +97 -70
- package/dist/dc-http2/stream.cjs.js.map +1 -1
- package/dist/dc-http2/stream.d.ts +2 -0
- package/dist/dc-http2/stream.esm.js +97 -70
- package/dist/dc-http2/stream.esm.js.map +1 -1
- package/dist/grpc.js +810 -579
- package/dist/grpc.js.map +1 -1
- package/dist/grpc.min.js +1 -1
- package/dist/grpc.min.js.map +1 -1
- package/dist/index.cjs.js +633 -411
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +633 -411
- package/dist/index.esm.js.map +1 -1
- package/dist/stats.html +1 -1
- package/package.json +1 -1
- package/src/dc-http2/frame.ts +11 -9
- package/src/dc-http2/hpack.ts +43 -13
- package/src/dc-http2/parser.ts +219 -196
- package/src/dc-http2/stream.ts +84 -79
- package/src/index.ts +240 -183
package/src/dc-http2/parser.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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,95 @@ 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.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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) {
|
|
75
|
-
|
|
104
|
+
// abort() 触发的清理错误(如 'Call cleanup' / 'unaryCall cleanup')属于预期行为,降级为 debug 日志
|
|
105
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
106
|
+
const isAbortCleanup = /cleanup/i.test(errMsg) || /aborted/i.test(errMsg);
|
|
107
|
+
if (isAbortCleanup) {
|
|
108
|
+
console.debug("[processStream] stream aborted (expected):", errMsg);
|
|
109
|
+
} else {
|
|
110
|
+
console.error("Error processing stream:", error);
|
|
111
|
+
}
|
|
112
|
+
// 确保 waitForEndOfStream 等待者得到通知,防止 operationPromise 后台挂死
|
|
113
|
+
if (!this.endFlag) {
|
|
114
|
+
this._notifyEndOfStream();
|
|
115
|
+
}
|
|
76
116
|
throw error;
|
|
77
117
|
}
|
|
78
118
|
}
|
|
79
119
|
|
|
80
|
-
// 处理单个数据块
|
|
120
|
+
// 处理单个数据块 — 分段列表追加,避免每次 O(n) 全量拷贝
|
|
81
121
|
private _processChunk(chunk: Uint8Array | { subarray(): Uint8Array }): void {
|
|
82
122
|
// chunk 是 Uint8ArrayList 或 Uint8Array
|
|
83
123
|
const newData: Uint8Array = 'subarray' in chunk && typeof chunk.subarray === 'function'
|
|
84
124
|
? chunk.subarray()
|
|
85
125
|
: (chunk as Uint8Array);
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
126
|
+
|
|
127
|
+
// 追加到分段列表,O(1),不拷贝历史数据
|
|
128
|
+
if (newData.length > 0) {
|
|
129
|
+
this.bufferChunks.push(newData);
|
|
130
|
+
this.bufferTotalLength += newData.length;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 将所有分段合并为一块后处理帧(只合并一次,后续 slice 替换)
|
|
134
|
+
// 仅在确实有完整帧时才触发合并,碎片仅 push 不合并
|
|
135
|
+
if (this.bufferTotalLength < 9) return;
|
|
136
|
+
|
|
137
|
+
// 合并一次
|
|
138
|
+
const flat = this._flattenBuffer();
|
|
139
|
+
this.bufferChunks = [flat];
|
|
140
|
+
// bufferTotalLength 保持不变
|
|
92
141
|
|
|
93
142
|
// 持续处理所有完整的帧
|
|
94
143
|
let readOffset = 0;
|
|
95
|
-
while (
|
|
144
|
+
while (flat.length - readOffset >= 9) {
|
|
96
145
|
// 判断是否有HTTP/2前导
|
|
97
|
-
if (
|
|
146
|
+
if (flat.length - readOffset >= 24 && this.isHttp2Preface(flat.subarray(readOffset))) {
|
|
98
147
|
readOffset += 24;
|
|
99
148
|
// 发送SETTINGS帧
|
|
100
149
|
const settingFrame = Http2Frame.createSettingsFrame();
|
|
101
150
|
this.writer.write(settingFrame);
|
|
102
151
|
continue;
|
|
103
152
|
}
|
|
104
|
-
|
|
105
|
-
const frameHeader = this._parseFrameHeader(
|
|
153
|
+
|
|
154
|
+
const frameHeader = this._parseFrameHeader(flat.subarray(readOffset));
|
|
106
155
|
const totalFrameLength = 9 + frameHeader.length;
|
|
107
156
|
|
|
108
157
|
// 检查是否有完整的帧
|
|
109
|
-
if (
|
|
158
|
+
if (flat.length - readOffset < totalFrameLength) {
|
|
110
159
|
break;
|
|
111
160
|
}
|
|
112
|
-
//
|
|
113
|
-
const frameData =
|
|
161
|
+
// 获取完整帧数据(subarray 视图,零拷贝)
|
|
162
|
+
const frameData = flat.subarray(readOffset, readOffset + totalFrameLength);
|
|
114
163
|
|
|
115
164
|
// 处理不同类型的帧
|
|
116
165
|
this._handleFrame(frameHeader, frameData).catch((err) => {
|
|
117
166
|
console.error("Error handling frame:", err);
|
|
118
167
|
});
|
|
119
168
|
|
|
120
|
-
// 移动偏移量
|
|
121
169
|
readOffset += totalFrameLength;
|
|
122
170
|
}
|
|
123
|
-
|
|
171
|
+
|
|
172
|
+
// 保留未消费的尾部字节(slice 一次,后续仍分段追加)
|
|
124
173
|
if (readOffset > 0) {
|
|
125
|
-
|
|
174
|
+
if (readOffset >= flat.length) {
|
|
175
|
+
this.bufferChunks = [];
|
|
176
|
+
this.bufferTotalLength = 0;
|
|
177
|
+
} else {
|
|
178
|
+
const remaining = flat.slice(readOffset);
|
|
179
|
+
this.bufferChunks = [remaining];
|
|
180
|
+
this.bufferTotalLength = remaining.length;
|
|
181
|
+
}
|
|
126
182
|
}
|
|
127
183
|
}
|
|
128
184
|
|
|
@@ -134,55 +190,53 @@ export class HTTP2Parser {
|
|
|
134
190
|
return true;
|
|
135
191
|
}
|
|
136
192
|
|
|
137
|
-
//
|
|
138
|
-
private _oldProcessStream_removed() {
|
|
139
|
-
// 这个方法已被上面的事件驱动实现替代
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 等待SETTINGS ACK
|
|
193
|
+
// 等待SETTINGS ACK — 事件驱动,无轮询
|
|
143
194
|
waitForSettingsAck(): Promise<void> {
|
|
144
195
|
return new Promise((resolve, reject) => {
|
|
145
|
-
if (this.settingsAckReceived) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
const interval = setInterval(() => {
|
|
150
|
-
if (this.settingsAckReceived) {
|
|
151
|
-
clearInterval(interval);
|
|
152
|
-
clearTimeout(timeout);
|
|
153
|
-
resolve();
|
|
154
|
-
}
|
|
155
|
-
}, 100);
|
|
156
|
-
|
|
196
|
+
if (this.settingsAckReceived) { resolve(); return; }
|
|
197
|
+
const waiter = { resolve, reject };
|
|
198
|
+
this.settingsAckWaiters.push(waiter);
|
|
157
199
|
const timeout = setTimeout(() => {
|
|
158
|
-
|
|
200
|
+
const idx = this.settingsAckWaiters.indexOf(waiter);
|
|
201
|
+
if (idx >= 0) this.settingsAckWaiters.splice(idx, 1);
|
|
159
202
|
reject(new Error("Settings ACK timeout"));
|
|
160
203
|
}, 30000);
|
|
204
|
+
// 覆盖 resolve 以便超时前自动清理定时器
|
|
205
|
+
waiter.resolve = () => { clearTimeout(timeout); resolve(); };
|
|
206
|
+
waiter.reject = (e: Error) => { clearTimeout(timeout); reject(e); };
|
|
161
207
|
});
|
|
162
208
|
}
|
|
163
209
|
|
|
164
|
-
|
|
210
|
+
/** 内部调用:SETTINGS ACK 收到时唤醒所有等待者 */
|
|
211
|
+
private _notifySettingsAck() {
|
|
212
|
+
this.settingsAckReceived = true;
|
|
213
|
+
const ws = this.settingsAckWaiters.splice(0);
|
|
214
|
+
for (const w of ws) { try { w.resolve(); } catch { /* ignore */ } }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 等待接收来自对端的 SETTINGS(非 ACK)— 事件驱动,无轮询
|
|
165
218
|
waitForPeerSettings(timeoutMs: number = 30000): Promise<void> {
|
|
166
219
|
return new Promise((resolve, reject) => {
|
|
167
|
-
if (this.peerSettingsReceived) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
const interval = setInterval(() => {
|
|
172
|
-
if (this.peerSettingsReceived) {
|
|
173
|
-
clearInterval(interval);
|
|
174
|
-
clearTimeout(timeout);
|
|
175
|
-
resolve();
|
|
176
|
-
}
|
|
177
|
-
}, 100);
|
|
178
|
-
|
|
220
|
+
if (this.peerSettingsReceived) { resolve(); return; }
|
|
221
|
+
const waiter = { resolve, reject };
|
|
222
|
+
this.peerSettingsWaiters.push(waiter);
|
|
179
223
|
const timeout = setTimeout(() => {
|
|
180
|
-
|
|
224
|
+
const idx = this.peerSettingsWaiters.indexOf(waiter);
|
|
225
|
+
if (idx >= 0) this.peerSettingsWaiters.splice(idx, 1);
|
|
181
226
|
reject(new Error("Peer SETTINGS timeout"));
|
|
182
227
|
}, timeoutMs);
|
|
228
|
+
waiter.resolve = () => { clearTimeout(timeout); resolve(); };
|
|
229
|
+
waiter.reject = (e: Error) => { clearTimeout(timeout); reject(e); };
|
|
183
230
|
});
|
|
184
231
|
}
|
|
185
232
|
|
|
233
|
+
/** 内部调用:收到对端 SETTINGS(非 ACK)时唤醒等待者 */
|
|
234
|
+
private _notifyPeerSettings() {
|
|
235
|
+
this.peerSettingsReceived = true;
|
|
236
|
+
const ws = this.peerSettingsWaiters.splice(0);
|
|
237
|
+
for (const w of ws) { try { w.resolve(); } catch { /* ignore */ } }
|
|
238
|
+
}
|
|
239
|
+
|
|
186
240
|
// 注册我们要发送数据的出站流(用于初始化该流的对端窗口)
|
|
187
241
|
registerOutboundStream(streamId: number) {
|
|
188
242
|
if (!this.sendStreamWindows.has(streamId)) {
|
|
@@ -210,56 +264,46 @@ export class HTTP2Parser {
|
|
|
210
264
|
this.sendConnWindow = Math.min(0x7fffffff, this.sendConnWindow + bytes);
|
|
211
265
|
const cur = this.sendStreamWindows.get(streamId) ?? 0;
|
|
212
266
|
this.sendStreamWindows.set(streamId, Math.min(0x7fffffff, cur + bytes));
|
|
267
|
+
// 窗口增大,唤醒等待者
|
|
268
|
+
this._wakeWindowWaiters();
|
|
213
269
|
}
|
|
214
270
|
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
const
|
|
271
|
+
// 等待可用发送窗口 — 事件驱动,WINDOW_UPDATE/SETTINGS 收到时直接唤醒
|
|
272
|
+
waitForSendWindow(streamId: number, minBytes: number = 1, timeoutMs: number = 30000): Promise<void> {
|
|
273
|
+
const { conn, stream } = this.getSendWindows(streamId);
|
|
274
|
+
if (conn >= minBytes && stream >= minBytes) return Promise.resolve();
|
|
275
|
+
|
|
218
276
|
return new Promise((resolve, reject) => {
|
|
219
|
-
let interval: ReturnType<typeof setInterval> | null = null;
|
|
220
277
|
let settled = false;
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (!settled) {
|
|
225
|
-
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) {
|
|
278
|
+
const timeout = timeoutMs > 0
|
|
279
|
+
? setTimeout(() => {
|
|
280
|
+
if (settled) return;
|
|
236
281
|
settled = true;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
interval = null;
|
|
240
|
-
}
|
|
282
|
+
const idx = this.sendWindowWaiters.findIndex(w => w.resolve === resolveWrap);
|
|
283
|
+
if (idx >= 0) this.sendWindowWaiters.splice(idx, 1);
|
|
241
284
|
reject(new Error('Send window wait timeout'));
|
|
242
|
-
}
|
|
243
|
-
|
|
285
|
+
}, timeoutMs)
|
|
286
|
+
: undefined;
|
|
287
|
+
|
|
288
|
+
const resolveWrap = () => {
|
|
289
|
+
if (settled) return;
|
|
290
|
+
const { conn: c2, stream: s2 } = this.getSendWindows(streamId);
|
|
291
|
+
if (c2 >= minBytes && s2 >= minBytes) {
|
|
292
|
+
settled = true;
|
|
293
|
+
if (timeout) clearTimeout(timeout);
|
|
294
|
+
resolve();
|
|
295
|
+
} else {
|
|
296
|
+
// 窗口仍不够,重新入队等待下一次更新
|
|
297
|
+
this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
|
|
244
298
|
}
|
|
245
|
-
return false;
|
|
246
299
|
};
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
300
|
+
const rejectWrap = (e: Error) => {
|
|
301
|
+
if (settled) return;
|
|
302
|
+
settled = true;
|
|
303
|
+
if (timeout) clearTimeout(timeout);
|
|
304
|
+
reject(e);
|
|
252
305
|
};
|
|
253
|
-
|
|
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);
|
|
306
|
+
this.sendWindowWaiters.push({ resolve: resolveWrap, reject: rejectWrap });
|
|
263
307
|
});
|
|
264
308
|
}
|
|
265
309
|
|
|
@@ -268,7 +312,7 @@ export class HTTP2Parser {
|
|
|
268
312
|
switch (frameHeader.type) {
|
|
269
313
|
case FRAME_TYPES.SETTINGS:
|
|
270
314
|
if ((frameHeader.flags & FRAME_FLAGS.ACK) === FRAME_FLAGS.ACK) {
|
|
271
|
-
this.
|
|
315
|
+
this._notifySettingsAck();
|
|
272
316
|
} else {
|
|
273
317
|
//接收到Setting请求,进行解析
|
|
274
318
|
const settingsPayload = frameData.slice(9);
|
|
@@ -278,11 +322,14 @@ export class HTTP2Parser {
|
|
|
278
322
|
for (let i = 0; i < settingsPayload.length; i += 6) {
|
|
279
323
|
// 正确解析:2字节ID + 4字节值
|
|
280
324
|
const id = (settingsPayload[i] << 8) | settingsPayload[i + 1];
|
|
281
|
-
|
|
325
|
+
// >>> 0 将结果转为无符号 32 位整数,防止高位为 1 时(如 0xffffffff)
|
|
326
|
+
// 被 JS 按有符号解读为负数,导致 maxConcurrentStreams 等字段为负值
|
|
327
|
+
const value = (
|
|
282
328
|
(settingsPayload[i + 2] << 24) |
|
|
283
329
|
(settingsPayload[i + 3] << 16) |
|
|
284
330
|
(settingsPayload[i + 4] << 8) |
|
|
285
|
-
settingsPayload[i + 5]
|
|
331
|
+
settingsPayload[i + 5]
|
|
332
|
+
) >>> 0;
|
|
286
333
|
|
|
287
334
|
settings[id] = value;
|
|
288
335
|
if (id === 4) {
|
|
@@ -322,51 +369,56 @@ export class HTTP2Parser {
|
|
|
322
369
|
if (this.onSettings) {
|
|
323
370
|
this.onSettings(frameHeader);
|
|
324
371
|
}
|
|
325
|
-
// 标记已收到对端 SETTINGS
|
|
326
|
-
this.
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
waiters.forEach(fn => { try { fn(); } catch (e) { console.debug('waiter error', e); } });
|
|
372
|
+
// 标记已收到对端 SETTINGS 并唤醒等待者
|
|
373
|
+
this._notifyPeerSettings();
|
|
374
|
+
// 唤醒发送窗口等待者(以防部分实现通过 SETTINGS 改变有效窗口)
|
|
375
|
+
this._wakeWindowWaiters();
|
|
330
376
|
}
|
|
331
377
|
break;
|
|
332
378
|
|
|
333
|
-
case FRAME_TYPES.DATA:
|
|
379
|
+
case FRAME_TYPES.DATA: {
|
|
334
380
|
// 处理数据帧
|
|
335
381
|
if (this.onData) {
|
|
336
382
|
this.onData(frameData.slice(9), frameHeader); // 跳过帧头
|
|
337
383
|
}
|
|
338
384
|
// 更新流窗口和连接窗口
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
385
|
+
// 仅在帧有实际数据时才发送 WINDOW_UPDATE:
|
|
386
|
+
// RFC 7540 §6.9.1 明确禁止 increment=0 的 WINDOW_UPDATE,
|
|
387
|
+
// 服务端必须以 PROTOCOL_ERROR 响应,会导致连接被强制关闭。
|
|
388
|
+
// 空 DATA 帧(如纯 END_STREAM 帧)length=0,不需要归还窗口。
|
|
389
|
+
const dataLength = frameHeader.length ?? 0;
|
|
390
|
+
if (dataLength > 0) {
|
|
391
|
+
try {
|
|
392
|
+
// 更新流级别的窗口
|
|
393
|
+
if (frameHeader.streamId !== 0) {
|
|
394
|
+
const streamWindowUpdate = Http2Frame.createWindowUpdateFrame(
|
|
395
|
+
frameHeader.streamId,
|
|
396
|
+
dataLength
|
|
397
|
+
);
|
|
398
|
+
this.writer.write(streamWindowUpdate);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 更新连接级别的窗口
|
|
402
|
+
const connWindowUpdate = Http2Frame.createWindowUpdateFrame(
|
|
403
|
+
0,
|
|
404
|
+
dataLength
|
|
345
405
|
);
|
|
346
|
-
this.writer.write(
|
|
406
|
+
this.writer.write(connWindowUpdate);
|
|
407
|
+
} catch (err) {
|
|
408
|
+
console.error("[HTTP2] Error sending window update:", err);
|
|
347
409
|
}
|
|
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
410
|
}
|
|
358
411
|
//判断是否是最后一个帧
|
|
359
412
|
if (
|
|
360
413
|
(frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
|
|
361
414
|
FRAME_FLAGS.END_STREAM
|
|
362
415
|
) {
|
|
363
|
-
this.
|
|
364
|
-
|
|
365
|
-
this.onEnd();
|
|
366
|
-
}
|
|
416
|
+
this.onEnd?.();
|
|
417
|
+
this._notifyEndOfStream();
|
|
367
418
|
return;
|
|
368
419
|
}
|
|
369
420
|
break;
|
|
421
|
+
}
|
|
370
422
|
case FRAME_TYPES.HEADERS:
|
|
371
423
|
// 处理头部帧
|
|
372
424
|
if (this.onHeaders) {
|
|
@@ -377,31 +429,24 @@ export class HTTP2Parser {
|
|
|
377
429
|
(frameHeader.flags & FRAME_FLAGS.END_STREAM) ===
|
|
378
430
|
FRAME_FLAGS.END_STREAM
|
|
379
431
|
) {
|
|
380
|
-
this.
|
|
381
|
-
|
|
382
|
-
this.onEnd();
|
|
383
|
-
}
|
|
432
|
+
this.onEnd?.();
|
|
433
|
+
this._notifyEndOfStream();
|
|
384
434
|
return;
|
|
385
435
|
}
|
|
386
436
|
break;
|
|
387
437
|
case FRAME_TYPES.WINDOW_UPDATE:
|
|
388
|
-
//
|
|
389
|
-
this.handleWindowUpdateFrame(
|
|
390
|
-
frameHeader,
|
|
391
|
-
frameData
|
|
392
|
-
);
|
|
393
|
-
// 更新发送窗口(对端接收窗口)
|
|
438
|
+
// 处理窗口更新帧(同时更新接收侧诊断计数器和发送侧流控窗口,只解析一次)
|
|
394
439
|
try {
|
|
395
|
-
const
|
|
440
|
+
const result = this.handleWindowUpdateFrame(frameHeader, frameData);
|
|
441
|
+
// 更新发送方向窗口(对端的接收窗口)
|
|
396
442
|
if (frameHeader.streamId === 0) {
|
|
397
|
-
this.sendConnWindow +=
|
|
443
|
+
this.sendConnWindow += result.windowSizeIncrement;
|
|
398
444
|
} else {
|
|
399
445
|
const cur = this.sendStreamWindows.get(frameHeader.streamId) ?? this.peerInitialStreamWindow;
|
|
400
|
-
this.sendStreamWindows.set(frameHeader.streamId, cur +
|
|
446
|
+
this.sendStreamWindows.set(frameHeader.streamId, cur + result.windowSizeIncrement);
|
|
401
447
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
} catch { /* ignore WINDOW_UPDATE parse errors */ }
|
|
448
|
+
this._wakeWindowWaiters();
|
|
449
|
+
} catch { /* ignore WINDOW_UPDATE parse errors (e.g. increment=0 is RFC PROTOCOL_ERROR) */ }
|
|
405
450
|
break;
|
|
406
451
|
case FRAME_TYPES.PING:
|
|
407
452
|
// 处理PING帧
|
|
@@ -427,12 +472,12 @@ export class HTTP2Parser {
|
|
|
427
472
|
} catch (err) {
|
|
428
473
|
console.error('Error during GOAWAY callback:', err);
|
|
429
474
|
}
|
|
430
|
-
this.endFlag = true;
|
|
431
475
|
try {
|
|
432
476
|
this.onEnd?.();
|
|
433
477
|
} catch (err) {
|
|
434
478
|
console.error('Error during GOAWAY onEnd callback:', err);
|
|
435
479
|
}
|
|
480
|
+
this._notifyEndOfStream();
|
|
436
481
|
break;
|
|
437
482
|
}
|
|
438
483
|
|
|
@@ -441,10 +486,8 @@ export class HTTP2Parser {
|
|
|
441
486
|
// this.handlePushPromiseFrame(frameHeader, frameData);
|
|
442
487
|
// break;
|
|
443
488
|
case FRAME_TYPES.RST_STREAM:
|
|
444
|
-
this.
|
|
445
|
-
|
|
446
|
-
this.onEnd();
|
|
447
|
-
}
|
|
489
|
+
this.onEnd?.();
|
|
490
|
+
this._notifyEndOfStream();
|
|
448
491
|
break;
|
|
449
492
|
default:
|
|
450
493
|
console.debug("Unknown frame type:", frameHeader.type);
|
|
@@ -455,8 +498,9 @@ export class HTTP2Parser {
|
|
|
455
498
|
const length = (buffer[0] << 16) | (buffer[1] << 8) | buffer[2];
|
|
456
499
|
const type = buffer[3];
|
|
457
500
|
const flags = buffer[4];
|
|
501
|
+
// RFC 7540 §4.1: most significant bit is reserved and MUST be ignored on receipt
|
|
458
502
|
const streamId =
|
|
459
|
-
(buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8];
|
|
503
|
+
((buffer[5] << 24) | (buffer[6] << 16) | (buffer[7] << 8) | buffer[8]) & 0x7fffffff;
|
|
460
504
|
|
|
461
505
|
return {
|
|
462
506
|
length,
|
|
@@ -486,55 +530,34 @@ export class HTTP2Parser {
|
|
|
486
530
|
}
|
|
487
531
|
}
|
|
488
532
|
|
|
489
|
-
|
|
533
|
+
// 等待流结束 — 事件驱动,onEnd 触发时直接唤醒,无 setInterval 轮询
|
|
490
534
|
waitForEndOfStream(waitTime: number): Promise<void> {
|
|
491
535
|
return new Promise((resolve, reject) => {
|
|
492
|
-
|
|
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
|
-
}
|
|
536
|
+
if (this.endFlag) { resolve(); return; }
|
|
505
537
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
};
|
|
538
|
+
const waiter = { resolve, reject };
|
|
539
|
+
this.endOfStreamWaiters.push(waiter);
|
|
540
|
+
|
|
541
|
+
const timeout = waitTime > 0
|
|
542
|
+
? setTimeout(() => {
|
|
543
|
+
const idx = this.endOfStreamWaiters.indexOf(waiter);
|
|
544
|
+
if (idx >= 0) this.endOfStreamWaiters.splice(idx, 1);
|
|
545
|
+
reject(new Error("End of stream timeout"));
|
|
546
|
+
}, waitTime)
|
|
547
|
+
: null;
|
|
548
|
+
|
|
549
|
+
waiter.resolve = () => { if (timeout) clearTimeout(timeout); resolve(); };
|
|
550
|
+
waiter.reject = (e: Error) => { if (timeout) clearTimeout(timeout); reject(e); };
|
|
535
551
|
});
|
|
536
552
|
}
|
|
537
553
|
|
|
554
|
+
/** 内部调用:流结束时唤醒所有 waitForEndOfStream 等待者 */
|
|
555
|
+
private _notifyEndOfStream() {
|
|
556
|
+
this.endFlag = true;
|
|
557
|
+
const ws = this.endOfStreamWaiters.splice(0);
|
|
558
|
+
for (const w of ws) { try { w.resolve(); } catch { /* ignore */ } }
|
|
559
|
+
}
|
|
560
|
+
|
|
538
561
|
// 解析 WINDOW_UPDATE 帧
|
|
539
562
|
parseWindowUpdateFrame(frameBuffer: Uint8Array, frameHeader: Frame) {
|
|
540
563
|
// WINDOW_UPDATE帧的payload固定为4字节
|