@gjsify/http2 0.3.16 → 0.3.18
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/lib/esm/client-session.js +1 -274
- package/lib/esm/index.js +1 -48
- package/lib/esm/protocol.js +1 -342
- package/lib/esm/server.js +4 -599
- package/package.json +10 -10
- package/tsconfig.tsbuildinfo +1 -1
package/lib/esm/server.js
CHANGED
|
@@ -1,599 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { EventEmitter } from "node:events";
|
|
6
|
-
import { Readable, Writable } from "node:stream";
|
|
7
|
-
import { Buffer } from "node:buffer";
|
|
8
|
-
import { deferEmit, ensureMainLoop } from "@gjsify/utils";
|
|
9
|
-
|
|
10
|
-
//#region src/server.ts
|
|
11
|
-
var Http2ServerRequest = class extends Readable {
|
|
12
|
-
method = "GET";
|
|
13
|
-
url = "/";
|
|
14
|
-
headers = {};
|
|
15
|
-
rawHeaders = [];
|
|
16
|
-
authority = "";
|
|
17
|
-
scheme = "https";
|
|
18
|
-
httpVersion = "2.0";
|
|
19
|
-
httpVersionMajor = 2;
|
|
20
|
-
httpVersionMinor = 0;
|
|
21
|
-
complete = false;
|
|
22
|
-
socket = null;
|
|
23
|
-
trailers = {};
|
|
24
|
-
rawTrailers = [];
|
|
25
|
-
_stream = null;
|
|
26
|
-
_timeoutTimer = null;
|
|
27
|
-
get stream() {
|
|
28
|
-
return this._stream;
|
|
29
|
-
}
|
|
30
|
-
_setStream(stream) {
|
|
31
|
-
this._stream = stream;
|
|
32
|
-
}
|
|
33
|
-
constructor() {
|
|
34
|
-
super();
|
|
35
|
-
}
|
|
36
|
-
_read(_size) {}
|
|
37
|
-
_autoClose() {}
|
|
38
|
-
_pushBody(body) {
|
|
39
|
-
if (body && body.length > 0) {
|
|
40
|
-
this.push(Buffer.from(body));
|
|
41
|
-
}
|
|
42
|
-
this.push(null);
|
|
43
|
-
this.complete = true;
|
|
44
|
-
if (this._timeoutTimer) {
|
|
45
|
-
clearTimeout(this._timeoutTimer);
|
|
46
|
-
this._timeoutTimer = null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
setTimeout(msecs, callback) {
|
|
50
|
-
if (this._timeoutTimer) {
|
|
51
|
-
clearTimeout(this._timeoutTimer);
|
|
52
|
-
this._timeoutTimer = null;
|
|
53
|
-
}
|
|
54
|
-
if (callback) this.once("timeout", callback);
|
|
55
|
-
if (msecs > 0) {
|
|
56
|
-
this._timeoutTimer = setTimeout(() => {
|
|
57
|
-
this._timeoutTimer = null;
|
|
58
|
-
this.emit("timeout");
|
|
59
|
-
}, msecs);
|
|
60
|
-
}
|
|
61
|
-
return this;
|
|
62
|
-
}
|
|
63
|
-
destroy(error) {
|
|
64
|
-
if (this._timeoutTimer) {
|
|
65
|
-
clearTimeout(this._timeoutTimer);
|
|
66
|
-
this._timeoutTimer = null;
|
|
67
|
-
}
|
|
68
|
-
return super.destroy(error);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
var Http2ServerResponse = class extends Writable {
|
|
72
|
-
statusCode = 200;
|
|
73
|
-
statusMessage = "";
|
|
74
|
-
headersSent = false;
|
|
75
|
-
finished = false;
|
|
76
|
-
sendDate = true;
|
|
77
|
-
_soupMsg;
|
|
78
|
-
_headers = new Map();
|
|
79
|
-
_streaming = false;
|
|
80
|
-
_timeoutTimer = null;
|
|
81
|
-
_stream = null;
|
|
82
|
-
get stream() {
|
|
83
|
-
return this._stream;
|
|
84
|
-
}
|
|
85
|
-
get socket() {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
_setStream(stream) {
|
|
89
|
-
this._stream = stream;
|
|
90
|
-
}
|
|
91
|
-
constructor(soupMsg) {
|
|
92
|
-
super();
|
|
93
|
-
this._soupMsg = soupMsg;
|
|
94
|
-
}
|
|
95
|
-
setHeader(name, value) {
|
|
96
|
-
this._headers.set(name.toLowerCase(), typeof value === "number" ? String(value) : value);
|
|
97
|
-
return this;
|
|
98
|
-
}
|
|
99
|
-
getHeader(name) {
|
|
100
|
-
return this._headers.get(name.toLowerCase());
|
|
101
|
-
}
|
|
102
|
-
removeHeader(name) {
|
|
103
|
-
this._headers.delete(name.toLowerCase());
|
|
104
|
-
}
|
|
105
|
-
hasHeader(name) {
|
|
106
|
-
return this._headers.has(name.toLowerCase());
|
|
107
|
-
}
|
|
108
|
-
getHeaderNames() {
|
|
109
|
-
return Array.from(this._headers.keys());
|
|
110
|
-
}
|
|
111
|
-
getHeaders() {
|
|
112
|
-
const result = {};
|
|
113
|
-
for (const [key, value] of this._headers) {
|
|
114
|
-
result[key] = value;
|
|
115
|
-
}
|
|
116
|
-
return result;
|
|
117
|
-
}
|
|
118
|
-
appendHeader(name, value) {
|
|
119
|
-
const lower = name.toLowerCase();
|
|
120
|
-
const existing = this._headers.get(lower);
|
|
121
|
-
if (existing === undefined) {
|
|
122
|
-
this._headers.set(lower, value);
|
|
123
|
-
} else if (Array.isArray(existing)) {
|
|
124
|
-
Array.isArray(value) ? existing.push(...value) : existing.push(value);
|
|
125
|
-
} else {
|
|
126
|
-
this._headers.set(lower, Array.isArray(value) ? [existing, ...value] : [existing, value]);
|
|
127
|
-
}
|
|
128
|
-
return this;
|
|
129
|
-
}
|
|
130
|
-
flushHeaders() {
|
|
131
|
-
if (!this.headersSent) this.headersSent = true;
|
|
132
|
-
}
|
|
133
|
-
writeHead(statusCode, statusMessage, headers) {
|
|
134
|
-
this.statusCode = statusCode;
|
|
135
|
-
if (typeof statusMessage === "object") {
|
|
136
|
-
headers = statusMessage;
|
|
137
|
-
statusMessage = undefined;
|
|
138
|
-
}
|
|
139
|
-
if (typeof statusMessage === "string") this.statusMessage = statusMessage;
|
|
140
|
-
if (headers) {
|
|
141
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
142
|
-
this.setHeader(key, value);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return this;
|
|
146
|
-
}
|
|
147
|
-
respond(headers, options) {
|
|
148
|
-
const status = Number(headers[":status"] ?? 200);
|
|
149
|
-
const rest = {};
|
|
150
|
-
for (const [k, v] of Object.entries(headers)) {
|
|
151
|
-
if (k === ":status") continue;
|
|
152
|
-
rest[k] = typeof v === "number" ? String(v) : v;
|
|
153
|
-
}
|
|
154
|
-
this.writeHead(status, rest);
|
|
155
|
-
if (options?.endStream) this.end();
|
|
156
|
-
}
|
|
157
|
-
writeContinue(callback) {
|
|
158
|
-
if (callback) Promise.resolve().then(callback);
|
|
159
|
-
}
|
|
160
|
-
writeEarlyHints(_hints, callback) {
|
|
161
|
-
if (callback) Promise.resolve().then(callback);
|
|
162
|
-
}
|
|
163
|
-
addTrailers(_headers) {}
|
|
164
|
-
setTimeout(msecs, callback) {
|
|
165
|
-
if (this._timeoutTimer) {
|
|
166
|
-
clearTimeout(this._timeoutTimer);
|
|
167
|
-
this._timeoutTimer = null;
|
|
168
|
-
}
|
|
169
|
-
if (callback) this.once("timeout", callback);
|
|
170
|
-
if (msecs > 0) {
|
|
171
|
-
this._timeoutTimer = setTimeout(() => {
|
|
172
|
-
this._timeoutTimer = null;
|
|
173
|
-
this.emit("timeout");
|
|
174
|
-
}, msecs);
|
|
175
|
-
}
|
|
176
|
-
return this;
|
|
177
|
-
}
|
|
178
|
-
_startStreaming() {
|
|
179
|
-
if (this._streaming) return;
|
|
180
|
-
this._streaming = true;
|
|
181
|
-
this.headersSent = true;
|
|
182
|
-
if (this._timeoutTimer) {
|
|
183
|
-
clearTimeout(this._timeoutTimer);
|
|
184
|
-
this._timeoutTimer = null;
|
|
185
|
-
}
|
|
186
|
-
this._soupMsg.set_status(this.statusCode, this.statusMessage || null);
|
|
187
|
-
const responseHeaders = this._soupMsg.get_response_headers();
|
|
188
|
-
if (this._headers.has("content-length")) {
|
|
189
|
-
responseHeaders.set_encoding(Soup.Encoding.CONTENT_LENGTH);
|
|
190
|
-
} else {
|
|
191
|
-
responseHeaders.set_encoding(Soup.Encoding.CHUNKED);
|
|
192
|
-
}
|
|
193
|
-
for (const [key, value] of this._headers) {
|
|
194
|
-
if (Array.isArray(value)) {
|
|
195
|
-
for (const v of value) responseHeaders.append(key, v);
|
|
196
|
-
} else {
|
|
197
|
-
responseHeaders.replace(key, value);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
_write(chunk, encoding, callback) {
|
|
202
|
-
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
|
|
203
|
-
this._startStreaming();
|
|
204
|
-
const responseBody = this._soupMsg.get_response_body();
|
|
205
|
-
responseBody.append(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
|
|
206
|
-
this._soupMsg.unpause();
|
|
207
|
-
callback();
|
|
208
|
-
}
|
|
209
|
-
_final(callback) {
|
|
210
|
-
if (this._streaming) {
|
|
211
|
-
const responseBody = this._soupMsg.get_response_body();
|
|
212
|
-
responseBody.complete();
|
|
213
|
-
this._soupMsg.unpause();
|
|
214
|
-
} else {
|
|
215
|
-
this._sendBatchResponse();
|
|
216
|
-
}
|
|
217
|
-
this.finished = true;
|
|
218
|
-
callback();
|
|
219
|
-
}
|
|
220
|
-
_sendBatchResponse() {
|
|
221
|
-
if (this.headersSent) return;
|
|
222
|
-
this.headersSent = true;
|
|
223
|
-
if (this._timeoutTimer) {
|
|
224
|
-
clearTimeout(this._timeoutTimer);
|
|
225
|
-
this._timeoutTimer = null;
|
|
226
|
-
}
|
|
227
|
-
this._soupMsg.set_status(this.statusCode, this.statusMessage || null);
|
|
228
|
-
const responseHeaders = this._soupMsg.get_response_headers();
|
|
229
|
-
for (const [key, value] of this._headers) {
|
|
230
|
-
if (Array.isArray(value)) {
|
|
231
|
-
for (const v of value) responseHeaders.append(key, v);
|
|
232
|
-
} else {
|
|
233
|
-
responseHeaders.replace(key, value);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const contentType = this._headers.get("content-type") || "text/plain";
|
|
237
|
-
this._soupMsg.set_response(contentType, Soup.MemoryUse.COPY, new Uint8Array(0));
|
|
238
|
-
}
|
|
239
|
-
end(chunk, encoding, callback) {
|
|
240
|
-
if (typeof chunk === "function") {
|
|
241
|
-
callback = chunk;
|
|
242
|
-
chunk = undefined;
|
|
243
|
-
} else if (typeof encoding === "function") {
|
|
244
|
-
callback = encoding;
|
|
245
|
-
encoding = undefined;
|
|
246
|
-
}
|
|
247
|
-
if (chunk != null) {
|
|
248
|
-
this.write(chunk, encoding);
|
|
249
|
-
}
|
|
250
|
-
super.end(callback);
|
|
251
|
-
return this;
|
|
252
|
-
}
|
|
253
|
-
respondWithFD(_fd, _headers, _options) {
|
|
254
|
-
throw new Error("http2 respondWithFD is not yet implemented in GJS (Phase 2)");
|
|
255
|
-
}
|
|
256
|
-
respondWithFile(_path, _headers, _options) {
|
|
257
|
-
throw new Error("http2 respondWithFile is not yet implemented in GJS (Phase 2)");
|
|
258
|
-
}
|
|
259
|
-
pushStream(_headers, _options, _callback) {
|
|
260
|
-
throw new Error("http2 server push is not yet implemented in GJS (Phase 2)");
|
|
261
|
-
}
|
|
262
|
-
createPushResponse(_headers, _callback) {
|
|
263
|
-
throw new Error("http2 server push is not yet implemented in GJS (Phase 2)");
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
var ServerHttp2Stream = class extends EventEmitter {
|
|
267
|
-
id = 1;
|
|
268
|
-
pushAllowed = false;
|
|
269
|
-
sentHeaders = {};
|
|
270
|
-
_res;
|
|
271
|
-
_session;
|
|
272
|
-
get session() {
|
|
273
|
-
return this._session;
|
|
274
|
-
}
|
|
275
|
-
get headersSent() {
|
|
276
|
-
return this._res.headersSent;
|
|
277
|
-
}
|
|
278
|
-
get closed() {
|
|
279
|
-
return this._res.writableEnded;
|
|
280
|
-
}
|
|
281
|
-
get destroyed() {
|
|
282
|
-
return this._res.destroyed;
|
|
283
|
-
}
|
|
284
|
-
get pending() {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
get state() {
|
|
288
|
-
return this.closed ? constants.NGHTTP2_STREAM_STATE_CLOSED : constants.NGHTTP2_STREAM_STATE_OPEN;
|
|
289
|
-
}
|
|
290
|
-
constructor(res, session = null) {
|
|
291
|
-
super();
|
|
292
|
-
this._res = res;
|
|
293
|
-
this._session = session;
|
|
294
|
-
res.on("finish", () => this.emit("close"));
|
|
295
|
-
res.on("error", (err) => this.emit("error", err));
|
|
296
|
-
}
|
|
297
|
-
respond(headers, options) {
|
|
298
|
-
this._res.respond(headers, options);
|
|
299
|
-
}
|
|
300
|
-
write(chunk, encoding, callback) {
|
|
301
|
-
return this._res.write(chunk, encoding, callback);
|
|
302
|
-
}
|
|
303
|
-
end(chunk, encoding, callback) {
|
|
304
|
-
this._res.end(chunk, encoding, callback);
|
|
305
|
-
return this;
|
|
306
|
-
}
|
|
307
|
-
destroy(error) {
|
|
308
|
-
this._res.destroy(error);
|
|
309
|
-
return this;
|
|
310
|
-
}
|
|
311
|
-
close(code, callback) {
|
|
312
|
-
if (callback) this.once("close", callback);
|
|
313
|
-
this._res.end();
|
|
314
|
-
}
|
|
315
|
-
priority(_options) {}
|
|
316
|
-
setTimeout(msecs, callback) {
|
|
317
|
-
this._res.setTimeout(msecs, callback);
|
|
318
|
-
return this;
|
|
319
|
-
}
|
|
320
|
-
sendTrailers(_headers) {}
|
|
321
|
-
additionalHeaders(_headers) {}
|
|
322
|
-
respondWithFD(_fd, _headers, _options) {
|
|
323
|
-
throw new Error("http2 respondWithFD is not yet implemented in GJS (Phase 2)");
|
|
324
|
-
}
|
|
325
|
-
respondWithFile(_path, _headers, _options) {
|
|
326
|
-
throw new Error("http2 respondWithFile is not yet implemented in GJS (Phase 2)");
|
|
327
|
-
}
|
|
328
|
-
pushStream(_headers, _options, _callback) {
|
|
329
|
-
throw new Error("http2 server push is not yet implemented in GJS (Phase 2)");
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
var ServerHttp2Session = class extends EventEmitter {
|
|
333
|
-
type = constants.NGHTTP2_SESSION_SERVER;
|
|
334
|
-
alpnProtocol = "h2";
|
|
335
|
-
encrypted = true;
|
|
336
|
-
_closed = false;
|
|
337
|
-
_destroyed = false;
|
|
338
|
-
_settings;
|
|
339
|
-
constructor() {
|
|
340
|
-
super();
|
|
341
|
-
this._settings = getDefaultSettings();
|
|
342
|
-
}
|
|
343
|
-
get closed() {
|
|
344
|
-
return this._closed;
|
|
345
|
-
}
|
|
346
|
-
get destroyed() {
|
|
347
|
-
return this._destroyed;
|
|
348
|
-
}
|
|
349
|
-
get pendingSettingsAck() {
|
|
350
|
-
return false;
|
|
351
|
-
}
|
|
352
|
-
get localSettings() {
|
|
353
|
-
return { ...this._settings };
|
|
354
|
-
}
|
|
355
|
-
get remoteSettings() {
|
|
356
|
-
return getDefaultSettings();
|
|
357
|
-
}
|
|
358
|
-
get originSet() {
|
|
359
|
-
return [];
|
|
360
|
-
}
|
|
361
|
-
settings(settings, callback) {
|
|
362
|
-
Object.assign(this._settings, settings);
|
|
363
|
-
if (callback) Promise.resolve().then(callback);
|
|
364
|
-
}
|
|
365
|
-
goaway(code, _lastStreamId, _data) {
|
|
366
|
-
this.emit("goaway", code ?? constants.NGHTTP2_NO_ERROR);
|
|
367
|
-
this.destroy();
|
|
368
|
-
}
|
|
369
|
-
ping(_payload, callback) {
|
|
370
|
-
const buf = new Uint8Array(8);
|
|
371
|
-
if (callback) Promise.resolve().then(() => callback(null, 0, buf));
|
|
372
|
-
return true;
|
|
373
|
-
}
|
|
374
|
-
close(callback) {
|
|
375
|
-
if (this._closed) return;
|
|
376
|
-
this._closed = true;
|
|
377
|
-
this.emit("close");
|
|
378
|
-
if (callback) callback();
|
|
379
|
-
}
|
|
380
|
-
destroy(error, code) {
|
|
381
|
-
if (this._destroyed) return;
|
|
382
|
-
this._destroyed = true;
|
|
383
|
-
this._closed = true;
|
|
384
|
-
if (error) this.emit("error", error);
|
|
385
|
-
if (code !== undefined) this.emit("goaway", code);
|
|
386
|
-
this.emit("close");
|
|
387
|
-
}
|
|
388
|
-
altsvc(_alt, _originOrStream) {}
|
|
389
|
-
origin(..._origins) {}
|
|
390
|
-
ref() {}
|
|
391
|
-
unref() {}
|
|
392
|
-
};
|
|
393
|
-
const _activeServers = new Set();
|
|
394
|
-
var Http2Server = class extends EventEmitter {
|
|
395
|
-
listening = false;
|
|
396
|
-
maxHeadersCount = 2e3;
|
|
397
|
-
timeout = 0;
|
|
398
|
-
_soupServer = null;
|
|
399
|
-
_address = null;
|
|
400
|
-
_options;
|
|
401
|
-
get soupServer() {
|
|
402
|
-
return this._soupServer;
|
|
403
|
-
}
|
|
404
|
-
constructor(options, handler) {
|
|
405
|
-
super();
|
|
406
|
-
if (typeof options === "function") {
|
|
407
|
-
handler = options;
|
|
408
|
-
options = {};
|
|
409
|
-
}
|
|
410
|
-
this._options = options ?? {};
|
|
411
|
-
if (handler) this.on("request", handler);
|
|
412
|
-
}
|
|
413
|
-
listen(...args) {
|
|
414
|
-
let port = 0;
|
|
415
|
-
let hostname = "0.0.0.0";
|
|
416
|
-
let callback;
|
|
417
|
-
for (const arg of args) {
|
|
418
|
-
if (typeof arg === "number") port = arg;
|
|
419
|
-
else if (typeof arg === "string") hostname = arg;
|
|
420
|
-
else if (typeof arg === "function") callback = arg;
|
|
421
|
-
}
|
|
422
|
-
if (callback) this.once("listening", callback);
|
|
423
|
-
try {
|
|
424
|
-
this._soupServer = new Soup.Server({});
|
|
425
|
-
this._configureSoupServer(this._soupServer);
|
|
426
|
-
this._soupServer.add_handler(null, (_server, msg, _path) => {
|
|
427
|
-
this._handleRequest(msg);
|
|
428
|
-
});
|
|
429
|
-
this._soupServer.listen_local(port, Soup.ServerListenOptions.IPV4_ONLY);
|
|
430
|
-
ensureMainLoop();
|
|
431
|
-
const listeners = this._soupServer.get_listeners();
|
|
432
|
-
let actualPort = port;
|
|
433
|
-
if (listeners && listeners.length > 0) {
|
|
434
|
-
const addr = listeners[0].get_local_address();
|
|
435
|
-
if (addr && typeof addr.get_port === "function") {
|
|
436
|
-
actualPort = addr.get_port();
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
this.listening = true;
|
|
440
|
-
this._address = {
|
|
441
|
-
port: actualPort,
|
|
442
|
-
family: "IPv4",
|
|
443
|
-
address: hostname
|
|
444
|
-
};
|
|
445
|
-
_activeServers.add(this);
|
|
446
|
-
deferEmit(this, "listening");
|
|
447
|
-
} catch (err) {
|
|
448
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
449
|
-
if (this.listenerCount("error") === 0) throw error;
|
|
450
|
-
deferEmit(this, "error", error);
|
|
451
|
-
}
|
|
452
|
-
return this;
|
|
453
|
-
}
|
|
454
|
-
_configureSoupServer(_server) {}
|
|
455
|
-
_handleRequest(soupMsg) {
|
|
456
|
-
const req = new Http2ServerRequest();
|
|
457
|
-
const res = new Http2ServerResponse(soupMsg);
|
|
458
|
-
req.method = soupMsg.get_method();
|
|
459
|
-
const uri = soupMsg.get_uri();
|
|
460
|
-
const path = uri.get_path();
|
|
461
|
-
const query = uri.get_query();
|
|
462
|
-
req.url = query ? path + "?" + query : path;
|
|
463
|
-
req.authority = uri.get_host() ?? "";
|
|
464
|
-
req.scheme = uri.get_scheme() ?? "http";
|
|
465
|
-
const httpVersion = soupMsg.get_http_version();
|
|
466
|
-
if (httpVersion === Soup.HTTPVersion.HTTP_2_0) {
|
|
467
|
-
req.httpVersion = "2.0";
|
|
468
|
-
req.httpVersionMajor = 2;
|
|
469
|
-
req.httpVersionMinor = 0;
|
|
470
|
-
} else {
|
|
471
|
-
req.httpVersion = "1.1";
|
|
472
|
-
req.httpVersionMajor = 1;
|
|
473
|
-
req.httpVersionMinor = 1;
|
|
474
|
-
}
|
|
475
|
-
const requestHeaders = soupMsg.get_request_headers();
|
|
476
|
-
requestHeaders.foreach((name, value) => {
|
|
477
|
-
const lower = name.toLowerCase();
|
|
478
|
-
req.rawHeaders.push(name, value);
|
|
479
|
-
if (lower in req.headers) {
|
|
480
|
-
const existing = req.headers[lower];
|
|
481
|
-
if (Array.isArray(existing)) {
|
|
482
|
-
existing.push(value);
|
|
483
|
-
} else {
|
|
484
|
-
req.headers[lower] = [existing, value];
|
|
485
|
-
}
|
|
486
|
-
} else {
|
|
487
|
-
req.headers[lower] = value;
|
|
488
|
-
}
|
|
489
|
-
});
|
|
490
|
-
const remoteHost = soupMsg.get_remote_host() ?? "127.0.0.1";
|
|
491
|
-
const remoteAddr = soupMsg.get_remote_address();
|
|
492
|
-
const remotePort = remoteAddr instanceof Gio.InetSocketAddress ? remoteAddr.get_port() : 0;
|
|
493
|
-
req.socket = {
|
|
494
|
-
remoteAddress: remoteHost,
|
|
495
|
-
remotePort,
|
|
496
|
-
localAddress: this._address?.address ?? "127.0.0.1",
|
|
497
|
-
localPort: this._address?.port ?? 0,
|
|
498
|
-
encrypted: this instanceof Http2SecureServer
|
|
499
|
-
};
|
|
500
|
-
const body = soupMsg.get_request_body();
|
|
501
|
-
if (body?.data && body.data.length > 0) {
|
|
502
|
-
req._pushBody(body.data);
|
|
503
|
-
} else {
|
|
504
|
-
req._pushBody(null);
|
|
505
|
-
}
|
|
506
|
-
const streamHeaders = {
|
|
507
|
-
":method": req.method,
|
|
508
|
-
":path": req.url,
|
|
509
|
-
":authority": req.authority,
|
|
510
|
-
":scheme": req.scheme,
|
|
511
|
-
...req.headers
|
|
512
|
-
};
|
|
513
|
-
soupMsg.pause();
|
|
514
|
-
res.on("finish", () => soupMsg.unpause());
|
|
515
|
-
const session = new ServerHttp2Session();
|
|
516
|
-
const stream = new ServerHttp2Stream(res, session);
|
|
517
|
-
req._setStream(stream);
|
|
518
|
-
res._setStream(stream);
|
|
519
|
-
this.emit("stream", stream, streamHeaders);
|
|
520
|
-
this.emit("request", req, res);
|
|
521
|
-
}
|
|
522
|
-
address() {
|
|
523
|
-
return this._address;
|
|
524
|
-
}
|
|
525
|
-
close(callback) {
|
|
526
|
-
if (callback) this.once("close", callback);
|
|
527
|
-
if (this._soupServer) {
|
|
528
|
-
this._soupServer.disconnect();
|
|
529
|
-
this._soupServer = null;
|
|
530
|
-
}
|
|
531
|
-
this.listening = false;
|
|
532
|
-
_activeServers.delete(this);
|
|
533
|
-
deferEmit(this, "close");
|
|
534
|
-
return this;
|
|
535
|
-
}
|
|
536
|
-
setTimeout(msecs, callback) {
|
|
537
|
-
this.timeout = msecs;
|
|
538
|
-
if (callback) this.on("timeout", callback);
|
|
539
|
-
return this;
|
|
540
|
-
}
|
|
541
|
-
};
|
|
542
|
-
var Http2SecureServer = class extends Http2Server {
|
|
543
|
-
_tlsCert = null;
|
|
544
|
-
constructor(options, handler) {
|
|
545
|
-
super(options, handler);
|
|
546
|
-
if (options.cert && options.key) {
|
|
547
|
-
const certPem = _toPemString(options.cert);
|
|
548
|
-
const keyPem = _toPemString(options.key);
|
|
549
|
-
this._tlsCert = _createTlsCertificate(certPem, keyPem);
|
|
550
|
-
} else if (options.pfx) {}
|
|
551
|
-
}
|
|
552
|
-
_configureSoupServer(server) {
|
|
553
|
-
if (this._tlsCert) {
|
|
554
|
-
server.set_tls_certificate(this._tlsCert);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
setSecureContext(options) {
|
|
558
|
-
if (options.cert && options.key) {
|
|
559
|
-
const certPem = _toPemString(options.cert);
|
|
560
|
-
const keyPem = _toPemString(options.key);
|
|
561
|
-
this._tlsCert = _createTlsCertificate(certPem, keyPem);
|
|
562
|
-
if (this._soupServer && this._tlsCert) {
|
|
563
|
-
this._soupServer.set_tls_certificate(this._tlsCert);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
function _toPemString(value) {
|
|
569
|
-
if (Array.isArray(value)) {
|
|
570
|
-
return value.map(_toPemString).join("\n");
|
|
571
|
-
}
|
|
572
|
-
return Buffer.isBuffer(value) ? value.toString("utf8") : value;
|
|
573
|
-
}
|
|
574
|
-
function _createTlsCertificate(certPem, keyPem) {
|
|
575
|
-
const combined = certPem.trimEnd() + "\n" + keyPem.trimEnd() + "\n";
|
|
576
|
-
try {
|
|
577
|
-
return Gio.TlsCertificate.new_from_pem(combined, -1);
|
|
578
|
-
} catch (err) {
|
|
579
|
-
const tmpDir = GLib.get_tmp_dir();
|
|
580
|
-
const certPath = GLib.build_filenamev([tmpDir, "gjsify-http2-cert.pem"]);
|
|
581
|
-
const keyPath = GLib.build_filenamev([tmpDir, "gjsify-http2-key.pem"]);
|
|
582
|
-
try {
|
|
583
|
-
GLib.file_set_contents(certPath, certPem);
|
|
584
|
-
GLib.file_set_contents(keyPath, keyPem);
|
|
585
|
-
const tlsCert = Gio.TlsCertificate.new_from_files(certPath, keyPath);
|
|
586
|
-
return tlsCert;
|
|
587
|
-
} finally {
|
|
588
|
-
try {
|
|
589
|
-
Gio.File.new_for_path(certPath).delete(null);
|
|
590
|
-
} catch {}
|
|
591
|
-
try {
|
|
592
|
-
Gio.File.new_for_path(keyPath).delete(null);
|
|
593
|
-
} catch {}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
//#endregion
|
|
599
|
-
export { Http2SecureServer, Http2Server, Http2ServerRequest, Http2ServerResponse, ServerHttp2Session, ServerHttp2Stream };
|
|
1
|
+
import{constants as e,getDefaultSettings as t}from"./protocol.js";import n from"@girs/soup-3.0";import r from"@girs/gio-2.0";import i from"@girs/glib-2.0";import{EventEmitter as a}from"node:events";import{Readable as o,Writable as s}from"node:stream";import{Buffer as c}from"node:buffer";import{deferEmit as l,ensureMainLoop as u}from"@gjsify/utils";var d=class extends o{method=`GET`;url=`/`;headers={};rawHeaders=[];authority=``;scheme=`https`;httpVersion=`2.0`;httpVersionMajor=2;httpVersionMinor=0;complete=!1;socket=null;trailers={};rawTrailers=[];_stream=null;_timeoutTimer=null;get stream(){return this._stream}_setStream(e){this._stream=e}constructor(){super()}_read(e){}_autoClose(){}_pushBody(e){e&&e.length>0&&this.push(c.from(e)),this.push(null),this.complete=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null)}setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}destroy(e){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),super.destroy(e)}},f=class extends s{statusCode=200;statusMessage=``;headersSent=!1;finished=!1;sendDate=!0;_soupMsg;_headers=new Map;_streaming=!1;_timeoutTimer=null;_stream=null;get stream(){return this._stream}get socket(){return null}_setStream(e){this._stream=e}constructor(e){super(),this._soupMsg=e}setHeader(e,t){return this._headers.set(e.toLowerCase(),typeof t==`number`?String(t):t),this}getHeader(e){return this._headers.get(e.toLowerCase())}removeHeader(e){this._headers.delete(e.toLowerCase())}hasHeader(e){return this._headers.has(e.toLowerCase())}getHeaderNames(){return Array.from(this._headers.keys())}getHeaders(){let e={};for(let[t,n]of this._headers)e[t]=n;return e}appendHeader(e,t){let n=e.toLowerCase(),r=this._headers.get(n);return r===void 0?this._headers.set(n,t):Array.isArray(r)?Array.isArray(t)?r.push(...t):r.push(t):this._headers.set(n,Array.isArray(t)?[r,...t]:[r,t]),this}flushHeaders(){this.headersSent||=!0}writeHead(e,t,n){if(this.statusCode=e,typeof t==`object`&&(n=t,t=void 0),typeof t==`string`&&(this.statusMessage=t),n)for(let[e,t]of Object.entries(n))this.setHeader(e,t);return this}respond(e,t){let n=Number(e[`:status`]??200),r={};for(let[t,n]of Object.entries(e))t!==`:status`&&(r[t]=typeof n==`number`?String(n):n);this.writeHead(n,r),t?.endStream&&this.end()}writeContinue(e){e&&Promise.resolve().then(e)}writeEarlyHints(e,t){t&&Promise.resolve().then(t)}addTrailers(e){}setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}_startStreaming(){if(this._streaming)return;this._streaming=!0,this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let e=this._soupMsg.get_response_headers();this._headers.has(`content-length`)?e.set_encoding(n.Encoding.CONTENT_LENGTH):e.set_encoding(n.Encoding.CHUNKED);for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n)}_write(e,t,n){let r=c.isBuffer(e)?e:c.from(e,t);this._startStreaming(),this._soupMsg.get_response_body().append(new Uint8Array(r.buffer,r.byteOffset,r.byteLength)),this._soupMsg.unpause(),n()}_final(e){this._streaming?(this._soupMsg.get_response_body().complete(),this._soupMsg.unpause()):this._sendBatchResponse(),this.finished=!0,e()}_sendBatchResponse(){if(this.headersSent)return;this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._soupMsg.set_status(this.statusCode,this.statusMessage||null);let e=this._soupMsg.get_response_headers();for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n);let t=this._headers.get(`content-type`)||`text/plain`;this._soupMsg.set_response(t,n.MemoryUse.COPY,new Uint8Array)}end(e,t,n){return typeof e==`function`?(n=e,e=void 0):typeof t==`function`&&(n=t,t=void 0),e!=null&&this.write(e,t),super.end(n),this}respondWithFD(e,t,n){throw Error(`http2 respondWithFD is not yet implemented in GJS (Phase 2)`)}respondWithFile(e,t,n){throw Error(`http2 respondWithFile is not yet implemented in GJS (Phase 2)`)}pushStream(e,t,n){throw Error(`http2 server push is not yet implemented in GJS (Phase 2)`)}createPushResponse(e,t){throw Error(`http2 server push is not yet implemented in GJS (Phase 2)`)}},p=class extends a{id=1;pushAllowed=!1;sentHeaders={};_res;_session;get session(){return this._session}get headersSent(){return this._res.headersSent}get closed(){return this._res.writableEnded}get destroyed(){return this._res.destroyed}get pending(){return!1}get state(){return this.closed?e.NGHTTP2_STREAM_STATE_CLOSED:e.NGHTTP2_STREAM_STATE_OPEN}constructor(e,t=null){super(),this._res=e,this._session=t,e.on(`finish`,()=>this.emit(`close`)),e.on(`error`,e=>this.emit(`error`,e))}respond(e,t){this._res.respond(e,t)}write(e,t,n){return this._res.write(e,t,n)}end(e,t,n){return this._res.end(e,t,n),this}destroy(e){return this._res.destroy(e),this}close(e,t){t&&this.once(`close`,t),this._res.end()}priority(e){}setTimeout(e,t){return this._res.setTimeout(e,t),this}sendTrailers(e){}additionalHeaders(e){}respondWithFD(e,t,n){throw Error(`http2 respondWithFD is not yet implemented in GJS (Phase 2)`)}respondWithFile(e,t,n){throw Error(`http2 respondWithFile is not yet implemented in GJS (Phase 2)`)}pushStream(e,t,n){throw Error(`http2 server push is not yet implemented in GJS (Phase 2)`)}},m=class extends a{type=e.NGHTTP2_SESSION_SERVER;alpnProtocol=`h2`;encrypted=!0;_closed=!1;_destroyed=!1;_settings;constructor(){super(),this._settings=t()}get closed(){return this._closed}get destroyed(){return this._destroyed}get pendingSettingsAck(){return!1}get localSettings(){return{...this._settings}}get remoteSettings(){return t()}get originSet(){return[]}settings(e,t){Object.assign(this._settings,e),t&&Promise.resolve().then(t)}goaway(t,n,r){this.emit(`goaway`,t??e.NGHTTP2_NO_ERROR),this.destroy()}ping(e,t){let n=new Uint8Array(8);return t&&Promise.resolve().then(()=>t(null,0,n)),!0}close(e){this._closed||(this._closed=!0,this.emit(`close`),e&&e())}destroy(e,t){this._destroyed||(this._destroyed=!0,this._closed=!0,e&&this.emit(`error`,e),t!==void 0&&this.emit(`goaway`,t),this.emit(`close`))}altsvc(e,t){}origin(...e){}ref(){}unref(){}};const h=new Set;var g=class extends a{listening=!1;maxHeadersCount=2e3;timeout=0;_soupServer=null;_address=null;_options;get soupServer(){return this._soupServer}constructor(e,t){super(),typeof e==`function`&&(t=e,e={}),this._options=e??{},t&&this.on(`request`,t)}listen(...e){let t=0,r=`0.0.0.0`,i;for(let n of e)typeof n==`number`?t=n:typeof n==`string`?r=n:typeof n==`function`&&(i=n);i&&this.once(`listening`,i);try{this._soupServer=new n.Server({}),this._configureSoupServer(this._soupServer),this._soupServer.add_handler(null,(e,t,n)=>{this._handleRequest(t)}),this._soupServer.listen_local(t,n.ServerListenOptions.IPV4_ONLY),u();let e=this._soupServer.get_listeners(),i=t;if(e&&e.length>0){let t=e[0].get_local_address();t&&typeof t.get_port==`function`&&(i=t.get_port())}this.listening=!0,this._address={port:i,family:`IPv4`,address:r},h.add(this),l(this,`listening`)}catch(e){let t=e instanceof Error?e:Error(String(e));if(this.listenerCount(`error`)===0)throw t;l(this,`error`,t)}return this}_configureSoupServer(e){}_handleRequest(e){let t=new d,i=new f(e);t.method=e.get_method();let a=e.get_uri(),o=a.get_path(),s=a.get_query();t.url=s?o+`?`+s:o,t.authority=a.get_host()??``,t.scheme=a.get_scheme()??`http`,e.get_http_version()===n.HTTPVersion.HTTP_2_0?(t.httpVersion=`2.0`,t.httpVersionMajor=2,t.httpVersionMinor=0):(t.httpVersion=`1.1`,t.httpVersionMajor=1,t.httpVersionMinor=1),e.get_request_headers().foreach((e,n)=>{let r=e.toLowerCase();if(t.rawHeaders.push(e,n),r in t.headers){let e=t.headers[r];Array.isArray(e)?e.push(n):t.headers[r]=[e,n]}else t.headers[r]=n});let c=e.get_remote_host()??`127.0.0.1`,l=e.get_remote_address();t.socket={remoteAddress:c,remotePort:l instanceof r.InetSocketAddress?l.get_port():0,localAddress:this._address?.address??`127.0.0.1`,localPort:this._address?.port??0,encrypted:this instanceof _};let u=e.get_request_body();u?.data&&u.data.length>0?t._pushBody(u.data):t._pushBody(null);let h={":method":t.method,":path":t.url,":authority":t.authority,":scheme":t.scheme,...t.headers};e.pause(),i.on(`finish`,()=>e.unpause());let g=new p(i,new m);t._setStream(g),i._setStream(g),this.emit(`stream`,g,h),this.emit(`request`,t,i)}address(){return this._address}close(e){return e&&this.once(`close`,e),this._soupServer&&=(this._soupServer.disconnect(),null),this.listening=!1,h.delete(this),l(this,`close`),this}setTimeout(e,t){return this.timeout=e,t&&this.on(`timeout`,t),this}},_=class extends g{_tlsCert=null;constructor(e,t){if(super(e,t),e.cert&&e.key){let t=v(e.cert),n=v(e.key);this._tlsCert=y(t,n)}else e.pfx}_configureSoupServer(e){this._tlsCert&&e.set_tls_certificate(this._tlsCert)}setSecureContext(e){if(e.cert&&e.key){let t=v(e.cert),n=v(e.key);this._tlsCert=y(t,n),this._soupServer&&this._tlsCert&&this._soupServer.set_tls_certificate(this._tlsCert)}}};function v(e){return Array.isArray(e)?e.map(v).join(`
|
|
2
|
+
`):c.isBuffer(e)?e.toString(`utf8`):e}function y(e,t){let n=e.trimEnd()+`
|
|
3
|
+
`+t.trimEnd()+`
|
|
4
|
+
`;try{return r.TlsCertificate.new_from_pem(n,-1)}catch{let n=i.get_tmp_dir(),a=i.build_filenamev([n,`gjsify-http2-cert.pem`]),o=i.build_filenamev([n,`gjsify-http2-key.pem`]);try{return i.file_set_contents(a,e),i.file_set_contents(o,t),r.TlsCertificate.new_from_files(a,o)}finally{try{r.File.new_for_path(a).delete(null)}catch{}try{r.File.new_for_path(o).delete(null)}catch{}}}}export{_ as Http2SecureServer,g as Http2Server,d as Http2ServerRequest,f as Http2ServerResponse,m as ServerHttp2Session,p as ServerHttp2Stream};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/http2",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"description": "Node.js http2 module for Gjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -30,17 +30,17 @@
|
|
|
30
30
|
"http2"
|
|
31
31
|
],
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@gjsify/cli": "^0.3.
|
|
34
|
-
"@gjsify/unit": "^0.3.
|
|
35
|
-
"@types/node": "^25.6.
|
|
33
|
+
"@gjsify/cli": "^0.3.18",
|
|
34
|
+
"@gjsify/unit": "^0.3.18",
|
|
35
|
+
"@types/node": "^25.6.2",
|
|
36
36
|
"typescript": "^6.0.3"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@girs/gio-2.0": "2.88.0-4.0.0-rc.
|
|
40
|
-
"@girs/glib-2.0": "2.88.0-4.0.0-rc.
|
|
41
|
-
"@girs/gobject-2.0": "2.88.0-4.0.0-rc.
|
|
42
|
-
"@girs/soup-3.0": "3.6.6-4.0.0-rc.
|
|
43
|
-
"@gjsify/events": "^0.3.
|
|
44
|
-
"@gjsify/utils": "^0.3.
|
|
39
|
+
"@girs/gio-2.0": "2.88.0-4.0.0-rc.14",
|
|
40
|
+
"@girs/glib-2.0": "2.88.0-4.0.0-rc.14",
|
|
41
|
+
"@girs/gobject-2.0": "2.88.0-4.0.0-rc.14",
|
|
42
|
+
"@girs/soup-3.0": "3.6.6-4.0.0-rc.14",
|
|
43
|
+
"@gjsify/events": "^0.3.18",
|
|
44
|
+
"@gjsify/utils": "^0.3.18"
|
|
45
45
|
}
|
|
46
46
|
}
|