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.
- 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 -188
- 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 -188
- 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 +820 -582
- 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 +646 -414
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +646 -414
- 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 +220 -195
- package/src/dc-http2/stream.ts +84 -79
- package/src/index.ts +253 -187
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,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.
|
|
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) {
|
|
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
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 (
|
|
146
|
+
while (flat.length - readOffset >= 9) {
|
|
96
147
|
// 判断是否有HTTP/2前导
|
|
97
|
-
if (
|
|
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(
|
|
155
|
+
|
|
156
|
+
const frameHeader = this._parseFrameHeader(flat.subarray(readOffset));
|
|
106
157
|
const totalFrameLength = 9 + frameHeader.length;
|
|
107
158
|
|
|
108
159
|
// 检查是否有完整的帧
|
|
109
|
-
if (
|
|
160
|
+
if (flat.length - readOffset < totalFrameLength) {
|
|
110
161
|
break;
|
|
111
162
|
}
|
|
112
|
-
//
|
|
113
|
-
const frameData =
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
216
|
-
|
|
217
|
-
const
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (!settled) {
|
|
280
|
+
const timeout = timeoutMs > 0
|
|
281
|
+
? setTimeout(() => {
|
|
282
|
+
if (settled) return;
|
|
225
283
|
settled = true;
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
327
|
-
//
|
|
328
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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(
|
|
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.
|
|
364
|
-
|
|
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.
|
|
381
|
-
|
|
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
|
|
442
|
+
const result = this.handleWindowUpdateFrame(frameHeader, frameData);
|
|
443
|
+
// 更新发送方向窗口(对端的接收窗口)
|
|
396
444
|
if (frameHeader.streamId === 0) {
|
|
397
|
-
this.sendConnWindow +=
|
|
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 +
|
|
448
|
+
this.sendStreamWindows.set(frameHeader.streamId, cur + result.windowSizeIncrement);
|
|
401
449
|
}
|
|
402
|
-
|
|
403
|
-
|
|
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.
|
|
445
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
};
|
|
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字节
|