@gjsify/http 0.3.12 → 0.3.14
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-request.js +233 -221
- package/lib/esm/constants.js +101 -101
- package/lib/esm/incoming-message.js +66 -64
- package/lib/esm/index.js +112 -97
- package/lib/esm/server-request-socket.js +69 -69
- package/lib/esm/server.js +387 -376
- package/lib/esm/validators.js +19 -19
- package/package.json +13 -13
package/lib/esm/server.js
CHANGED
|
@@ -1,380 +1,391 @@
|
|
|
1
|
-
import { EventEmitter } from "node:events";
|
|
2
|
-
import { Writable } from "node:stream";
|
|
3
|
-
import { Buffer } from "node:buffer";
|
|
4
|
-
import { Socket as NetSocket } from "@gjsify/net/socket";
|
|
5
|
-
import {
|
|
6
|
-
Server as BridgeServer
|
|
7
|
-
} from "@gjsify/http-soup-bridge";
|
|
8
1
|
import { ServerRequestSocket } from "./server-request-socket.js";
|
|
9
|
-
import { createNodeError, deferEmit, ensureMainLoop } from "@gjsify/utils";
|
|
10
2
|
import { STATUS_CODES } from "./constants.js";
|
|
11
3
|
import { IncomingMessage } from "./incoming-message.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
4
|
+
import { Buffer } from "node:buffer";
|
|
5
|
+
import { createNodeError, deferEmit, ensureMainLoop } from "@gjsify/utils";
|
|
6
|
+
import { EventEmitter } from "node:events";
|
|
7
|
+
import { Writable } from "node:stream";
|
|
8
|
+
import { Socket } from "@gjsify/net/socket";
|
|
9
|
+
import { Server as Server$1 } from "@gjsify/http-soup-bridge";
|
|
10
|
+
|
|
11
|
+
//#region src/server.ts
|
|
12
|
+
/**
|
|
13
|
+
* OutgoingMessage — Base class for ServerResponse and ClientRequest.
|
|
14
|
+
* Reference: Node.js lib/_http_outgoing.js
|
|
15
|
+
*/
|
|
16
|
+
var OutgoingMessage = class extends Writable {
|
|
17
|
+
headersSent = false;
|
|
18
|
+
sendDate = true;
|
|
19
|
+
finished = false;
|
|
20
|
+
socket = null;
|
|
21
|
+
_headers = new Map();
|
|
22
|
+
/** Set a header. */
|
|
23
|
+
setHeader(name, value) {
|
|
24
|
+
this._headers.set(name.toLowerCase(), typeof value === "number" ? String(value) : value);
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
/** Get a header. */
|
|
28
|
+
getHeader(name) {
|
|
29
|
+
return this._headers.get(name.toLowerCase());
|
|
30
|
+
}
|
|
31
|
+
/** Remove a header. */
|
|
32
|
+
removeHeader(name) {
|
|
33
|
+
this._headers.delete(name.toLowerCase());
|
|
34
|
+
}
|
|
35
|
+
/** Check if a header has been set. */
|
|
36
|
+
hasHeader(name) {
|
|
37
|
+
return this._headers.has(name.toLowerCase());
|
|
38
|
+
}
|
|
39
|
+
/** Get all header names. */
|
|
40
|
+
getHeaderNames() {
|
|
41
|
+
return Array.from(this._headers.keys());
|
|
42
|
+
}
|
|
43
|
+
/** Get all headers as an object. */
|
|
44
|
+
getHeaders() {
|
|
45
|
+
const result = {};
|
|
46
|
+
for (const [key, value] of this._headers) {
|
|
47
|
+
result[key] = value;
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
/** Append a header value instead of replacing. */
|
|
52
|
+
appendHeader(name, value) {
|
|
53
|
+
const lower = name.toLowerCase();
|
|
54
|
+
const existing = this._headers.get(lower);
|
|
55
|
+
if (existing === undefined) {
|
|
56
|
+
this._headers.set(lower, value);
|
|
57
|
+
} else if (Array.isArray(existing)) {
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
existing.push(...value);
|
|
60
|
+
} else {
|
|
61
|
+
existing.push(value);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
this._headers.set(lower, [existing, ...value]);
|
|
66
|
+
} else {
|
|
67
|
+
this._headers.set(lower, [existing, value]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/** Flush headers (no-op in base class). */
|
|
73
|
+
flushHeaders() {
|
|
74
|
+
this.headersSent = true;
|
|
75
|
+
}
|
|
76
|
+
_write(_chunk, _encoding, callback) {
|
|
77
|
+
callback();
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* ServerResponse — Writable stream representing an HTTP response.
|
|
82
|
+
*
|
|
83
|
+
* Holds a `BridgeResponse` from `@gjsify/http-soup-bridge`. All header /
|
|
84
|
+
* status / body operations delegate to the bridge, which handles the
|
|
85
|
+
* underlying Soup.ServerMessage in C-space (no JS-visible boxed types).
|
|
86
|
+
*/
|
|
87
|
+
var ServerResponse = class extends OutgoingMessage {
|
|
88
|
+
statusCode = 200;
|
|
89
|
+
statusMessage = "";
|
|
90
|
+
_streaming = false;
|
|
91
|
+
_bridge;
|
|
92
|
+
_timeoutTimer = null;
|
|
93
|
+
constructor(bridge) {
|
|
94
|
+
super();
|
|
95
|
+
this._bridge = bridge;
|
|
96
|
+
bridge.connect("close", () => {
|
|
97
|
+
this.emit("close");
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** Set a timeout for the response. Emits 'timeout' if response not sent within msecs. */
|
|
101
|
+
setTimeout(msecs, callback) {
|
|
102
|
+
if (this._timeoutTimer) {
|
|
103
|
+
clearTimeout(this._timeoutTimer);
|
|
104
|
+
this._timeoutTimer = null;
|
|
105
|
+
}
|
|
106
|
+
if (callback) this.once("timeout", callback);
|
|
107
|
+
if (msecs > 0) {
|
|
108
|
+
this._timeoutTimer = setTimeout(() => {
|
|
109
|
+
this._timeoutTimer = null;
|
|
110
|
+
this.emit("timeout");
|
|
111
|
+
}, msecs);
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/** Write the status line and headers. */
|
|
116
|
+
writeHead(statusCode, statusMessage, headers) {
|
|
117
|
+
this.statusCode = statusCode;
|
|
118
|
+
if (typeof statusMessage === "object") {
|
|
119
|
+
headers = statusMessage;
|
|
120
|
+
statusMessage = undefined;
|
|
121
|
+
}
|
|
122
|
+
this.statusMessage = statusMessage || STATUS_CODES[statusCode] || "";
|
|
123
|
+
if (headers) {
|
|
124
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
125
|
+
this.setHeader(key, value);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
/** Send a 100 Continue response. */
|
|
131
|
+
writeContinue(callback) {
|
|
132
|
+
if (callback) Promise.resolve().then(callback);
|
|
133
|
+
}
|
|
134
|
+
/** Send a 102 Processing response (WebDAV). */
|
|
135
|
+
writeProcessing(callback) {
|
|
136
|
+
if (callback) Promise.resolve().then(callback);
|
|
137
|
+
}
|
|
138
|
+
/** Flush headers (send them immediately). */
|
|
139
|
+
flushHeaders() {
|
|
140
|
+
if (!this.headersSent) {
|
|
141
|
+
this.headersSent = true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Add trailing headers for chunked transfer encoding. */
|
|
145
|
+
addTrailers(headers) {
|
|
146
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
147
|
+
this._headers.set("trailer-" + key.toLowerCase(), value);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Push our header map to the bridge and call write_head() on first
|
|
152
|
+
* write. Idempotent.
|
|
153
|
+
*/
|
|
154
|
+
_startStreaming() {
|
|
155
|
+
if (this._streaming) return;
|
|
156
|
+
this._streaming = true;
|
|
157
|
+
this.headersSent = true;
|
|
158
|
+
if (this._timeoutTimer) {
|
|
159
|
+
clearTimeout(this._timeoutTimer);
|
|
160
|
+
this._timeoutTimer = null;
|
|
161
|
+
}
|
|
162
|
+
for (const [key, value] of this._headers) {
|
|
163
|
+
if (Array.isArray(value)) {
|
|
164
|
+
for (const v of value) this._bridge.append_header(key, v);
|
|
165
|
+
} else {
|
|
166
|
+
this._bridge.set_header(key, value);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
this._bridge.write_head(this.statusCode, this.statusMessage || null);
|
|
170
|
+
}
|
|
171
|
+
/** Writable stream _write — sends headers on first call, then appends each chunk. */
|
|
172
|
+
_write(chunk, encoding, callback) {
|
|
173
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
|
|
174
|
+
this._startStreaming();
|
|
175
|
+
this._bridge.write_chunk(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
|
|
176
|
+
callback();
|
|
177
|
+
}
|
|
178
|
+
/** Called by Writable.end() — finishes the bridge response. */
|
|
179
|
+
_final(callback) {
|
|
180
|
+
if (!this._streaming) {
|
|
181
|
+
for (const [key, value] of this._headers) {
|
|
182
|
+
if (Array.isArray(value)) {
|
|
183
|
+
for (const v of value) this._bridge.append_header(key, v);
|
|
184
|
+
} else {
|
|
185
|
+
this._bridge.set_header(key, value);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
this._bridge.write_head(this.statusCode, this.statusMessage || null);
|
|
189
|
+
}
|
|
190
|
+
this._bridge.end();
|
|
191
|
+
this.finished = true;
|
|
192
|
+
callback();
|
|
193
|
+
}
|
|
194
|
+
/** Write status + headers + body in one call (convenience). */
|
|
195
|
+
end(chunk, encoding, callback) {
|
|
196
|
+
if (typeof chunk === "function") {
|
|
197
|
+
callback = chunk;
|
|
198
|
+
chunk = undefined;
|
|
199
|
+
} else if (typeof encoding === "function") {
|
|
200
|
+
callback = encoding;
|
|
201
|
+
encoding = undefined;
|
|
202
|
+
}
|
|
203
|
+
if (chunk != null) {
|
|
204
|
+
this.write(chunk, encoding);
|
|
205
|
+
}
|
|
206
|
+
super.end(callback);
|
|
207
|
+
return this;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const _activeServers = new Set();
|
|
211
|
+
/**
|
|
212
|
+
* HTTP Server — wraps a `@gjsify/http-soup-bridge` Server.
|
|
213
|
+
*
|
|
214
|
+
* Public API matches Node.js `http.Server`. Internal differences from the
|
|
215
|
+
* pre-bridge version are invisible to consumers (Hono / Express / MCP /
|
|
216
|
+
* engine.io / `@gjsify/ws`) — they continue to use `.on('request', …)`,
|
|
217
|
+
* `.on('upgrade', …)`, `.listen()`, `.close()`, `.address()`, etc.
|
|
218
|
+
*/
|
|
219
|
+
var Server = class extends EventEmitter {
|
|
220
|
+
listening = false;
|
|
221
|
+
maxHeadersCount = 2e3;
|
|
222
|
+
timeout = 0;
|
|
223
|
+
keepAliveTimeout = 5e3;
|
|
224
|
+
headersTimeout = 6e4;
|
|
225
|
+
requestTimeout = 3e5;
|
|
226
|
+
_bridge = null;
|
|
227
|
+
_address = null;
|
|
228
|
+
/** Exposes the underlying Soup.Server so consumers (e.g. WebSocketServer
|
|
229
|
+
* in `@gjsify/ws`) can register additional handlers (websocket, path-
|
|
230
|
+
* specific) on the same server instance without port-sharing conflicts. */
|
|
231
|
+
get soupServer() {
|
|
232
|
+
return this._bridge?.soup_server ?? null;
|
|
233
|
+
}
|
|
234
|
+
constructor(optionsOrListener, requestListener) {
|
|
235
|
+
super();
|
|
236
|
+
const listener = typeof optionsOrListener === "function" ? optionsOrListener : requestListener;
|
|
237
|
+
if (listener) this.on("request", listener);
|
|
238
|
+
}
|
|
239
|
+
listen(...args) {
|
|
240
|
+
let port = 0;
|
|
241
|
+
let hostname = "0.0.0.0";
|
|
242
|
+
let callback;
|
|
243
|
+
for (const arg of args) {
|
|
244
|
+
if (typeof arg === "number") port = arg;
|
|
245
|
+
else if (typeof arg === "string") hostname = arg;
|
|
246
|
+
else if (typeof arg === "function") callback = arg;
|
|
247
|
+
}
|
|
248
|
+
if (callback) this.once("listening", callback);
|
|
249
|
+
try {
|
|
250
|
+
this._bridge = new Server$1();
|
|
251
|
+
this._bridge.connect("request-received", (_self, req, res) => {
|
|
252
|
+
this._handleRequest(req, res);
|
|
253
|
+
});
|
|
254
|
+
this._bridge.connect("upgrade", (_self, req, iostream, _head) => {
|
|
255
|
+
this._handleUpgrade(req, iostream);
|
|
256
|
+
});
|
|
257
|
+
this._bridge.connect("error-occurred", (_self, msg) => {
|
|
258
|
+
this.emit("error", new Error(msg));
|
|
259
|
+
});
|
|
260
|
+
this._bridge.listen(port, hostname);
|
|
261
|
+
ensureMainLoop();
|
|
262
|
+
this.listening = true;
|
|
263
|
+
this._address = {
|
|
264
|
+
port: this._bridge.port,
|
|
265
|
+
family: "IPv4",
|
|
266
|
+
address: this._bridge.address || hostname
|
|
267
|
+
};
|
|
268
|
+
_activeServers.add(this);
|
|
269
|
+
deferEmit(this, "listening");
|
|
270
|
+
} catch (err) {
|
|
271
|
+
const nodeErr = createNodeError(err, "listen", {
|
|
272
|
+
address: hostname,
|
|
273
|
+
port
|
|
274
|
+
});
|
|
275
|
+
deferEmit(this, "error", nodeErr);
|
|
276
|
+
}
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
_handleRequest(bridgeReq, bridgeRes) {
|
|
280
|
+
const req = new IncomingMessage();
|
|
281
|
+
const res = new ServerResponse(bridgeRes);
|
|
282
|
+
req.method = bridgeReq.method;
|
|
283
|
+
req.url = bridgeReq.url;
|
|
284
|
+
req.httpVersion = "1.1";
|
|
285
|
+
const pairs = bridgeReq.header_pairs ?? [];
|
|
286
|
+
for (let i = 0; i + 1 < pairs.length; i += 2) {
|
|
287
|
+
const name = pairs[i];
|
|
288
|
+
const value = pairs[i + 1];
|
|
289
|
+
const lower = name.toLowerCase();
|
|
290
|
+
req.rawHeaders.push(name, value);
|
|
291
|
+
if (lower in req.headers) {
|
|
292
|
+
const existing = req.headers[lower];
|
|
293
|
+
if (Array.isArray(existing)) existing.push(value);
|
|
294
|
+
else req.headers[lower] = [existing, value];
|
|
295
|
+
} else {
|
|
296
|
+
req.headers[lower] = value;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
req.socket = new ServerRequestSocket(bridgeReq.remote_address ?? "127.0.0.1", bridgeReq.remote_port ?? 0, this._address?.address ?? "127.0.0.1", this._address?.port ?? 0, bridgeRes);
|
|
300
|
+
const body = bridgeReq.get_body();
|
|
301
|
+
if (body.length > 0) req._pushBody(body);
|
|
302
|
+
else req._pushBody(null);
|
|
303
|
+
bridgeReq.connect("aborted_signal", () => {
|
|
304
|
+
if (!req.aborted) {
|
|
305
|
+
req.aborted = true;
|
|
306
|
+
req.emit("aborted");
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
bridgeReq.connect("close", () => {
|
|
310
|
+
req.emit("close");
|
|
311
|
+
});
|
|
312
|
+
try {
|
|
313
|
+
const result = this.emit("request", req, res);
|
|
314
|
+
if (result instanceof Promise || result !== null && typeof result === "object" && typeof result.then === "function") {
|
|
315
|
+
result.catch((err) => {
|
|
316
|
+
console.error("[HTTP] Unhandled error in async request handler:", err);
|
|
317
|
+
if (!res.headersSent) {
|
|
318
|
+
try {
|
|
319
|
+
res.writeHead(500);
|
|
320
|
+
res.end("Internal Server Error");
|
|
321
|
+
} catch {}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error("[HTTP] Unhandled error in request handler:", err);
|
|
327
|
+
if (!res.headersSent) {
|
|
328
|
+
try {
|
|
329
|
+
res.writeHead(500);
|
|
330
|
+
res.end("Internal Server Error");
|
|
331
|
+
} catch {}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
_handleUpgrade(bridgeReq, iostream) {
|
|
336
|
+
const req = new IncomingMessage();
|
|
337
|
+
req.method = bridgeReq.method;
|
|
338
|
+
req.url = bridgeReq.url;
|
|
339
|
+
req.httpVersion = "1.1";
|
|
340
|
+
const pairs = bridgeReq.header_pairs ?? [];
|
|
341
|
+
for (let i = 0; i + 1 < pairs.length; i += 2) {
|
|
342
|
+
const name = pairs[i];
|
|
343
|
+
const value = pairs[i + 1];
|
|
344
|
+
const lower = name.toLowerCase();
|
|
345
|
+
req.rawHeaders.push(name, value);
|
|
346
|
+
req.headers[lower] = value;
|
|
347
|
+
}
|
|
348
|
+
if (this.listenerCount("upgrade") > 0) {
|
|
349
|
+
const socket = new Socket();
|
|
350
|
+
socket._attachOutputOnly(iostream);
|
|
351
|
+
this.emit("upgrade", req, socket, Buffer.alloc(0));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
address() {
|
|
355
|
+
return this._address;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Register a WebSocket handler on this server (GJS only).
|
|
359
|
+
* Delegates to the underlying `Soup.Server.add_websocket_handler()`.
|
|
360
|
+
* @param path URL path to handle WebSocket upgrades (e.g., '/ws')
|
|
361
|
+
* @param callback Called for each new WebSocket connection with the Soup.WebsocketConnection
|
|
362
|
+
*/
|
|
363
|
+
addWebSocketHandler(path, callback) {
|
|
364
|
+
if (!this._bridge) {
|
|
365
|
+
throw new Error("Server must be listening before adding WebSocket handlers. Call listen() first.");
|
|
366
|
+
}
|
|
367
|
+
const soupServer = this._bridge.soup_server;
|
|
368
|
+
soupServer.add_websocket_handler(path, null, null, (_srv, _msg, _p, connection) => {
|
|
369
|
+
callback(connection);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
close(callback) {
|
|
373
|
+
if (callback) this.once("close", callback);
|
|
374
|
+
if (this._bridge) {
|
|
375
|
+
this._bridge.close();
|
|
376
|
+
this._bridge = null;
|
|
377
|
+
}
|
|
378
|
+
this.listening = false;
|
|
379
|
+
_activeServers.delete(this);
|
|
380
|
+
deferEmit(this, "close");
|
|
381
|
+
return this;
|
|
382
|
+
}
|
|
383
|
+
setTimeout(msecs, callback) {
|
|
384
|
+
this.timeout = msecs;
|
|
385
|
+
if (callback) this.on("timeout", callback);
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
380
388
|
};
|
|
389
|
+
|
|
390
|
+
//#endregion
|
|
391
|
+
export { OutgoingMessage, Server, ServerResponse };
|