@wooksjs/event-http 0.6.6 → 0.7.0
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/README.md +11 -45
- package/dist/index.cjs +1143 -963
- package/dist/index.d.ts +359 -303
- package/dist/index.mjs +1101 -917
- package/package.json +7 -7
- package/skills/wooksjs-event-http/SKILL.md +28 -21
- package/skills/wooksjs-event-http/core.md +83 -228
- package/skills/wooksjs-event-http/request.md +130 -146
- package/skills/wooksjs-event-http/response.md +166 -235
- package/skills/wooksjs-event-http/testing.md +150 -0
- package/skills/wooksjs-event-http/addons.md +0 -307
- package/skills/wooksjs-event-http/error-handling.md +0 -253
- package/skills/wooksjs-event-http/event-core.md +0 -562
- package/skills/wooksjs-event-http/routing.md +0 -412
package/dist/index.cjs
CHANGED
|
@@ -27,28 +27,18 @@ const node_stream = __toESM(require("node:stream"));
|
|
|
27
27
|
const node_util = __toESM(require("node:util"));
|
|
28
28
|
const node_zlib = __toESM(require("node:zlib"));
|
|
29
29
|
const url = __toESM(require("url"));
|
|
30
|
+
const stream = __toESM(require("stream"));
|
|
30
31
|
const http = __toESM(require("http"));
|
|
31
32
|
const wooks = __toESM(require("wooks"));
|
|
32
|
-
const
|
|
33
|
+
const net = __toESM(require("net"));
|
|
33
34
|
|
|
34
|
-
//#region packages/event-http/src/
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
options
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Wrapper on useEventContext with HTTP event types
|
|
47
|
-
* @returns set of hooks { getCtx, restoreCtx, clearCtx, hookStore, getStore, setStore }
|
|
48
|
-
*/
|
|
49
|
-
function useHttpContext() {
|
|
50
|
-
return (0, __wooksjs_event_core.useAsyncEventContext)("HTTP");
|
|
51
|
-
}
|
|
35
|
+
//#region packages/event-http/src/http-kind.ts
|
|
36
|
+
/** Event kind definition for HTTP requests. Provides typed context slots for `req`, `response`, and `requestLimits`. */
|
|
37
|
+
const httpKind = (0, __wooksjs_event_core.defineEventKind)("http", {
|
|
38
|
+
req: (0, __wooksjs_event_core.slot)(),
|
|
39
|
+
response: (0, __wooksjs_event_core.slot)(),
|
|
40
|
+
requestLimits: (0, __wooksjs_event_core.slot)()
|
|
41
|
+
});
|
|
52
42
|
|
|
53
43
|
//#endregion
|
|
54
44
|
//#region packages/event-http/src/utils/helpers.ts
|
|
@@ -68,53 +58,114 @@ function safeDecodeURIComponent(uri) {
|
|
|
68
58
|
}
|
|
69
59
|
|
|
70
60
|
//#endregion
|
|
71
|
-
//#region packages/event-http/src/
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
61
|
+
//#region packages/event-http/src/composables/cookies.ts
|
|
62
|
+
const cookieRegExpCache = /* @__PURE__ */ new Map();
|
|
63
|
+
function getCookieRegExp(name) {
|
|
64
|
+
let re = cookieRegExpCache.get(name);
|
|
65
|
+
if (!re) {
|
|
66
|
+
re = new RegExp(`(?:^|; )${escapeRegex(name)}=(.*?)(?:;?$|; )`, "i");
|
|
67
|
+
cookieRegExpCache.set(name, re);
|
|
68
|
+
}
|
|
69
|
+
return re;
|
|
79
70
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
71
|
+
const parseCookieValue = (0, __wooksjs_event_core.cachedBy)((name, ctx) => {
|
|
72
|
+
const cookie = ctx.get(httpKind.keys.req).headers.cookie;
|
|
73
|
+
if (cookie) {
|
|
74
|
+
const result = getCookieRegExp(name).exec(cookie);
|
|
75
|
+
return result?.[1] ? safeDecodeURIComponent(result[1]) : null;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Provides access to parsed request cookies.
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* const { getCookie, rawCookies } = useCookies()
|
|
84
|
+
* const sessionId = getCookie('session_id')
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
const useCookies = (0, __wooksjs_event_core.defineWook)((ctx) => ({
|
|
88
|
+
rawCookies: ctx.get(httpKind.keys.req).headers.cookie,
|
|
89
|
+
getCookie: (name) => parseCookieValue(name, ctx)
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region packages/event-http/src/composables/header-accept.ts
|
|
94
|
+
const ACCEPT_TYPE_MAP = {
|
|
95
|
+
json: "application/json",
|
|
96
|
+
html: "text/html",
|
|
97
|
+
xml: "application/xml",
|
|
98
|
+
text: "text/plain"
|
|
89
99
|
};
|
|
100
|
+
const acceptsMime = (0, __wooksjs_event_core.cachedBy)((type, ctx) => {
|
|
101
|
+
const accept = ctx.get(httpKind.keys.req).headers.accept;
|
|
102
|
+
const mime = ACCEPT_TYPE_MAP[type] || type;
|
|
103
|
+
return !!(accept && (accept === "*/*" || accept.includes(mime)));
|
|
104
|
+
});
|
|
105
|
+
/** Provides helpers to check the request's Accept header for supported MIME types. */
|
|
106
|
+
const useAccept = (0, __wooksjs_event_core.defineWook)((ctx) => {
|
|
107
|
+
const accept = ctx.get(httpKind.keys.req).headers.accept;
|
|
108
|
+
return {
|
|
109
|
+
accept,
|
|
110
|
+
accepts: (type) => acceptsMime(type, ctx)
|
|
111
|
+
};
|
|
112
|
+
});
|
|
90
113
|
|
|
91
114
|
//#endregion
|
|
92
|
-
//#region packages/event-http/src/
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (!COOKIE_NAME_RE.test(key)) throw new TypeError(`Invalid cookie name "${key}"`);
|
|
99
|
-
let attrs = "";
|
|
100
|
-
for (const [a, v] of Object.entries(data.attrs)) {
|
|
101
|
-
const func = cookieAttrFunc[a];
|
|
102
|
-
if (typeof func === "function") {
|
|
103
|
-
const val = func(v);
|
|
104
|
-
attrs += val ? `; ${val}` : "";
|
|
105
|
-
} else throw new TypeError(`Unknown Set-Cookie attribute ${a}`);
|
|
115
|
+
//#region packages/event-http/src/composables/header-authorization.ts
|
|
116
|
+
const authTypeSlot = (0, __wooksjs_event_core.cached)((ctx) => {
|
|
117
|
+
const authorization = ctx.get(httpKind.keys.req).headers.authorization;
|
|
118
|
+
if (authorization) {
|
|
119
|
+
const space = authorization.indexOf(" ");
|
|
120
|
+
return authorization.slice(0, space);
|
|
106
121
|
}
|
|
107
|
-
return
|
|
108
|
-
}
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
return null;
|
|
123
|
+
});
|
|
124
|
+
const authCredentialsSlot = (0, __wooksjs_event_core.cached)((ctx) => {
|
|
125
|
+
const authorization = ctx.get(httpKind.keys.req).headers.authorization;
|
|
126
|
+
if (authorization) {
|
|
127
|
+
const space = authorization.indexOf(" ");
|
|
128
|
+
return authorization.slice(space + 1);
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
});
|
|
132
|
+
const basicCredentialsSlot = (0, __wooksjs_event_core.cached)((ctx) => {
|
|
133
|
+
const authorization = ctx.get(httpKind.keys.req).headers.authorization;
|
|
134
|
+
if (authorization) {
|
|
135
|
+
const type = ctx.get(authTypeSlot);
|
|
136
|
+
if (type?.toLocaleLowerCase() === "basic") {
|
|
137
|
+
const creds = buffer.Buffer.from(ctx.get(authCredentialsSlot) || "", "base64").toString("ascii");
|
|
138
|
+
const [username, password] = creds.split(":");
|
|
139
|
+
return {
|
|
140
|
+
username,
|
|
141
|
+
password
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
});
|
|
147
|
+
const authIsSlot = (0, __wooksjs_event_core.cachedBy)((type, ctx) => {
|
|
148
|
+
const authType = ctx.get(authTypeSlot);
|
|
149
|
+
return authType?.toLowerCase() === type.toLowerCase();
|
|
150
|
+
});
|
|
151
|
+
/**
|
|
152
|
+
* Provides parsed access to the Authorization header (type, credentials, Basic decoding).
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* const { authIs, authRawCredentials, basicCredentials } = useAuthorization()
|
|
156
|
+
* if (authIs('bearer')) { const token = authRawCredentials() }
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
const useAuthorization = (0, __wooksjs_event_core.defineWook)((ctx) => {
|
|
160
|
+
const authorization = ctx.get(httpKind.keys.req).headers.authorization;
|
|
161
|
+
return {
|
|
162
|
+
authorization,
|
|
163
|
+
authType: () => ctx.get(authTypeSlot),
|
|
164
|
+
authRawCredentials: () => ctx.get(authCredentialsSlot),
|
|
165
|
+
authIs: (type) => authIsSlot(type, ctx),
|
|
166
|
+
basicCredentials: () => ctx.get(basicCredentialsSlot)
|
|
167
|
+
};
|
|
168
|
+
});
|
|
118
169
|
|
|
119
170
|
//#endregion
|
|
120
171
|
//#region packages/event-http/src/compressor/body-compressor.ts
|
|
@@ -192,26 +243,9 @@ compressors.deflate.uncompress = async (b) => (await zlib()).inflate(b);
|
|
|
192
243
|
compressors.br.compress = async (b) => (await zlib()).brotliCompress(b);
|
|
193
244
|
compressors.br.uncompress = async (b) => (await zlib()).brotliDecompress(b);
|
|
194
245
|
|
|
195
|
-
//#endregion
|
|
196
|
-
//#region packages/event-http/src/response/renderer.ts
|
|
197
|
-
var BaseHttpResponseRenderer = class {
|
|
198
|
-
render(response) {
|
|
199
|
-
if (typeof response.body === "string" || typeof response.body === "boolean" || typeof response.body === "number") {
|
|
200
|
-
if (!response.getContentType()) response.setContentType("text/plain");
|
|
201
|
-
return response.body.toString();
|
|
202
|
-
}
|
|
203
|
-
if (response.body === void 0) return "";
|
|
204
|
-
if (response.body instanceof Uint8Array) return response.body;
|
|
205
|
-
if (typeof response.body === "object") {
|
|
206
|
-
if (!response.getContentType()) response.setContentType("application/json");
|
|
207
|
-
return JSON.stringify(response.body);
|
|
208
|
-
}
|
|
209
|
-
throw new Error(`Unsupported body format "${typeof response.body}"`);
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
|
|
213
246
|
//#endregion
|
|
214
247
|
//#region packages/event-http/src/utils/status-codes.ts
|
|
248
|
+
/** Maps numeric HTTP status codes to their human-readable descriptions. */
|
|
215
249
|
const httpStatusCodes = {
|
|
216
250
|
100: "Continue",
|
|
217
251
|
101: "Switching protocols",
|
|
@@ -277,6 +311,7 @@ const httpStatusCodes = {
|
|
|
277
311
|
510: "Not Extended",
|
|
278
312
|
511: "Network Authentication Required"
|
|
279
313
|
};
|
|
314
|
+
/** Enum of all standard HTTP status codes (100–511). */
|
|
280
315
|
let EHttpStatusCode = /* @__PURE__ */ function(EHttpStatusCode$1) {
|
|
281
316
|
EHttpStatusCode$1[EHttpStatusCode$1["Continue"] = 100] = "Continue";
|
|
282
317
|
EHttpStatusCode$1[EHttpStatusCode$1["SwitchingProtocols"] = 101] = "SwitchingProtocols";
|
|
@@ -345,46 +380,708 @@ let EHttpStatusCode = /* @__PURE__ */ function(EHttpStatusCode$1) {
|
|
|
345
380
|
}({});
|
|
346
381
|
|
|
347
382
|
//#endregion
|
|
348
|
-
//#region packages/event-http/src/errors/
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
383
|
+
//#region packages/event-http/src/errors/http-error.ts
|
|
384
|
+
/** Represents an HTTP error with a status code and optional structured body. */
|
|
385
|
+
var HttpError = class extends Error {
|
|
386
|
+
name = "HttpError";
|
|
387
|
+
constructor(code = 500, _body = "") {
|
|
388
|
+
const prev = Error.stackTraceLimit;
|
|
389
|
+
Error.stackTraceLimit = 0;
|
|
390
|
+
super(typeof _body === "string" ? _body : _body.message);
|
|
391
|
+
this.code = code;
|
|
392
|
+
this._body = _body;
|
|
393
|
+
Error.stackTraceLimit = prev;
|
|
394
|
+
}
|
|
395
|
+
get body() {
|
|
396
|
+
return typeof this._body === "string" ? {
|
|
397
|
+
statusCode: this.code,
|
|
398
|
+
message: this.message,
|
|
399
|
+
error: httpStatusCodes[this.code]
|
|
400
|
+
} : {
|
|
401
|
+
...this._body,
|
|
402
|
+
statusCode: this.code,
|
|
403
|
+
message: this.message,
|
|
404
|
+
error: httpStatusCodes[this.code]
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
};
|
|
355
408
|
|
|
356
|
-
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region packages/event-http/src/composables/request.ts
|
|
411
|
+
const xForwardedFor = "x-forwarded-for";
|
|
412
|
+
/** Default safety limits for request body reading (size, ratio, timeout). */
|
|
413
|
+
const DEFAULT_LIMITS = {
|
|
414
|
+
maxCompressed: 1 * 1024 * 1024,
|
|
415
|
+
maxInflated: 10 * 1024 * 1024,
|
|
416
|
+
maxRatio: 100,
|
|
417
|
+
readTimeoutMs: 1e4
|
|
418
|
+
};
|
|
419
|
+
const contentEncodingsSlot = (0, __wooksjs_event_core.cached)((ctx) => {
|
|
420
|
+
const req = ctx.get(httpKind.keys.req);
|
|
421
|
+
const contentEncoding = req.headers["content-encoding"];
|
|
422
|
+
return (contentEncoding || "").split(",").map((p) => p.trim()).filter((p) => !!p);
|
|
423
|
+
});
|
|
424
|
+
const isCompressedSlot = (0, __wooksjs_event_core.cached)((ctx) => {
|
|
425
|
+
const parts = ctx.get(contentEncodingsSlot);
|
|
426
|
+
for (const p of parts) if ([
|
|
427
|
+
"deflate",
|
|
428
|
+
"gzip",
|
|
429
|
+
"br"
|
|
430
|
+
].includes(p)) return true;
|
|
431
|
+
return false;
|
|
432
|
+
});
|
|
433
|
+
/** @internal Exported for test pre-seeding via `ctx.set(rawBodySlot, ...)`. */
|
|
434
|
+
const rawBodySlot = (0, __wooksjs_event_core.cached)(async (ctx) => {
|
|
435
|
+
const req = ctx.get(httpKind.keys.req);
|
|
436
|
+
const encs = ctx.get(contentEncodingsSlot);
|
|
437
|
+
const isZip = ctx.get(isCompressedSlot);
|
|
438
|
+
const streamable = isZip && encodingSupportsStream(encs);
|
|
439
|
+
const limits = ctx.get(httpKind.keys.requestLimits);
|
|
440
|
+
const maxCompressed = limits?.maxCompressed ?? DEFAULT_LIMITS.maxCompressed;
|
|
441
|
+
const maxInflated = limits?.maxInflated ?? DEFAULT_LIMITS.maxInflated;
|
|
442
|
+
const maxRatio = limits?.maxRatio ?? DEFAULT_LIMITS.maxRatio;
|
|
443
|
+
const timeoutMs = limits?.readTimeoutMs ?? DEFAULT_LIMITS.readTimeoutMs;
|
|
444
|
+
const cl = Number(req.headers["content-length"] ?? 0);
|
|
445
|
+
const upfrontLimit = isZip ? maxCompressed : maxInflated;
|
|
446
|
+
if (cl && cl > upfrontLimit) throw new HttpError(413, "Payload Too Large");
|
|
447
|
+
for (const enc of encs) if (!compressors[enc]) throw new HttpError(415, `Unsupported Content-Encoding "${enc}"`);
|
|
448
|
+
let timer = null;
|
|
449
|
+
function resetTimer() {
|
|
450
|
+
if (timeoutMs === 0) return;
|
|
451
|
+
clearTimer();
|
|
452
|
+
timer = setTimeout(() => {
|
|
453
|
+
clearTimer();
|
|
454
|
+
req.destroy();
|
|
455
|
+
}, timeoutMs);
|
|
456
|
+
}
|
|
457
|
+
function clearTimer() {
|
|
458
|
+
if (timer) {
|
|
459
|
+
clearTimeout(timer);
|
|
460
|
+
timer = null;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
let rawBytes = 0;
|
|
464
|
+
async function* limitedCompressed() {
|
|
465
|
+
resetTimer();
|
|
466
|
+
try {
|
|
467
|
+
for await (const chunk of req) {
|
|
468
|
+
rawBytes += chunk.length;
|
|
469
|
+
if (rawBytes > upfrontLimit) {
|
|
470
|
+
req.destroy();
|
|
471
|
+
throw new HttpError(413, "Payload Too Large");
|
|
472
|
+
}
|
|
473
|
+
resetTimer();
|
|
474
|
+
yield chunk;
|
|
475
|
+
}
|
|
476
|
+
} finally {
|
|
477
|
+
clearTimer();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
let stream$1 = limitedCompressed();
|
|
481
|
+
if (streamable) stream$1 = await uncompressBodyStream(encs, stream$1);
|
|
482
|
+
const chunks = [];
|
|
483
|
+
let inflatedBytes = 0;
|
|
484
|
+
try {
|
|
485
|
+
for await (const chunk of stream$1) {
|
|
486
|
+
inflatedBytes += chunk.length;
|
|
487
|
+
if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
|
|
488
|
+
chunks.push(chunk);
|
|
489
|
+
}
|
|
490
|
+
} catch (error) {
|
|
491
|
+
if (error instanceof HttpError) throw error;
|
|
492
|
+
throw new HttpError(408, "Request body timeout");
|
|
493
|
+
}
|
|
494
|
+
let body = buffer.Buffer.concat(chunks);
|
|
495
|
+
if (!streamable && isZip) {
|
|
496
|
+
body = await uncompressBody(encs, body);
|
|
497
|
+
inflatedBytes = body.byteLength;
|
|
498
|
+
if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
|
|
499
|
+
}
|
|
500
|
+
if (isZip && rawBytes > 0 && inflatedBytes / rawBytes > maxRatio) throw new HttpError(413, "Compression ratio too high");
|
|
501
|
+
return body;
|
|
502
|
+
});
|
|
503
|
+
const forwardedIpSlot = (0, __wooksjs_event_core.cached)((ctx) => {
|
|
504
|
+
const req = ctx.get(httpKind.keys.req);
|
|
505
|
+
if (typeof req.headers[xForwardedFor] === "string" && req.headers[xForwardedFor]) return req.headers[xForwardedFor].split(",").shift()?.trim();
|
|
506
|
+
return "";
|
|
507
|
+
});
|
|
508
|
+
const remoteIpSlot = (0, __wooksjs_event_core.cached)((ctx) => {
|
|
509
|
+
const req = ctx.get(httpKind.keys.req);
|
|
510
|
+
return req.socket.remoteAddress || req.connection.remoteAddress || "";
|
|
511
|
+
});
|
|
512
|
+
const ipListSlot = (0, __wooksjs_event_core.cached)((ctx) => {
|
|
513
|
+
const req = ctx.get(httpKind.keys.req);
|
|
514
|
+
return {
|
|
515
|
+
remoteIp: req.socket.remoteAddress || req.connection.remoteAddress || "",
|
|
516
|
+
forwarded: (req.headers[xForwardedFor] || "").split(",").map((s) => s.trim())
|
|
517
|
+
};
|
|
518
|
+
});
|
|
519
|
+
/**
|
|
520
|
+
* Provides access to the incoming HTTP request (method, url, headers, body, IP).
|
|
521
|
+
* @example
|
|
522
|
+
* ```ts
|
|
523
|
+
* const { method, url, rawBody, getIp } = useRequest()
|
|
524
|
+
* const body = await rawBody()
|
|
525
|
+
* ```
|
|
526
|
+
*/
|
|
527
|
+
const useRequest = (0, __wooksjs_event_core.defineWook)((ctx) => {
|
|
528
|
+
const req = ctx.get(httpKind.keys.req);
|
|
529
|
+
const limits = () => ctx.get(httpKind.keys.requestLimits);
|
|
530
|
+
const setLimit = (limitKey, value) => {
|
|
531
|
+
let obj = limits();
|
|
532
|
+
if (!obj?.perRequest) {
|
|
533
|
+
obj = {
|
|
534
|
+
...obj,
|
|
535
|
+
perRequest: true
|
|
536
|
+
};
|
|
537
|
+
ctx.set(httpKind.keys.requestLimits, obj);
|
|
538
|
+
}
|
|
539
|
+
obj[limitKey] = value;
|
|
540
|
+
};
|
|
541
|
+
const getMaxCompressed = () => limits()?.maxCompressed ?? DEFAULT_LIMITS.maxCompressed;
|
|
542
|
+
const setMaxCompressed = (limit) => setLimit("maxCompressed", limit);
|
|
543
|
+
const getMaxInflated = () => limits()?.maxInflated ?? DEFAULT_LIMITS.maxInflated;
|
|
544
|
+
const setMaxInflated = (limit) => setLimit("maxInflated", limit);
|
|
545
|
+
const getMaxRatio = () => limits()?.maxRatio ?? DEFAULT_LIMITS.maxRatio;
|
|
546
|
+
const setMaxRatio = (limit) => setLimit("maxRatio", limit);
|
|
547
|
+
const getReadTimeoutMs = () => limits()?.readTimeoutMs ?? DEFAULT_LIMITS.readTimeoutMs;
|
|
548
|
+
const setReadTimeoutMs = (limit) => setLimit("readTimeoutMs", limit);
|
|
549
|
+
function getIp(options) {
|
|
550
|
+
if (options?.trustProxy) return ctx.get(forwardedIpSlot) || ctx.get(remoteIpSlot);
|
|
551
|
+
return ctx.get(remoteIpSlot);
|
|
552
|
+
}
|
|
553
|
+
return {
|
|
554
|
+
rawRequest: req,
|
|
555
|
+
url: req.url,
|
|
556
|
+
method: req.method,
|
|
557
|
+
headers: req.headers,
|
|
558
|
+
rawBody: () => ctx.get(rawBodySlot),
|
|
559
|
+
reqId: (0, __wooksjs_event_core.useEventId)(ctx).getId,
|
|
560
|
+
getIp,
|
|
561
|
+
getIpList: () => ctx.get(ipListSlot),
|
|
562
|
+
isCompressed: () => ctx.get(isCompressedSlot),
|
|
563
|
+
getMaxCompressed,
|
|
564
|
+
setMaxCompressed,
|
|
565
|
+
getReadTimeoutMs,
|
|
566
|
+
setReadTimeoutMs,
|
|
567
|
+
getMaxInflated,
|
|
568
|
+
setMaxInflated,
|
|
569
|
+
getMaxRatio,
|
|
570
|
+
setMaxRatio
|
|
571
|
+
};
|
|
572
|
+
});
|
|
357
573
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
574
|
+
//#endregion
|
|
575
|
+
//#region packages/event-http/src/composables/headers.ts
|
|
576
|
+
/**
|
|
577
|
+
* Returns the incoming request headers.
|
|
578
|
+
* @example
|
|
579
|
+
* ```ts
|
|
580
|
+
* const { host, authorization } = useHeaders()
|
|
581
|
+
* ```
|
|
582
|
+
*/
|
|
583
|
+
function useHeaders(ctx) {
|
|
584
|
+
return useRequest(ctx).headers;
|
|
361
585
|
}
|
|
362
586
|
|
|
363
587
|
//#endregion
|
|
364
|
-
//#region packages/event-http/src/
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
</use>
|
|
588
|
+
//#region packages/event-http/src/composables/response.ts
|
|
589
|
+
/**
|
|
590
|
+
* Returns the HttpResponse instance for the current request.
|
|
591
|
+
* All response operations (status, headers, cookies, cache control, sending)
|
|
592
|
+
* are methods on the returned object.
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* ```ts
|
|
596
|
+
* const response = useResponse()
|
|
597
|
+
* response.status = 200
|
|
598
|
+
* response.setHeader('x-custom', 'value')
|
|
599
|
+
* response.setCookie('session', 'abc', { httpOnly: true })
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
function useResponse(ctx) {
|
|
603
|
+
return (ctx ?? (0, __wooksjs_event_core.current)()).get(httpKind.keys.response);
|
|
604
|
+
}
|
|
382
605
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
606
|
+
//#endregion
|
|
607
|
+
//#region packages/event-http/src/utils/url-search-params.ts
|
|
608
|
+
const ILLEGAL_KEYS = new Set([
|
|
609
|
+
"__proto__",
|
|
610
|
+
"constructor",
|
|
611
|
+
"prototype"
|
|
612
|
+
]);
|
|
613
|
+
/**
|
|
614
|
+
* Extended `URLSearchParams` with safe JSON conversion.
|
|
615
|
+
*
|
|
616
|
+
* Rejects prototype-pollution keys (`__proto__`, `constructor`, `prototype`) and duplicate non-array keys.
|
|
617
|
+
* Array parameters are detected by a trailing `[]` in the key name (e.g. `tags[]=a&tags[]=b`).
|
|
618
|
+
*/
|
|
619
|
+
var WooksURLSearchParams = class extends url.URLSearchParams {
|
|
620
|
+
/** Converts query parameters to a plain object. Array params (keys ending with `[]`) become `string[]`. */
|
|
621
|
+
toJson() {
|
|
622
|
+
const json = Object.create(null);
|
|
623
|
+
for (const [key, value] of this.entries()) if (isArrayParam(key)) {
|
|
624
|
+
const a = json[key] = json[key] || [];
|
|
625
|
+
a.push(value);
|
|
626
|
+
} else {
|
|
627
|
+
if (ILLEGAL_KEYS.has(key)) throw new HttpError(400, `Illegal key name "${key}"`);
|
|
628
|
+
if (key in json) throw new HttpError(400, `Duplicate key "${key}"`);
|
|
629
|
+
json[key] = value;
|
|
630
|
+
}
|
|
631
|
+
return json;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
function isArrayParam(name) {
|
|
635
|
+
return name.endsWith("[]");
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
//#endregion
|
|
639
|
+
//#region packages/event-http/src/composables/search-params.ts
|
|
640
|
+
const rawSearchParamsSlot = (0, __wooksjs_event_core.cached)((ctx) => {
|
|
641
|
+
const url$1 = ctx.get(httpKind.keys.req).url || "";
|
|
642
|
+
const i = url$1.indexOf("?");
|
|
643
|
+
return i >= 0 ? url$1.slice(i) : "";
|
|
644
|
+
});
|
|
645
|
+
const urlSearchParamsSlot = (0, __wooksjs_event_core.cached)((ctx) => new WooksURLSearchParams(ctx.get(rawSearchParamsSlot)));
|
|
646
|
+
/**
|
|
647
|
+
* Provides access to URL search (query) parameters from the request.
|
|
648
|
+
* @example
|
|
649
|
+
* ```ts
|
|
650
|
+
* const { urlSearchParams, jsonSearchParams } = useSearchParams()
|
|
651
|
+
* const page = urlSearchParams().get('page')
|
|
652
|
+
* ```
|
|
653
|
+
*/
|
|
654
|
+
const useSearchParams = (0, __wooksjs_event_core.defineWook)((ctx) => ({
|
|
655
|
+
rawSearchParams: () => ctx.get(rawSearchParamsSlot),
|
|
656
|
+
urlSearchParams: () => ctx.get(urlSearchParamsSlot),
|
|
657
|
+
jsonSearchParams: () => ctx.get(urlSearchParamsSlot).toJson()
|
|
658
|
+
}));
|
|
659
|
+
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region packages/event-http/src/utils/time.ts
|
|
662
|
+
function convertTime(time, unit = "ms") {
|
|
663
|
+
if (typeof time === "number") return time / units[unit];
|
|
664
|
+
const rg = /(\d+)(\w+)/gu;
|
|
665
|
+
let t = 0;
|
|
666
|
+
let r;
|
|
667
|
+
while (r = rg.exec(time)) t += Number(r[1]) * (units[r[2]] || 0);
|
|
668
|
+
return t / units[unit];
|
|
669
|
+
}
|
|
670
|
+
const units = {
|
|
671
|
+
ms: 1,
|
|
672
|
+
s: 1e3,
|
|
673
|
+
m: 1e3 * 60,
|
|
674
|
+
h: 1e3 * 60 * 60,
|
|
675
|
+
d: 1e3 * 60 * 60 * 24,
|
|
676
|
+
w: 1e3 * 60 * 60 * 24 * 7,
|
|
677
|
+
M: 1e3 * 60 * 60 * 24 * 30,
|
|
678
|
+
Y: 1e3 * 60 * 60 * 24 * 365
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
//#endregion
|
|
682
|
+
//#region packages/event-http/src/utils/cache-control.ts
|
|
683
|
+
/** Renders a `TCacheControl` object into a `Cache-Control` header string. */
|
|
684
|
+
function renderCacheControl(data) {
|
|
685
|
+
let attrs = "";
|
|
686
|
+
for (const [a, v] of Object.entries(data)) {
|
|
687
|
+
if (v === void 0) continue;
|
|
688
|
+
const func = cacheControlFunc[a];
|
|
689
|
+
if (typeof func === "function") {
|
|
690
|
+
const val = func(v);
|
|
691
|
+
if (val) attrs += attrs ? `, ${val}` : val;
|
|
692
|
+
} else throw new TypeError(`Unknown Cache-Control attribute ${a}`);
|
|
693
|
+
}
|
|
694
|
+
return attrs;
|
|
695
|
+
}
|
|
696
|
+
const cacheControlFunc = {
|
|
697
|
+
mustRevalidate: (v) => v ? "must-revalidate" : "",
|
|
698
|
+
noCache: (v) => v ? typeof v === "string" ? `no-cache="${v}"` : "no-cache" : "",
|
|
699
|
+
noStore: (v) => v ? "no-store" : "",
|
|
700
|
+
noTransform: (v) => v ? "no-transform" : "",
|
|
701
|
+
public: (v) => v ? "public" : "",
|
|
702
|
+
private: (v) => v ? typeof v === "string" ? `private="${v}"` : "private" : "",
|
|
703
|
+
proxyRevalidate: (v) => v ? "proxy-revalidate" : "",
|
|
704
|
+
maxAge: (v) => `max-age=${convertTime(v, "s").toString()}`,
|
|
705
|
+
sMaxage: (v) => `s-maxage=${convertTime(v, "s").toString()}`
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
//#endregion
|
|
709
|
+
//#region packages/event-http/src/utils/set-cookie.ts
|
|
710
|
+
const COOKIE_NAME_RE = /^[\w!#$%&'*+\-.^`|~]+$/;
|
|
711
|
+
function sanitizeCookieAttrValue(v) {
|
|
712
|
+
return v.replace(/[;\r\n]/g, "");
|
|
713
|
+
}
|
|
714
|
+
function renderCookie(key, data) {
|
|
715
|
+
if (!COOKIE_NAME_RE.test(key)) throw new TypeError(`Invalid cookie name "${key}"`);
|
|
716
|
+
let attrs = "";
|
|
717
|
+
for (const [a, v] of Object.entries(data.attrs)) {
|
|
718
|
+
const func = cookieAttrFunc[a];
|
|
719
|
+
if (typeof func === "function") {
|
|
720
|
+
const val = func(v);
|
|
721
|
+
attrs += val ? `; ${val}` : "";
|
|
722
|
+
} else throw new TypeError(`Unknown Set-Cookie attribute ${a}`);
|
|
723
|
+
}
|
|
724
|
+
return `${key}=${encodeURIComponent(data.value)}${attrs}`;
|
|
725
|
+
}
|
|
726
|
+
const cookieAttrFunc = {
|
|
727
|
+
expires: (v) => `Expires=${typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString()}`,
|
|
728
|
+
maxAge: (v) => `Max-Age=${convertTime(v, "s").toString()}`,
|
|
729
|
+
domain: (v) => `Domain=${sanitizeCookieAttrValue(String(v))}`,
|
|
730
|
+
path: (v) => `Path=${sanitizeCookieAttrValue(String(v))}`,
|
|
731
|
+
secure: (v) => v ? "Secure" : "",
|
|
732
|
+
httpOnly: (v) => v ? "HttpOnly" : "",
|
|
733
|
+
sameSite: (v) => v ? `SameSite=${typeof v === "string" ? v : "Strict"}` : ""
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
//#endregion
|
|
737
|
+
//#region packages/event-http/src/response/http-response.ts
|
|
738
|
+
const hasFetchResponse = typeof globalThis.Response === "function";
|
|
739
|
+
const defaultStatus = {
|
|
740
|
+
GET: EHttpStatusCode.OK,
|
|
741
|
+
POST: EHttpStatusCode.Created,
|
|
742
|
+
PUT: EHttpStatusCode.Created,
|
|
743
|
+
PATCH: EHttpStatusCode.Accepted,
|
|
744
|
+
DELETE: EHttpStatusCode.Accepted
|
|
745
|
+
};
|
|
746
|
+
/**
|
|
747
|
+
* Manages response status, headers, cookies, cache control, and body for an HTTP request.
|
|
748
|
+
*
|
|
749
|
+
* All header mutations are accumulated in memory and flushed in a single `writeHead()` call
|
|
750
|
+
* when `send()` is invoked. Setter methods are chainable.
|
|
751
|
+
*
|
|
752
|
+
* @example
|
|
753
|
+
* ```ts
|
|
754
|
+
* const response = useResponse()
|
|
755
|
+
* response.setStatus(200).setHeader('x-custom', 'value')
|
|
756
|
+
* response.setCookie('session', 'abc', { httpOnly: true })
|
|
757
|
+
* ```
|
|
758
|
+
*/
|
|
759
|
+
var HttpResponse = class {
|
|
760
|
+
/**
|
|
761
|
+
* @param _res - The underlying Node.js `ServerResponse`.
|
|
762
|
+
* @param _req - The underlying Node.js `IncomingMessage`.
|
|
763
|
+
* @param _logger - Logger instance for error reporting.
|
|
764
|
+
* @param defaultHeaders - Optional headers to pre-populate on this response (e.g. from `securityHeaders()`).
|
|
765
|
+
*/
|
|
766
|
+
constructor(_res, _req, _logger, defaultHeaders) {
|
|
767
|
+
this._res = _res;
|
|
768
|
+
this._req = _req;
|
|
769
|
+
this._logger = _logger;
|
|
770
|
+
if (defaultHeaders) for (const key in defaultHeaders) this._headers[key] = defaultHeaders[key];
|
|
771
|
+
}
|
|
772
|
+
_status = 0;
|
|
773
|
+
_body = void 0;
|
|
774
|
+
_headers = {};
|
|
775
|
+
_cookies = {};
|
|
776
|
+
_rawCookies = [];
|
|
777
|
+
_hasCookies = false;
|
|
778
|
+
_responded = false;
|
|
779
|
+
/** The HTTP status code. If not set, it is inferred automatically when `send()` is called. */
|
|
780
|
+
get status() {
|
|
781
|
+
return this._status;
|
|
782
|
+
}
|
|
783
|
+
set status(value) {
|
|
784
|
+
this._status = value;
|
|
785
|
+
}
|
|
786
|
+
/** Sets the HTTP status code (chainable). */
|
|
787
|
+
setStatus(value) {
|
|
788
|
+
this._status = value;
|
|
789
|
+
return this;
|
|
790
|
+
}
|
|
791
|
+
/** The response body. Automatically serialized by `send()` (objects → JSON, strings → text). */
|
|
792
|
+
get body() {
|
|
793
|
+
return this._body;
|
|
794
|
+
}
|
|
795
|
+
set body(value) {
|
|
796
|
+
this._body = value;
|
|
797
|
+
}
|
|
798
|
+
/** Sets the response body (chainable). */
|
|
799
|
+
setBody(value) {
|
|
800
|
+
this._body = value;
|
|
801
|
+
return this;
|
|
802
|
+
}
|
|
803
|
+
/** Sets a single response header (chainable). Arrays produce multi-value headers. */
|
|
804
|
+
setHeader(name, value) {
|
|
805
|
+
this._headers[name] = Array.isArray(value) ? value : value.toString();
|
|
806
|
+
return this;
|
|
807
|
+
}
|
|
808
|
+
/** Batch-sets multiple response headers from a record (chainable). Existing keys are overwritten. */
|
|
809
|
+
setHeaders(headers) {
|
|
810
|
+
for (const key in headers) this._headers[key] = headers[key];
|
|
811
|
+
return this;
|
|
812
|
+
}
|
|
813
|
+
/** Returns the value of a response header, or `undefined` if not set. */
|
|
814
|
+
getHeader(name) {
|
|
815
|
+
return this._headers[name];
|
|
816
|
+
}
|
|
817
|
+
/** Removes a response header (chainable). */
|
|
818
|
+
removeHeader(name) {
|
|
819
|
+
delete this._headers[name];
|
|
820
|
+
return this;
|
|
821
|
+
}
|
|
822
|
+
/** Returns a read-only snapshot of all response headers. */
|
|
823
|
+
headers() {
|
|
824
|
+
return this._headers;
|
|
825
|
+
}
|
|
826
|
+
/** Sets the `Content-Type` response header (chainable). */
|
|
827
|
+
setContentType(value) {
|
|
828
|
+
this._headers["content-type"] = value;
|
|
829
|
+
return this;
|
|
830
|
+
}
|
|
831
|
+
/** Returns the current `Content-Type` header value. */
|
|
832
|
+
getContentType() {
|
|
833
|
+
return this._headers["content-type"];
|
|
834
|
+
}
|
|
835
|
+
/** Sets the `Access-Control-Allow-Origin` header (chainable). Defaults to `'*'`. */
|
|
836
|
+
enableCors(origin = "*") {
|
|
837
|
+
this._headers["access-control-allow-origin"] = origin;
|
|
838
|
+
return this;
|
|
839
|
+
}
|
|
840
|
+
/** Sets an outgoing `Set-Cookie` header with optional attributes (chainable). */
|
|
841
|
+
setCookie(name, value, attrs) {
|
|
842
|
+
this._cookies[name] = {
|
|
843
|
+
value,
|
|
844
|
+
attrs: attrs || {}
|
|
845
|
+
};
|
|
846
|
+
this._hasCookies = true;
|
|
847
|
+
return this;
|
|
848
|
+
}
|
|
849
|
+
/** Returns a previously set cookie's data, or `undefined` if not set. */
|
|
850
|
+
getCookie(name) {
|
|
851
|
+
return this._cookies[name];
|
|
852
|
+
}
|
|
853
|
+
/** Removes a cookie from the outgoing set list (chainable). */
|
|
854
|
+
removeCookie(name) {
|
|
855
|
+
delete this._cookies[name];
|
|
856
|
+
return this;
|
|
857
|
+
}
|
|
858
|
+
/** Removes all outgoing cookies (chainable). */
|
|
859
|
+
clearCookies() {
|
|
860
|
+
this._cookies = {};
|
|
861
|
+
this._rawCookies = [];
|
|
862
|
+
this._hasCookies = false;
|
|
863
|
+
return this;
|
|
864
|
+
}
|
|
865
|
+
/** Appends a raw `Set-Cookie` header string (chainable). Use when you need full control over the cookie format. */
|
|
866
|
+
setCookieRaw(rawValue) {
|
|
867
|
+
this._rawCookies.push(rawValue);
|
|
868
|
+
this._hasCookies = true;
|
|
869
|
+
return this;
|
|
870
|
+
}
|
|
871
|
+
/** Sets the `Cache-Control` header from a directive object (chainable). */
|
|
872
|
+
setCacheControl(data) {
|
|
873
|
+
this._headers["cache-control"] = renderCacheControl(data);
|
|
874
|
+
return this;
|
|
875
|
+
}
|
|
876
|
+
/** Sets the `Age` header in seconds (chainable). Accepts a number or time string (e.g. `'2h 15m'`). */
|
|
877
|
+
setAge(value) {
|
|
878
|
+
this._headers.age = convertTime(value, "s").toString();
|
|
879
|
+
return this;
|
|
880
|
+
}
|
|
881
|
+
/** Sets the `Expires` header (chainable). Accepts a `Date`, date string, or timestamp. */
|
|
882
|
+
setExpires(value) {
|
|
883
|
+
this._headers.expires = typeof value === "string" || typeof value === "number" ? new Date(value).toUTCString() : value.toUTCString();
|
|
884
|
+
return this;
|
|
885
|
+
}
|
|
886
|
+
/** Sets or clears the `Pragma: no-cache` header (chainable). */
|
|
887
|
+
setPragmaNoCache(value = true) {
|
|
888
|
+
this._headers.pragma = value ? "no-cache" : "";
|
|
889
|
+
return this;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Returns the underlying Node.js `ServerResponse`.
|
|
893
|
+
* @param passthrough - If `true`, the framework still manages the response lifecycle. If `false` (default), the response is marked as "responded" and the framework will not touch it.
|
|
894
|
+
*/
|
|
895
|
+
getRawRes(passthrough) {
|
|
896
|
+
if (!passthrough) this._responded = true;
|
|
897
|
+
return this._res;
|
|
898
|
+
}
|
|
899
|
+
/** Whether the response has already been sent (or the underlying stream is no longer writable). */
|
|
900
|
+
get responded() {
|
|
901
|
+
return this._responded || !this._res.writable || this._res.writableEnded;
|
|
902
|
+
}
|
|
903
|
+
renderBody() {
|
|
904
|
+
const body = this._body;
|
|
905
|
+
if (body === void 0 || body === null) return "";
|
|
906
|
+
if (typeof body === "string") {
|
|
907
|
+
if (!this._headers["content-type"]) this._headers["content-type"] = "text/plain";
|
|
908
|
+
return body;
|
|
909
|
+
}
|
|
910
|
+
if (typeof body === "boolean" || typeof body === "number") {
|
|
911
|
+
if (!this._headers["content-type"]) this._headers["content-type"] = "text/plain";
|
|
912
|
+
return body.toString();
|
|
913
|
+
}
|
|
914
|
+
if (body instanceof Uint8Array) return body;
|
|
915
|
+
if (typeof body === "object") {
|
|
916
|
+
if (!this._headers["content-type"]) this._headers["content-type"] = "application/json";
|
|
917
|
+
return JSON.stringify(body);
|
|
918
|
+
}
|
|
919
|
+
throw new Error(`Unsupported body format "${typeof body}"`);
|
|
920
|
+
}
|
|
921
|
+
renderError(data, _ctx) {
|
|
922
|
+
this._status = data.statusCode || 500;
|
|
923
|
+
this._headers["content-type"] = "application/json";
|
|
924
|
+
this._body = JSON.stringify(data);
|
|
925
|
+
}
|
|
926
|
+
/** Renders and sends an HTTP error response. Called automatically by the framework when a handler throws an `HttpError`. */
|
|
927
|
+
sendError(error, ctx) {
|
|
928
|
+
const data = error.body;
|
|
929
|
+
this.renderError(data, ctx);
|
|
930
|
+
return this.send();
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Finalizes and sends the response.
|
|
934
|
+
*
|
|
935
|
+
* Flushes all accumulated headers (including cookies) in a single `writeHead()` call,
|
|
936
|
+
* then writes the body. Supports `Readable` streams, `fetch` `Response` objects, and regular values.
|
|
937
|
+
*
|
|
938
|
+
* @throws Error if the response was already sent.
|
|
939
|
+
*/
|
|
940
|
+
send() {
|
|
941
|
+
if (this._responded) {
|
|
942
|
+
const err = /* @__PURE__ */ new Error("The response was already sent.");
|
|
943
|
+
this._logger.error(err.message, err);
|
|
944
|
+
throw err;
|
|
945
|
+
}
|
|
946
|
+
this._responded = true;
|
|
947
|
+
this.finalizeCookies();
|
|
948
|
+
const body = this._body;
|
|
949
|
+
const method = this._req.method;
|
|
950
|
+
if (body instanceof stream.Readable) return this.sendStream(body, method);
|
|
951
|
+
if (hasFetchResponse && body instanceof Response) return this.sendFetchResponse(body, method);
|
|
952
|
+
this.sendRegular(method);
|
|
953
|
+
}
|
|
954
|
+
finalizeCookies() {
|
|
955
|
+
if (!this._hasCookies) return;
|
|
956
|
+
const entries = Object.entries(this._cookies);
|
|
957
|
+
const rendered = [];
|
|
958
|
+
for (const [name, data] of entries) if (data) rendered.push(renderCookie(name, data));
|
|
959
|
+
if (this._rawCookies.length > 0) rendered.push(...this._rawCookies);
|
|
960
|
+
if (rendered.length > 0) {
|
|
961
|
+
const existing = this._headers["set-cookie"];
|
|
962
|
+
if (existing) this._headers["set-cookie"] = [...Array.isArray(existing) ? existing : [existing], ...rendered];
|
|
963
|
+
else this._headers["set-cookie"] = rendered;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
autoStatus(hasBody) {
|
|
967
|
+
if (this._status) return;
|
|
968
|
+
if (!hasBody) {
|
|
969
|
+
this._status = EHttpStatusCode.NoContent;
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
this._status = defaultStatus[this._req.method] || EHttpStatusCode.OK;
|
|
973
|
+
}
|
|
974
|
+
sendStream(stream$1, method) {
|
|
975
|
+
this.autoStatus(true);
|
|
976
|
+
this._res.writeHead(this._status, this._headers);
|
|
977
|
+
this._req.once("close", () => {
|
|
978
|
+
stream$1.destroy();
|
|
979
|
+
});
|
|
980
|
+
if (method === "HEAD") {
|
|
981
|
+
stream$1.destroy();
|
|
982
|
+
this._res.end();
|
|
983
|
+
return Promise.resolve();
|
|
984
|
+
}
|
|
985
|
+
return new Promise((resolve, reject) => {
|
|
986
|
+
stream$1.on("error", (e) => {
|
|
987
|
+
this._logger.error("Stream error", e);
|
|
988
|
+
stream$1.destroy();
|
|
989
|
+
this._res.end();
|
|
990
|
+
reject(e);
|
|
991
|
+
});
|
|
992
|
+
stream$1.on("close", () => {
|
|
993
|
+
stream$1.destroy();
|
|
994
|
+
resolve();
|
|
995
|
+
});
|
|
996
|
+
stream$1.pipe(this._res);
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
async sendFetchResponse(fetchResponse, method) {
|
|
1000
|
+
this._status = this._status || fetchResponse.status;
|
|
1001
|
+
const fetchContentLength = fetchResponse.headers.get("content-length");
|
|
1002
|
+
if (fetchContentLength) this._headers["content-length"] = fetchContentLength;
|
|
1003
|
+
const fetchContentType = fetchResponse.headers.get("content-type");
|
|
1004
|
+
if (fetchContentType) this._headers["content-type"] = fetchContentType;
|
|
1005
|
+
this._res.writeHead(this._status, this._headers);
|
|
1006
|
+
if (method === "HEAD") {
|
|
1007
|
+
this._res.end();
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
const fetchBody = fetchResponse.body;
|
|
1011
|
+
if (fetchBody) try {
|
|
1012
|
+
for await (const chunk of fetchBody) this._res.write(chunk);
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
this._logger.error("Error streaming fetch response body", error);
|
|
1015
|
+
}
|
|
1016
|
+
if (!this._res.writableEnded) this._res.end();
|
|
1017
|
+
}
|
|
1018
|
+
sendRegular(method) {
|
|
1019
|
+
const renderedBody = this.renderBody();
|
|
1020
|
+
this.autoStatus(!!renderedBody);
|
|
1021
|
+
const contentLength = typeof renderedBody === "string" ? Buffer.byteLength(renderedBody) : renderedBody.byteLength;
|
|
1022
|
+
this._headers["content-length"] = contentLength.toString();
|
|
1023
|
+
this._res.writeHead(this._status, this._headers).end(method === "HEAD" ? "" : renderedBody);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
//#endregion
|
|
1028
|
+
//#region packages/event-http/src/event-http.ts
|
|
1029
|
+
/** Creates an async event context for an incoming HTTP request/response pair. */
|
|
1030
|
+
function createHttpContext(data, options, ResponseClass = HttpResponse) {
|
|
1031
|
+
const ctx = new __wooksjs_event_core.EventContext(options);
|
|
1032
|
+
const response = new ResponseClass(data.res, data.req, ctx.logger);
|
|
1033
|
+
return (fn) => (0, __wooksjs_event_core.run)(ctx, () => ctx.seed(httpKind, {
|
|
1034
|
+
req: data.req,
|
|
1035
|
+
response,
|
|
1036
|
+
requestLimits: data.requestLimits
|
|
1037
|
+
}, fn));
|
|
1038
|
+
}
|
|
1039
|
+
/** Returns the current HTTP event context. */
|
|
1040
|
+
function useHttpContext(ctx) {
|
|
1041
|
+
return ctx ?? (0, __wooksjs_event_core.current)();
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
//#endregion
|
|
1045
|
+
//#region packages/event-http/src/errors/403.tl.svg
|
|
1046
|
+
function _403_tl_default(ctx) {
|
|
1047
|
+
return `<svg height="64" viewBox="0 4 100 96" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#888888" stroke-width="2">
|
|
1048
|
+
<path d="M50 90.625C64.4042 87.1875 83.5751 69.8667 83.5751 48.6937V24.0854L50 9.375L16.425 24.0833V48.6937C16.425 69.8667 35.5959 87.1875 50 90.625Z" fill="#ff000050">
|
|
1049
|
+
<animate attributeName="fill" dur="2s" repeatCount="indefinite"
|
|
1050
|
+
values="#ff000000;#ff000050;#ff000000" />
|
|
1051
|
+
</path>
|
|
1052
|
+
|
|
1053
|
+
<path d="M61.5395 46.0812H38.4604C37.1061 46.0812 36.0083 47.1791 36.0083 48.5333V65.075C36.0083 66.4292 37.1061 67.5271 38.4604 67.5271H61.5395C62.8938 67.5271 63.9916 66.4292 63.9916 65.075V48.5333C63.9916 47.1791 62.8938 46.0812 61.5395 46.0812Z" />
|
|
1054
|
+
|
|
1055
|
+
<path d="M41.7834 46.0834V39.6813C41.7834 37.5021 42.6491 35.4121 44.1901 33.8712C45.731 32.3303 47.8209 31.4646 50.0001 31.4646C52.1793 31.4646 54.2693 32.3303 55.8102 33.8712C57.3511 35.4121 58.2168 37.5021 58.2168 39.6813V46.0813" />
|
|
1056
|
+
</svg>
|
|
1057
|
+
`;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
//#endregion
|
|
1061
|
+
//#region packages/event-http/src/errors/404.tl.svg
|
|
1062
|
+
function _404_tl_default(ctx) {
|
|
1063
|
+
return `<svg height="64" viewBox="0 20 100 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1064
|
+
<defs>
|
|
1065
|
+
<path id="sheet" d="M86.5 36.5V47.5H97.5V92.5H52.5V36.5H86.5ZM63 68.5H87V67.5H63V68.5ZM63 63.5H87V62.5H63V63.5ZM63 58.5H87V57.5H63V58.5ZM96.793 46.5H87.5V37.207L96.793 46.5Z" fill="#88888833" stroke="#888888aa"/>
|
|
1066
|
+
</defs>
|
|
1067
|
+
|
|
1068
|
+
<g id="queue" transform="translate(-5 -10)">
|
|
1069
|
+
<use href="#sheet" opacity="0">
|
|
1070
|
+
<animateTransform attributeName="transform" type="translate"
|
|
1071
|
+
dur="3s" repeatCount="indefinite"
|
|
1072
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
1073
|
+
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
1074
|
+
<animate attributeName="opacity"
|
|
1075
|
+
dur="3s" repeatCount="indefinite"
|
|
1076
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
1077
|
+
values="0;1;1;0;0" />
|
|
1078
|
+
</use>
|
|
1079
|
+
|
|
1080
|
+
<use href="#sheet" opacity="0">
|
|
1081
|
+
<animateTransform attributeName="transform" type="translate"
|
|
1082
|
+
dur="3s" begin="1s" repeatCount="indefinite"
|
|
1083
|
+
keyTimes="0;0.1;0.32;0.42;1"
|
|
1084
|
+
values="30 0; -20 0; -20 0; -70 0; -70 0" />
|
|
388
1085
|
<animate attributeName="opacity"
|
|
389
1086
|
dur="3s" begin="1s" repeatCount="indefinite"
|
|
390
1087
|
keyTimes="0;0.1;0.32;0.42;1"
|
|
@@ -657,824 +1354,113 @@ function error_tl_default(ctx) {
|
|
|
657
1354
|
font-size: 1.5rem;
|
|
658
1355
|
margin-bottom: 20px;
|
|
659
1356
|
}
|
|
660
|
-
.error-message {
|
|
661
|
-
font-size: 1rem;
|
|
662
|
-
margin-bottom: 25px;
|
|
663
|
-
}
|
|
664
|
-
.json-details-container {
|
|
665
|
-
padding: 15px;
|
|
666
|
-
font-size: 0.8rem;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
</style>
|
|
670
|
-
</head>
|
|
671
|
-
<body>
|
|
672
|
-
<div class="error-container">
|
|
673
|
-
<div id="statusCode" class="status-code">${icon} ${statusCode}</div>
|
|
674
|
-
|
|
675
|
-
<div id="statusText" class="status-text">${statusMessage}</div>
|
|
676
|
-
|
|
677
|
-
<p id="errorMessage" class="error-message">${message}</p>
|
|
678
|
-
|
|
679
|
-
<!-- prettier-ignore -->
|
|
680
|
-
<div class="json-details-container"style="display: ${details ? "block" : "none"};">
|
|
681
|
-
<p class="text-sm">Technical Details:</p>
|
|
682
|
-
<pre><code id="jsonDetails">${details}</code></pre>
|
|
683
|
-
</div>
|
|
684
|
-
<div class="footer">
|
|
685
|
-
Powered by
|
|
686
|
-
<a href="${link}" target="_blank">
|
|
687
|
-
<img height="20" alt="%{poweredBy}" src="${image}" />
|
|
688
|
-
</a>
|
|
689
|
-
v${version}
|
|
690
|
-
</div>
|
|
691
|
-
</div>
|
|
692
|
-
</body>
|
|
693
|
-
</html>
|
|
694
|
-
`;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
//#endregion
|
|
698
|
-
//#region packages/event-http/src/errors/error-renderer.ts
|
|
699
|
-
let framework = {
|
|
700
|
-
version: "0.6.5",
|
|
701
|
-
poweredBy: `wooksjs`,
|
|
702
|
-
link: `https://wooks.moost.org/`,
|
|
703
|
-
image: `https://wooks.moost.org/wooks-full-logo.png`
|
|
704
|
-
};
|
|
705
|
-
/** Renders HTTP error responses in HTML, JSON, or plain text based on the Accept header. */
|
|
706
|
-
var HttpErrorRenderer = class extends BaseHttpResponseRenderer {
|
|
707
|
-
constructor(opts) {
|
|
708
|
-
super();
|
|
709
|
-
this.opts = opts;
|
|
710
|
-
}
|
|
711
|
-
icons = {
|
|
712
|
-
401: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
|
|
713
|
-
403: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
|
|
714
|
-
404: typeof _404_tl_default === "function" ? _404_tl_default({}) : "",
|
|
715
|
-
500: typeof _500_tl_default === "function" ? _500_tl_default({}) : ""
|
|
716
|
-
};
|
|
717
|
-
static registerFramework(opts) {
|
|
718
|
-
framework = opts;
|
|
719
|
-
}
|
|
720
|
-
renderHtml(response) {
|
|
721
|
-
const data = response.body || {};
|
|
722
|
-
response.setContentType("text/html");
|
|
723
|
-
const hasDetails = Object.keys(data).length > 3;
|
|
724
|
-
const icon = data.statusCode >= 500 ? this.icons[500] : this.icons[data.statusCode] || "";
|
|
725
|
-
return typeof error_tl_default === "function" ? error_tl_default({
|
|
726
|
-
icon,
|
|
727
|
-
statusCode: data.statusCode,
|
|
728
|
-
statusMessage: httpStatusCodes[data.statusCode],
|
|
729
|
-
message: data.message,
|
|
730
|
-
details: hasDetails ? JSON.stringify(data, null, " ") : "",
|
|
731
|
-
version: (this.opts || framework).version,
|
|
732
|
-
poweredBy: (this.opts || framework).poweredBy,
|
|
733
|
-
link: (this.opts || framework).link,
|
|
734
|
-
image: (this.opts || framework).image
|
|
735
|
-
}) : JSON.stringify(data, null, " ");
|
|
736
|
-
}
|
|
737
|
-
renderText(response) {
|
|
738
|
-
const data = response.body || {};
|
|
739
|
-
response.setContentType("text/plain");
|
|
740
|
-
const keys = Object.keys(data).filter((key) => ![
|
|
741
|
-
"statusCode",
|
|
742
|
-
"error",
|
|
743
|
-
"message"
|
|
744
|
-
].includes(key));
|
|
745
|
-
return `${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}\n\n${keys.length > 0 ? `${JSON.stringify({
|
|
746
|
-
...data,
|
|
747
|
-
statusCode: void 0,
|
|
748
|
-
message: void 0,
|
|
749
|
-
error: void 0
|
|
750
|
-
}, null, " ")}` : ""}`;
|
|
751
|
-
}
|
|
752
|
-
renderJson(response) {
|
|
753
|
-
response.setContentType("application/json");
|
|
754
|
-
return JSON.stringify(response.body || {});
|
|
755
|
-
}
|
|
756
|
-
render(response) {
|
|
757
|
-
const { acceptsJson, acceptsText, acceptsHtml } = useAccept();
|
|
758
|
-
response.status = response.body?.statusCode || 500;
|
|
759
|
-
if (acceptsJson()) return this.renderJson(response);
|
|
760
|
-
else if (acceptsHtml()) return this.renderHtml(response);
|
|
761
|
-
else if (acceptsText()) return this.renderText(response);
|
|
762
|
-
else return this.renderJson(response);
|
|
763
|
-
}
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
//#endregion
|
|
767
|
-
//#region packages/event-http/src/errors/http-error.ts
|
|
768
|
-
/** Represents an HTTP error with a status code and optional structured body. */
|
|
769
|
-
var HttpError = class extends Error {
|
|
770
|
-
name = "HttpError";
|
|
771
|
-
constructor(code = 500, _body = "") {
|
|
772
|
-
super(typeof _body === "string" ? _body : _body.message);
|
|
773
|
-
this.code = code;
|
|
774
|
-
this._body = _body;
|
|
775
|
-
}
|
|
776
|
-
get body() {
|
|
777
|
-
return typeof this._body === "string" ? {
|
|
778
|
-
statusCode: this.code,
|
|
779
|
-
message: this.message,
|
|
780
|
-
error: httpStatusCodes[this.code]
|
|
781
|
-
} : {
|
|
782
|
-
...this._body,
|
|
783
|
-
statusCode: this.code,
|
|
784
|
-
message: this.message,
|
|
785
|
-
error: httpStatusCodes[this.code]
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
renderer;
|
|
789
|
-
attachRenderer(renderer) {
|
|
790
|
-
this.renderer = renderer;
|
|
791
|
-
}
|
|
792
|
-
getRenderer() {
|
|
793
|
-
return this.renderer;
|
|
794
|
-
}
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
//#endregion
|
|
798
|
-
//#region packages/event-http/src/composables/request.ts
|
|
799
|
-
const xForwardedFor = "x-forwarded-for";
|
|
800
|
-
/** Default safety limits for request body reading (size, ratio, timeout). */
|
|
801
|
-
const DEFAULT_LIMITS = {
|
|
802
|
-
maxCompressed: 1 * 1024 * 1024,
|
|
803
|
-
maxInflated: 10 * 1024 * 1024,
|
|
804
|
-
maxRatio: 100,
|
|
805
|
-
readTimeoutMs: 1e4
|
|
806
|
-
};
|
|
807
|
-
/**
|
|
808
|
-
* Provides access to the incoming HTTP request (method, url, headers, body, IP).
|
|
809
|
-
* @example
|
|
810
|
-
* ```ts
|
|
811
|
-
* const { method, url, rawBody, getIp } = useRequest()
|
|
812
|
-
* const body = await rawBody()
|
|
813
|
-
* ```
|
|
814
|
-
*/
|
|
815
|
-
function useRequest() {
|
|
816
|
-
const { store } = useHttpContext();
|
|
817
|
-
const { init } = store("request");
|
|
818
|
-
const event = store("event");
|
|
819
|
-
const req = event.get("req");
|
|
820
|
-
const contentEncoding = req.headers["content-encoding"];
|
|
821
|
-
const contentEncodings = () => init("contentEncodings", () => (contentEncoding || "").split(",").map((p) => p.trim()).filter((p) => !!p));
|
|
822
|
-
const isCompressed = () => init("isCompressed", () => {
|
|
823
|
-
const parts = contentEncodings();
|
|
824
|
-
for (const p of parts) if ([
|
|
825
|
-
"deflate",
|
|
826
|
-
"gzip",
|
|
827
|
-
"br"
|
|
828
|
-
].includes(p)) return true;
|
|
829
|
-
return false;
|
|
830
|
-
});
|
|
831
|
-
const limits = () => event.get("requestLimits");
|
|
832
|
-
const setLimit = (key, value) => {
|
|
833
|
-
let obj = limits();
|
|
834
|
-
if (!obj?.perRequest) {
|
|
835
|
-
obj = {
|
|
836
|
-
...obj,
|
|
837
|
-
perRequest: true
|
|
838
|
-
};
|
|
839
|
-
event.set("requestLimits", obj);
|
|
840
|
-
}
|
|
841
|
-
obj[key] = value;
|
|
842
|
-
};
|
|
843
|
-
const getMaxCompressed = () => limits()?.maxCompressed ?? DEFAULT_LIMITS.maxCompressed;
|
|
844
|
-
const setMaxCompressed = (limit) => setLimit("maxCompressed", limit);
|
|
845
|
-
const getMaxInflated = () => limits()?.maxInflated ?? DEFAULT_LIMITS.maxInflated;
|
|
846
|
-
const setMaxInflated = (limit) => setLimit("maxInflated", limit);
|
|
847
|
-
const getMaxRatio = () => limits()?.maxRatio ?? DEFAULT_LIMITS.maxRatio;
|
|
848
|
-
const setMaxRatio = (limit) => setLimit("maxRatio", limit);
|
|
849
|
-
const getReadTimeoutMs = () => limits()?.readTimeoutMs ?? DEFAULT_LIMITS.readTimeoutMs;
|
|
850
|
-
const setReadTimeoutMs = (limit) => setLimit("readTimeoutMs", limit);
|
|
851
|
-
const rawBody = () => init("rawBody", async () => {
|
|
852
|
-
const encs = contentEncodings();
|
|
853
|
-
const isZip = isCompressed();
|
|
854
|
-
const streamable = isZip && encodingSupportsStream(encs);
|
|
855
|
-
const maxCompressed = getMaxCompressed();
|
|
856
|
-
const maxInflated = getMaxInflated();
|
|
857
|
-
const maxRatio = getMaxRatio();
|
|
858
|
-
const timeoutMs = getReadTimeoutMs();
|
|
859
|
-
const cl = Number(req.headers["content-length"] ?? 0);
|
|
860
|
-
const upfrontLimit = isZip ? maxCompressed : maxInflated;
|
|
861
|
-
if (cl && cl > upfrontLimit) throw new HttpError(413, "Payload Too Large");
|
|
862
|
-
for (const enc of encs) if (!compressors[enc]) throw new HttpError(415, `Unsupported Content-Encoding "${enc}"`);
|
|
863
|
-
let timer = null;
|
|
864
|
-
function resetTimer() {
|
|
865
|
-
if (timeoutMs === 0) return;
|
|
866
|
-
clearTimer();
|
|
867
|
-
timer = setTimeout(() => {
|
|
868
|
-
clearTimer();
|
|
869
|
-
req.destroy();
|
|
870
|
-
}, timeoutMs);
|
|
871
|
-
}
|
|
872
|
-
function clearTimer() {
|
|
873
|
-
if (timer) {
|
|
874
|
-
clearTimeout(timer);
|
|
875
|
-
timer = null;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
let rawBytes = 0;
|
|
879
|
-
async function* limitedCompressed() {
|
|
880
|
-
resetTimer();
|
|
881
|
-
try {
|
|
882
|
-
for await (const chunk of req) {
|
|
883
|
-
rawBytes += chunk.length;
|
|
884
|
-
if (rawBytes > upfrontLimit) {
|
|
885
|
-
req.destroy();
|
|
886
|
-
throw new HttpError(413, "Payload Too Large");
|
|
887
|
-
}
|
|
888
|
-
resetTimer();
|
|
889
|
-
yield chunk;
|
|
890
|
-
}
|
|
891
|
-
} finally {
|
|
892
|
-
clearTimer();
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
let stream$1 = limitedCompressed();
|
|
896
|
-
if (streamable) stream$1 = await uncompressBodyStream(encs, stream$1);
|
|
897
|
-
const chunks = [];
|
|
898
|
-
let inflatedBytes = 0;
|
|
899
|
-
try {
|
|
900
|
-
for await (const chunk of stream$1) {
|
|
901
|
-
inflatedBytes += chunk.length;
|
|
902
|
-
if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
|
|
903
|
-
chunks.push(chunk);
|
|
904
|
-
}
|
|
905
|
-
} catch (error) {
|
|
906
|
-
if (error instanceof HttpError) throw error;
|
|
907
|
-
throw new HttpError(408, "Request body timeout");
|
|
908
|
-
}
|
|
909
|
-
let body = buffer.Buffer.concat(chunks);
|
|
910
|
-
if (!streamable && isZip) {
|
|
911
|
-
body = await uncompressBody(encs, body);
|
|
912
|
-
inflatedBytes = body.byteLength;
|
|
913
|
-
if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
|
|
914
|
-
}
|
|
915
|
-
if (isZip && rawBytes > 0 && inflatedBytes / rawBytes > maxRatio) throw new HttpError(413, "Compression ratio too high");
|
|
916
|
-
return body;
|
|
917
|
-
});
|
|
918
|
-
const reqId = (0, __wooksjs_event_core.useEventId)().getId;
|
|
919
|
-
const forwardedIp = () => init("forwardedIp", () => {
|
|
920
|
-
if (typeof req.headers[xForwardedFor] === "string" && req.headers[xForwardedFor]) return req.headers[xForwardedFor].split(",").shift()?.trim();
|
|
921
|
-
else return "";
|
|
922
|
-
});
|
|
923
|
-
const remoteIp = () => init("remoteIp", () => req.socket.remoteAddress || req.connection.remoteAddress || "");
|
|
924
|
-
function getIp(options) {
|
|
925
|
-
if (options?.trustProxy) return forwardedIp() || getIp();
|
|
926
|
-
else return remoteIp();
|
|
927
|
-
}
|
|
928
|
-
const getIpList = () => init("ipList", () => ({
|
|
929
|
-
remoteIp: req.socket.remoteAddress || req.connection.remoteAddress || "",
|
|
930
|
-
forwarded: (req.headers[xForwardedFor] || "").split(",").map((s) => s.trim())
|
|
931
|
-
}));
|
|
932
|
-
return {
|
|
933
|
-
rawRequest: req,
|
|
934
|
-
url: req.url,
|
|
935
|
-
method: req.method,
|
|
936
|
-
headers: req.headers,
|
|
937
|
-
rawBody,
|
|
938
|
-
reqId,
|
|
939
|
-
getIp,
|
|
940
|
-
getIpList,
|
|
941
|
-
isCompressed,
|
|
942
|
-
getMaxCompressed,
|
|
943
|
-
setMaxCompressed,
|
|
944
|
-
getReadTimeoutMs,
|
|
945
|
-
setReadTimeoutMs,
|
|
946
|
-
getMaxInflated,
|
|
947
|
-
setMaxInflated,
|
|
948
|
-
getMaxRatio,
|
|
949
|
-
setMaxRatio
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
//#endregion
|
|
954
|
-
//#region packages/event-http/src/composables/headers.ts
|
|
955
|
-
/**
|
|
956
|
-
* Returns the incoming request headers.
|
|
957
|
-
* @example
|
|
958
|
-
* ```ts
|
|
959
|
-
* const { host, authorization } = useHeaders()
|
|
960
|
-
* ```
|
|
961
|
-
*/
|
|
962
|
-
function useHeaders() {
|
|
963
|
-
return useRequest().headers;
|
|
964
|
-
}
|
|
965
|
-
/**
|
|
966
|
-
* Provides methods to set, get, and remove outgoing response headers.
|
|
967
|
-
* @example
|
|
968
|
-
* ```ts
|
|
969
|
-
* const { setHeader, setContentType, enableCors } = useSetHeaders()
|
|
970
|
-
* setHeader('x-request-id', '123')
|
|
971
|
-
* ```
|
|
972
|
-
*/
|
|
973
|
-
function useSetHeaders() {
|
|
974
|
-
const { store } = useHttpContext();
|
|
975
|
-
const setHeaderStore = store("setHeader");
|
|
976
|
-
function setHeader(name, value) {
|
|
977
|
-
setHeaderStore.set(name, value.toString());
|
|
978
|
-
}
|
|
979
|
-
function setContentType(value) {
|
|
980
|
-
setHeader("content-type", value);
|
|
981
|
-
}
|
|
982
|
-
function enableCors(origin = "*") {
|
|
983
|
-
setHeader("access-control-allow-origin", origin);
|
|
984
|
-
}
|
|
985
|
-
return {
|
|
986
|
-
setHeader,
|
|
987
|
-
getHeader: setHeaderStore.get,
|
|
988
|
-
removeHeader: setHeaderStore.del,
|
|
989
|
-
setContentType,
|
|
990
|
-
headers: () => setHeaderStore.value || {},
|
|
991
|
-
enableCors
|
|
992
|
-
};
|
|
993
|
-
}
|
|
994
|
-
/** Returns a hookable accessor for a single outgoing response header by name. */
|
|
995
|
-
function useSetHeader(name) {
|
|
996
|
-
const { store } = useHttpContext();
|
|
997
|
-
const { hook } = store("setHeader");
|
|
998
|
-
return hook(name);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
//#endregion
|
|
1002
|
-
//#region packages/event-http/src/composables/cookies.ts
|
|
1003
|
-
const cookieRegExpCache = /* @__PURE__ */ new Map();
|
|
1004
|
-
function getCookieRegExp(name) {
|
|
1005
|
-
let re = cookieRegExpCache.get(name);
|
|
1006
|
-
if (!re) {
|
|
1007
|
-
re = new RegExp(`(?:^|; )${escapeRegex(name)}=(.*?)(?:;?$|; )`, "i");
|
|
1008
|
-
cookieRegExpCache.set(name, re);
|
|
1009
|
-
}
|
|
1010
|
-
return re;
|
|
1011
|
-
}
|
|
1012
|
-
/**
|
|
1013
|
-
* Provides access to parsed request cookies.
|
|
1014
|
-
* @example
|
|
1015
|
-
* ```ts
|
|
1016
|
-
* const { getCookie, rawCookies } = useCookies()
|
|
1017
|
-
* const sessionId = getCookie('session_id')
|
|
1018
|
-
* ```
|
|
1019
|
-
*/
|
|
1020
|
-
function useCookies() {
|
|
1021
|
-
const { store } = useHttpContext();
|
|
1022
|
-
const { cookie } = useHeaders();
|
|
1023
|
-
const { init } = store("cookies");
|
|
1024
|
-
const getCookie = (name) => init(name, () => {
|
|
1025
|
-
if (cookie) {
|
|
1026
|
-
const result = getCookieRegExp(name).exec(cookie);
|
|
1027
|
-
return result?.[1] ? safeDecodeURIComponent(result[1]) : null;
|
|
1028
|
-
} else return null;
|
|
1029
|
-
});
|
|
1030
|
-
return {
|
|
1031
|
-
rawCookies: cookie,
|
|
1032
|
-
getCookie
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
/** Provides methods to set, get, remove, and clear outgoing response cookies. */
|
|
1036
|
-
function useSetCookies() {
|
|
1037
|
-
const { store } = useHttpContext();
|
|
1038
|
-
const cookiesStore = store("setCookies");
|
|
1039
|
-
function setCookie(name, value, attrs) {
|
|
1040
|
-
cookiesStore.set(name, {
|
|
1041
|
-
value,
|
|
1042
|
-
attrs: attrs || {}
|
|
1043
|
-
});
|
|
1044
|
-
}
|
|
1045
|
-
function cookies() {
|
|
1046
|
-
const entries = cookiesStore.entries();
|
|
1047
|
-
if (entries.length === 0) return [];
|
|
1048
|
-
return entries.filter((a) => !!a[1]).map(([key, value]) => renderCookie(key, value));
|
|
1049
|
-
}
|
|
1050
|
-
return {
|
|
1051
|
-
setCookie,
|
|
1052
|
-
getCookie: cookiesStore.get,
|
|
1053
|
-
removeCookie: cookiesStore.del,
|
|
1054
|
-
clearCookies: cookiesStore.clear,
|
|
1055
|
-
cookies
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
/** Returns a hookable accessor for a single outgoing cookie by name. */
|
|
1059
|
-
function useSetCookie(name) {
|
|
1060
|
-
const { setCookie, getCookie } = useSetCookies();
|
|
1061
|
-
const valueHook = (0, __wooksjs_event_core.attachHook)({
|
|
1062
|
-
name,
|
|
1063
|
-
type: "cookie"
|
|
1064
|
-
}, {
|
|
1065
|
-
get: () => getCookie(name)?.value,
|
|
1066
|
-
set: (value) => {
|
|
1067
|
-
setCookie(name, value, getCookie(name)?.attrs);
|
|
1068
|
-
}
|
|
1069
|
-
});
|
|
1070
|
-
return (0, __wooksjs_event_core.attachHook)(valueHook, {
|
|
1071
|
-
get: () => getCookie(name)?.attrs,
|
|
1072
|
-
set: (attrs) => {
|
|
1073
|
-
setCookie(name, getCookie(name)?.value || "", attrs);
|
|
1074
|
-
}
|
|
1075
|
-
}, "attrs");
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
//#endregion
|
|
1079
|
-
//#region packages/event-http/src/composables/header-accept.ts
|
|
1080
|
-
/** Provides helpers to check the request's Accept header for supported MIME types. */
|
|
1081
|
-
function useAccept() {
|
|
1082
|
-
const { store } = useHttpContext();
|
|
1083
|
-
const { accept } = useHeaders();
|
|
1084
|
-
const accepts = (mime) => {
|
|
1085
|
-
const { set, get, has } = store("accept");
|
|
1086
|
-
if (!has(mime)) return set(mime, !!(accept && (accept === "*/*" || accept.includes(mime))));
|
|
1087
|
-
return get(mime);
|
|
1088
|
-
};
|
|
1089
|
-
return {
|
|
1090
|
-
accept,
|
|
1091
|
-
accepts,
|
|
1092
|
-
acceptsJson: () => accepts("application/json"),
|
|
1093
|
-
acceptsXml: () => accepts("application/xml"),
|
|
1094
|
-
acceptsText: () => accepts("text/plain"),
|
|
1095
|
-
acceptsHtml: () => accepts("text/html")
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
//#endregion
|
|
1100
|
-
//#region packages/event-http/src/composables/header-authorization.ts
|
|
1101
|
-
/**
|
|
1102
|
-
* Provides parsed access to the Authorization header (type, credentials, Basic decoding).
|
|
1103
|
-
* @example
|
|
1104
|
-
* ```ts
|
|
1105
|
-
* const { isBearer, authRawCredentials, basicCredentials } = useAuthorization()
|
|
1106
|
-
* if (isBearer()) { const token = authRawCredentials() }
|
|
1107
|
-
* ```
|
|
1108
|
-
*/
|
|
1109
|
-
function useAuthorization() {
|
|
1110
|
-
const { store } = useHttpContext();
|
|
1111
|
-
const { authorization } = useHeaders();
|
|
1112
|
-
const { init } = store("authorization");
|
|
1113
|
-
const authType = () => init("type", () => {
|
|
1114
|
-
if (authorization) {
|
|
1115
|
-
const space = authorization.indexOf(" ");
|
|
1116
|
-
return authorization.slice(0, space);
|
|
1117
|
-
}
|
|
1118
|
-
return null;
|
|
1119
|
-
});
|
|
1120
|
-
const authRawCredentials = () => init("credentials", () => {
|
|
1121
|
-
if (authorization) {
|
|
1122
|
-
const space = authorization.indexOf(" ");
|
|
1123
|
-
return authorization.slice(space + 1);
|
|
1124
|
-
}
|
|
1125
|
-
return null;
|
|
1126
|
-
});
|
|
1127
|
-
return {
|
|
1128
|
-
authorization,
|
|
1129
|
-
authType,
|
|
1130
|
-
authRawCredentials,
|
|
1131
|
-
isBasic: () => authType()?.toLocaleLowerCase() === "basic",
|
|
1132
|
-
isBearer: () => authType()?.toLocaleLowerCase() === "bearer",
|
|
1133
|
-
basicCredentials: () => init("basicCredentials", () => {
|
|
1134
|
-
if (authorization) {
|
|
1135
|
-
const type = authType();
|
|
1136
|
-
if (type?.toLocaleLowerCase() === "basic") {
|
|
1137
|
-
const creds = buffer.Buffer.from(authRawCredentials() || "", "base64").toString("ascii");
|
|
1138
|
-
const [username, password] = creds.split(":");
|
|
1139
|
-
return {
|
|
1140
|
-
username,
|
|
1141
|
-
password
|
|
1142
|
-
};
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
return null;
|
|
1146
|
-
})
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1357
|
+
.error-message {
|
|
1358
|
+
font-size: 1rem;
|
|
1359
|
+
margin-bottom: 25px;
|
|
1360
|
+
}
|
|
1361
|
+
.json-details-container {
|
|
1362
|
+
padding: 15px;
|
|
1363
|
+
font-size: 0.8rem;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
</style>
|
|
1367
|
+
</head>
|
|
1368
|
+
<body>
|
|
1369
|
+
<div class="error-container">
|
|
1370
|
+
<div id="statusCode" class="status-code">${icon} ${statusCode}</div>
|
|
1149
1371
|
|
|
1150
|
-
|
|
1151
|
-
//#region packages/event-http/src/utils/cache-control.ts
|
|
1152
|
-
function renderCacheControl(data) {
|
|
1153
|
-
let attrs = "";
|
|
1154
|
-
for (const [a, v] of Object.entries(data)) {
|
|
1155
|
-
if (v === void 0) continue;
|
|
1156
|
-
const func = cacheControlFunc[a];
|
|
1157
|
-
if (typeof func === "function") {
|
|
1158
|
-
const val = func(v);
|
|
1159
|
-
if (val) attrs += attrs ? `, ${val}` : val;
|
|
1160
|
-
} else throw new TypeError(`Unknown Cache-Control attribute ${a}`);
|
|
1161
|
-
}
|
|
1162
|
-
return attrs;
|
|
1163
|
-
}
|
|
1164
|
-
const cacheControlFunc = {
|
|
1165
|
-
mustRevalidate: (v) => v ? "must-revalidate" : "",
|
|
1166
|
-
noCache: (v) => v ? typeof v === "string" ? `no-cache="${v}"` : "no-cache" : "",
|
|
1167
|
-
noStore: (v) => v ? "no-store" : "",
|
|
1168
|
-
noTransform: (v) => v ? "no-transform" : "",
|
|
1169
|
-
public: (v) => v ? "public" : "",
|
|
1170
|
-
private: (v) => v ? typeof v === "string" ? `private="${v}"` : "private" : "",
|
|
1171
|
-
proxyRevalidate: (v) => v ? "proxy-revalidate" : "",
|
|
1172
|
-
maxAge: (v) => `max-age=${convertTime(v, "s").toString()}`,
|
|
1173
|
-
sMaxage: (v) => `s-maxage=${convertTime(v, "s").toString()}`
|
|
1174
|
-
};
|
|
1372
|
+
<div id="statusText" class="status-text">${statusMessage}</div>
|
|
1175
1373
|
|
|
1176
|
-
|
|
1177
|
-
//#region packages/event-http/src/composables/header-set-cache-control.ts
|
|
1178
|
-
const renderAge = (v) => convertTime(v, "s").toString();
|
|
1179
|
-
const renderExpires = (v) => typeof v === "string" || typeof v === "number" ? new Date(v).toUTCString() : v.toUTCString();
|
|
1180
|
-
const renderPragmaNoCache = (v) => v ? "no-cache" : "";
|
|
1181
|
-
/** Provides helpers to set cache-related response headers (Cache-Control, Expires, Age, Pragma). */
|
|
1182
|
-
function useSetCacheControl() {
|
|
1183
|
-
const { setHeader } = useSetHeaders();
|
|
1184
|
-
const setAge = (value) => {
|
|
1185
|
-
setHeader("age", renderAge(value));
|
|
1186
|
-
};
|
|
1187
|
-
const setExpires = (value) => {
|
|
1188
|
-
setHeader("expires", renderExpires(value));
|
|
1189
|
-
};
|
|
1190
|
-
const setPragmaNoCache = (value = true) => {
|
|
1191
|
-
setHeader("pragma", renderPragmaNoCache(value));
|
|
1192
|
-
};
|
|
1193
|
-
const setCacheControl = (data) => {
|
|
1194
|
-
setHeader("cache-control", renderCacheControl(data));
|
|
1195
|
-
};
|
|
1196
|
-
return {
|
|
1197
|
-
setExpires,
|
|
1198
|
-
setAge,
|
|
1199
|
-
setPragmaNoCache,
|
|
1200
|
-
setCacheControl
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1374
|
+
<p id="errorMessage" class="error-message">${message}</p>
|
|
1203
1375
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
function status(code) {
|
|
1221
|
-
return statusCode.value = code ? code : statusCode.value;
|
|
1222
|
-
}
|
|
1223
|
-
const rawResponse = (options) => {
|
|
1224
|
-
if (!options || !options.passthrough) responded.value = true;
|
|
1225
|
-
return res;
|
|
1226
|
-
};
|
|
1227
|
-
return {
|
|
1228
|
-
rawResponse,
|
|
1229
|
-
hasResponded: () => responded.value || !res.writable || res.writableEnded,
|
|
1230
|
-
status: (0, __wooksjs_event_core.attachHook)(status, {
|
|
1231
|
-
get: () => statusCode.value,
|
|
1232
|
-
set: (code) => statusCode.value = code
|
|
1233
|
-
})
|
|
1234
|
-
};
|
|
1235
|
-
}
|
|
1236
|
-
/** Returns a hookable accessor for the response status code. */
|
|
1237
|
-
function useStatus() {
|
|
1238
|
-
const { store } = useHttpContext();
|
|
1239
|
-
return store("status").hook("code");
|
|
1376
|
+
<!-- prettier-ignore -->
|
|
1377
|
+
<div class="json-details-container"style="display: ${details ? "block" : "none"};">
|
|
1378
|
+
<p class="text-sm">Technical Details:</p>
|
|
1379
|
+
<pre><code id="jsonDetails">${details}</code></pre>
|
|
1380
|
+
</div>
|
|
1381
|
+
<div class="footer">
|
|
1382
|
+
Powered by
|
|
1383
|
+
<a href="${link}" target="_blank">
|
|
1384
|
+
<img height="20" alt="%{poweredBy}" src="${image}" />
|
|
1385
|
+
</a>
|
|
1386
|
+
v${version}
|
|
1387
|
+
</div>
|
|
1388
|
+
</div>
|
|
1389
|
+
</body>
|
|
1390
|
+
</html>
|
|
1391
|
+
`;
|
|
1240
1392
|
}
|
|
1241
1393
|
|
|
1242
1394
|
//#endregion
|
|
1243
|
-
//#region packages/event-http/src/
|
|
1244
|
-
|
|
1245
|
-
"
|
|
1246
|
-
"
|
|
1247
|
-
"
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
} else {
|
|
1256
|
-
if (ILLEGAL_KEYS.has(key)) throw new HttpError(400, `Illegal key name "${key}"`);
|
|
1257
|
-
if (key in json) throw new HttpError(400, `Duplicate key "${key}"`);
|
|
1258
|
-
json[key] = value;
|
|
1259
|
-
}
|
|
1260
|
-
return json;
|
|
1261
|
-
}
|
|
1395
|
+
//#region packages/event-http/src/response/wooks-http-response.ts
|
|
1396
|
+
let framework = {
|
|
1397
|
+
version: "0.6.6",
|
|
1398
|
+
poweredBy: "wooksjs",
|
|
1399
|
+
link: "https://wooks.moost.org/",
|
|
1400
|
+
image: "https://wooks.moost.org/wooks-full-logo.png"
|
|
1401
|
+
};
|
|
1402
|
+
const icons = {
|
|
1403
|
+
401: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
|
|
1404
|
+
403: typeof _403_tl_default === "function" ? _403_tl_default({}) : "",
|
|
1405
|
+
404: typeof _404_tl_default === "function" ? _404_tl_default({}) : "",
|
|
1406
|
+
500: typeof _500_tl_default === "function" ? _500_tl_default({}) : ""
|
|
1262
1407
|
};
|
|
1263
|
-
function isArrayParam(name) {
|
|
1264
|
-
return name.endsWith("[]");
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
//#endregion
|
|
1268
|
-
//#region packages/event-http/src/composables/search-params.ts
|
|
1269
1408
|
/**
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
* const page = urlSearchParams().get('page')
|
|
1275
|
-
* ```
|
|
1409
|
+
* Default `HttpResponse` subclass used by `createHttpApp`.
|
|
1410
|
+
*
|
|
1411
|
+
* Overrides error rendering to produce content-negotiated responses (JSON, HTML, or plain text)
|
|
1412
|
+
* based on the request's `Accept` header. HTML error pages include SVG icons and framework branding.
|
|
1276
1413
|
*/
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
const rawSearchParams = () => init("raw", () => {
|
|
1282
|
-
const i = url$1.indexOf("?");
|
|
1283
|
-
return i >= 0 ? url$1.slice(i) : "";
|
|
1284
|
-
});
|
|
1285
|
-
const urlSearchParams = () => init("urlSearchParams", () => new WooksURLSearchParams(rawSearchParams()));
|
|
1286
|
-
return {
|
|
1287
|
-
rawSearchParams,
|
|
1288
|
-
urlSearchParams,
|
|
1289
|
-
jsonSearchParams: () => urlSearchParams().toJson()
|
|
1290
|
-
};
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
//#endregion
|
|
1294
|
-
//#region packages/event-http/src/response/core.ts
|
|
1295
|
-
const defaultStatus = {
|
|
1296
|
-
GET: EHttpStatusCode.OK,
|
|
1297
|
-
POST: EHttpStatusCode.Created,
|
|
1298
|
-
PUT: EHttpStatusCode.Created,
|
|
1299
|
-
PATCH: EHttpStatusCode.Accepted,
|
|
1300
|
-
DELETE: EHttpStatusCode.Accepted
|
|
1301
|
-
};
|
|
1302
|
-
const baseRenderer = new BaseHttpResponseRenderer();
|
|
1303
|
-
var BaseHttpResponse = class {
|
|
1304
|
-
constructor(renderer = baseRenderer) {
|
|
1305
|
-
this.renderer = renderer;
|
|
1306
|
-
}
|
|
1307
|
-
_status = 0;
|
|
1308
|
-
_body;
|
|
1309
|
-
_headers = {};
|
|
1310
|
-
get status() {
|
|
1311
|
-
return this._status;
|
|
1312
|
-
}
|
|
1313
|
-
set status(value) {
|
|
1314
|
-
this._status = value;
|
|
1315
|
-
}
|
|
1316
|
-
get body() {
|
|
1317
|
-
return this._body;
|
|
1318
|
-
}
|
|
1319
|
-
set body(value) {
|
|
1320
|
-
this._body = value;
|
|
1321
|
-
}
|
|
1322
|
-
setStatus(value) {
|
|
1323
|
-
this.status = value;
|
|
1324
|
-
return this;
|
|
1325
|
-
}
|
|
1326
|
-
setBody(value) {
|
|
1327
|
-
this.body = value;
|
|
1328
|
-
return this;
|
|
1329
|
-
}
|
|
1330
|
-
getContentType() {
|
|
1331
|
-
return this._headers["content-type"];
|
|
1332
|
-
}
|
|
1333
|
-
setContentType(value) {
|
|
1334
|
-
this._headers["content-type"] = value;
|
|
1335
|
-
return this;
|
|
1336
|
-
}
|
|
1337
|
-
enableCors(origin = "*") {
|
|
1338
|
-
this._headers["Access-Control-Allow-Origin"] = origin;
|
|
1339
|
-
return this;
|
|
1340
|
-
}
|
|
1341
|
-
setCookie(name, value, attrs) {
|
|
1342
|
-
const cookies = this._headers["set-cookie"] = this._headers["set-cookie"] || [];
|
|
1343
|
-
cookies.push(renderCookie(name, {
|
|
1344
|
-
value,
|
|
1345
|
-
attrs: attrs || {}
|
|
1346
|
-
}));
|
|
1347
|
-
return this;
|
|
1348
|
-
}
|
|
1349
|
-
setCacheControl(data) {
|
|
1350
|
-
this.setHeader("cache-control", renderCacheControl(data));
|
|
1351
|
-
}
|
|
1352
|
-
setCookieRaw(rawValue) {
|
|
1353
|
-
const cookies = this._headers["set-cookie"] = this._headers["set-cookie"] || [];
|
|
1354
|
-
cookies.push(rawValue);
|
|
1355
|
-
return this;
|
|
1356
|
-
}
|
|
1357
|
-
header(name, value) {
|
|
1358
|
-
this._headers[name] = value;
|
|
1359
|
-
return this;
|
|
1360
|
-
}
|
|
1361
|
-
setHeader(name, value) {
|
|
1362
|
-
return this.header(name, value);
|
|
1363
|
-
}
|
|
1364
|
-
getHeader(name) {
|
|
1365
|
-
return this._headers[name];
|
|
1366
|
-
}
|
|
1367
|
-
mergeHeaders() {
|
|
1368
|
-
const { headers } = useSetHeaders();
|
|
1369
|
-
const { cookies, removeCookie } = useSetCookies();
|
|
1370
|
-
const newCookies = this._headers["set-cookie"] || [];
|
|
1371
|
-
for (const cookie of newCookies) removeCookie(cookie.slice(0, cookie.indexOf("=")));
|
|
1372
|
-
const composableHeaders = headers();
|
|
1373
|
-
for (const key in composableHeaders) if (!(key in this._headers)) this._headers[key] = composableHeaders[key];
|
|
1374
|
-
const renderedCookies = cookies();
|
|
1375
|
-
if (newCookies.length > 0 || renderedCookies.length > 0) this._headers["set-cookie"] = newCookies.length > 0 && renderedCookies.length > 0 ? [...newCookies, ...renderedCookies] : newCookies.length > 0 ? newCookies : renderedCookies;
|
|
1376
|
-
return this;
|
|
1377
|
-
}
|
|
1378
|
-
mergeStatus(renderedBody) {
|
|
1379
|
-
this.status = this.status || useResponse().status();
|
|
1380
|
-
if (!this.status) {
|
|
1381
|
-
const { method } = useRequest();
|
|
1382
|
-
this.status = renderedBody ? defaultStatus[method] || EHttpStatusCode.OK : EHttpStatusCode.NoContent;
|
|
1383
|
-
}
|
|
1384
|
-
return this;
|
|
1414
|
+
var WooksHttpResponse = class extends HttpResponse {
|
|
1415
|
+
/** Registers framework metadata (name, version, link, logo) used in HTML error pages. */
|
|
1416
|
+
static registerFramework(opts) {
|
|
1417
|
+
framework = opts;
|
|
1385
1418
|
}
|
|
1386
|
-
|
|
1387
|
-
this.
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
if (hasResponded()) this.panic("The response was already sent.", logger);
|
|
1399
|
-
this.mergeHeaders();
|
|
1400
|
-
const res = rawResponse();
|
|
1401
|
-
if (this.body instanceof stream.Readable) {
|
|
1402
|
-
const stream$1 = this.body;
|
|
1403
|
-
this.mergeStatus(true);
|
|
1404
|
-
res.writeHead(this.status, { ...this._headers });
|
|
1405
|
-
rawRequest.once("close", () => {
|
|
1406
|
-
stream$1.destroy();
|
|
1407
|
-
});
|
|
1408
|
-
if (method === "HEAD") {
|
|
1409
|
-
stream$1.destroy();
|
|
1410
|
-
res.end();
|
|
1411
|
-
} else return new Promise((resolve, reject) => {
|
|
1412
|
-
stream$1.on("error", (e) => {
|
|
1413
|
-
stream$1.destroy();
|
|
1414
|
-
res.end();
|
|
1415
|
-
reject(e);
|
|
1416
|
-
});
|
|
1417
|
-
stream$1.on("close", () => {
|
|
1418
|
-
stream$1.destroy();
|
|
1419
|
-
resolve(void 0);
|
|
1420
|
-
});
|
|
1421
|
-
stream$1.pipe(res);
|
|
1422
|
-
});
|
|
1423
|
-
} else if (globalThis.Response && this.body instanceof Response) {
|
|
1424
|
-
this.mergeFetchStatus(this.body.status);
|
|
1425
|
-
const additionalHeaders = {};
|
|
1426
|
-
const fetchContentLength = this.body.headers.get("content-length");
|
|
1427
|
-
if (fetchContentLength) additionalHeaders["content-length"] = fetchContentLength;
|
|
1428
|
-
const fetchContentType = this.body.headers.get("content-type");
|
|
1429
|
-
if (fetchContentType) additionalHeaders["content-type"] = fetchContentType;
|
|
1430
|
-
res.writeHead(this.status, {
|
|
1431
|
-
...this._headers,
|
|
1432
|
-
...additionalHeaders
|
|
1433
|
-
});
|
|
1434
|
-
if (method === "HEAD") res.end();
|
|
1435
|
-
else await respondWithFetch(this.body.body, res, logger);
|
|
1419
|
+
renderError(data, ctx) {
|
|
1420
|
+
this._status = data.statusCode || 500;
|
|
1421
|
+
const { accepts } = useAccept(ctx);
|
|
1422
|
+
if (accepts("json")) {
|
|
1423
|
+
this._headers["content-type"] = "application/json";
|
|
1424
|
+
this._body = JSON.stringify(data);
|
|
1425
|
+
} else if (accepts("html")) {
|
|
1426
|
+
this._headers["content-type"] = "text/html";
|
|
1427
|
+
this._body = renderErrorHtml(data);
|
|
1428
|
+
} else if (accepts("text")) {
|
|
1429
|
+
this._headers["content-type"] = "text/plain";
|
|
1430
|
+
this._body = renderErrorText(data);
|
|
1436
1431
|
} else {
|
|
1437
|
-
|
|
1438
|
-
this.
|
|
1439
|
-
const contentLength = typeof renderedBody === "string" ? Buffer.byteLength(renderedBody) : renderedBody.byteLength;
|
|
1440
|
-
return new Promise((resolve) => {
|
|
1441
|
-
res.writeHead(this.status, {
|
|
1442
|
-
"content-length": contentLength,
|
|
1443
|
-
...this._headers
|
|
1444
|
-
}).end(method === "HEAD" ? "" : renderedBody, resolve);
|
|
1445
|
-
});
|
|
1432
|
+
this._headers["content-type"] = "application/json";
|
|
1433
|
+
this._body = JSON.stringify(data);
|
|
1446
1434
|
}
|
|
1447
1435
|
}
|
|
1448
1436
|
};
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1437
|
+
function renderErrorHtml(data) {
|
|
1438
|
+
const hasDetails = Object.keys(data).length > 3;
|
|
1439
|
+
const icon = data.statusCode >= 500 ? icons[500] : icons[data.statusCode] || "";
|
|
1440
|
+
return typeof error_tl_default === "function" ? error_tl_default({
|
|
1441
|
+
icon,
|
|
1442
|
+
statusCode: data.statusCode,
|
|
1443
|
+
statusMessage: httpStatusCodes[data.statusCode],
|
|
1444
|
+
message: data.message,
|
|
1445
|
+
details: hasDetails ? JSON.stringify(data, null, " ") : "",
|
|
1446
|
+
version: framework.version,
|
|
1447
|
+
poweredBy: framework.poweredBy,
|
|
1448
|
+
link: framework.link,
|
|
1449
|
+
image: framework.image
|
|
1450
|
+
}) : JSON.stringify(data, null, " ");
|
|
1456
1451
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
r.setBody(httpError.body);
|
|
1470
|
-
return r;
|
|
1471
|
-
} else if (data instanceof BaseHttpResponse) return data;
|
|
1472
|
-
else return new BaseHttpResponse(renderer).setBody(data);
|
|
1473
|
-
}
|
|
1474
|
-
return {
|
|
1475
|
-
createResponse,
|
|
1476
|
-
respond: (data) => createResponse(data)?.respond()
|
|
1477
|
-
};
|
|
1452
|
+
function renderErrorText(data) {
|
|
1453
|
+
const keys = Object.keys(data).filter((key) => ![
|
|
1454
|
+
"statusCode",
|
|
1455
|
+
"error",
|
|
1456
|
+
"message"
|
|
1457
|
+
].includes(key));
|
|
1458
|
+
return `${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}\n\n${keys.length > 0 ? JSON.stringify({
|
|
1459
|
+
...data,
|
|
1460
|
+
statusCode: void 0,
|
|
1461
|
+
message: void 0,
|
|
1462
|
+
error: void 0
|
|
1463
|
+
}, null, " ") : ""}`;
|
|
1478
1464
|
}
|
|
1479
1465
|
|
|
1480
1466
|
//#endregion
|
|
@@ -1482,12 +1468,14 @@ function createWooksResponder(renderer = new BaseHttpResponseRenderer(), errorRe
|
|
|
1482
1468
|
/** HTTP adapter for Wooks that provides route registration, server lifecycle, and request handling. */
|
|
1483
1469
|
var WooksHttp = class extends wooks.WooksAdapterBase {
|
|
1484
1470
|
logger;
|
|
1485
|
-
|
|
1471
|
+
ResponseClass;
|
|
1472
|
+
eventContextOptions;
|
|
1486
1473
|
constructor(opts, wooks$1) {
|
|
1487
1474
|
super(wooks$1, opts?.logger, opts?.router);
|
|
1488
1475
|
this.opts = opts;
|
|
1489
1476
|
this.logger = opts?.logger || this.getLogger(`[96m[wooks-http]`);
|
|
1490
|
-
this.
|
|
1477
|
+
this.ResponseClass = opts?.responseClass ?? WooksHttpResponse;
|
|
1478
|
+
this.eventContextOptions = this.getEventContextOptions();
|
|
1491
1479
|
}
|
|
1492
1480
|
/** Registers a handler for all HTTP methods on the given path. */
|
|
1493
1481
|
all(path, handler) {
|
|
@@ -1521,9 +1509,22 @@ var WooksHttp = class extends wooks.WooksAdapterBase {
|
|
|
1521
1509
|
options(path, handler) {
|
|
1522
1510
|
return this.on("OPTIONS", path, handler);
|
|
1523
1511
|
}
|
|
1512
|
+
/** Registers an UPGRADE route handler for WebSocket upgrade requests. */
|
|
1513
|
+
upgrade(path, handler) {
|
|
1514
|
+
return this.on("UPGRADE", path, handler);
|
|
1515
|
+
}
|
|
1516
|
+
wsHandler;
|
|
1517
|
+
/** Register a WebSocket upgrade handler that implements the WooksUpgradeHandler contract. */
|
|
1518
|
+
ws(handler) {
|
|
1519
|
+
this.wsHandler = handler;
|
|
1520
|
+
}
|
|
1524
1521
|
server;
|
|
1525
1522
|
async listen(port, hostname, backlog, listeningListener) {
|
|
1526
1523
|
const server = this.server = http.default.createServer(this.getServerCb());
|
|
1524
|
+
if (this.wsHandler) {
|
|
1525
|
+
const upgradeCb = this.getUpgradeCb();
|
|
1526
|
+
server.on("upgrade", upgradeCb);
|
|
1527
|
+
}
|
|
1527
1528
|
return new Promise((resolve, reject) => {
|
|
1528
1529
|
server.once("listening", resolve);
|
|
1529
1530
|
server.once("error", reject);
|
|
@@ -1573,11 +1574,14 @@ var WooksHttp = class extends wooks.WooksAdapterBase {
|
|
|
1573
1574
|
attachServer(server) {
|
|
1574
1575
|
this.server = server;
|
|
1575
1576
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1577
|
+
respond(data, response, ctx) {
|
|
1578
|
+
if (response.responded) return;
|
|
1579
|
+
if (data instanceof Error) {
|
|
1580
|
+
const httpError = data instanceof HttpError ? data : new HttpError(500, data.message);
|
|
1581
|
+
return response.sendError(httpError, ctx);
|
|
1582
|
+
}
|
|
1583
|
+
if (data !== response) response.body = data;
|
|
1584
|
+
return response.send();
|
|
1581
1585
|
}
|
|
1582
1586
|
/**
|
|
1583
1587
|
* Returns server callback function
|
|
@@ -1592,47 +1596,131 @@ var WooksHttp = class extends wooks.WooksAdapterBase {
|
|
|
1592
1596
|
* ```
|
|
1593
1597
|
*/
|
|
1594
1598
|
getServerCb() {
|
|
1599
|
+
const ctxOptions = this.eventContextOptions;
|
|
1600
|
+
const RequestLimits = this.opts?.requestLimits;
|
|
1601
|
+
const notFoundHandler = this.opts?.onNotFound;
|
|
1602
|
+
const defaultHeaders = this.opts?.defaultHeaders;
|
|
1595
1603
|
return (req, res) => {
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1598
|
-
res,
|
|
1599
|
-
requestLimits: this.opts?.requestLimits
|
|
1600
|
-
}, this._cachedEventOptions);
|
|
1604
|
+
const ctx = new __wooksjs_event_core.EventContext(ctxOptions);
|
|
1605
|
+
const response = new this.ResponseClass(res, req, ctx.logger, defaultHeaders);
|
|
1601
1606
|
const method = req.method || "";
|
|
1602
1607
|
const url$1 = req.url || "";
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1608
|
+
(0, __wooksjs_event_core.run)(ctx, () => {
|
|
1609
|
+
ctx.seed(httpKind, {
|
|
1610
|
+
req,
|
|
1611
|
+
response,
|
|
1612
|
+
requestLimits: RequestLimits
|
|
1613
|
+
});
|
|
1614
|
+
const handlers = this.wooks.lookupHandlers(method, url$1, ctx);
|
|
1615
|
+
if (handlers || notFoundHandler) {
|
|
1616
|
+
const result = this.processHandlers(handlers || [notFoundHandler], ctx, response);
|
|
1617
|
+
if (result !== null && result !== void 0 && typeof result.then === "function") result.catch((error) => {
|
|
1618
|
+
this.logger.error("Internal error, please report", error);
|
|
1619
|
+
this.respond(error, response, ctx);
|
|
1620
|
+
});
|
|
1621
|
+
} else {
|
|
1614
1622
|
this.logger.debug(`404 Not found (${method})${url$1}`);
|
|
1615
1623
|
const error = new HttpError(404);
|
|
1616
|
-
|
|
1617
|
-
return error;
|
|
1624
|
+
this.respond(error, response, ctx);
|
|
1618
1625
|
}
|
|
1619
1626
|
});
|
|
1620
1627
|
};
|
|
1621
1628
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1629
|
+
/**
|
|
1630
|
+
* Returns upgrade callback function for the HTTP server's 'upgrade' event.
|
|
1631
|
+
* Creates an HTTP context, seeds it with upgrade data, and routes as method 'UPGRADE'.
|
|
1632
|
+
*/
|
|
1633
|
+
getUpgradeCb() {
|
|
1634
|
+
const ctxOptions = this.eventContextOptions;
|
|
1635
|
+
const requestLimits = this.opts?.requestLimits;
|
|
1636
|
+
const wsHandler = this.wsHandler;
|
|
1637
|
+
return (req, socket, head) => {
|
|
1638
|
+
if (!wsHandler) {
|
|
1639
|
+
socket.destroy();
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
const ctx = new __wooksjs_event_core.EventContext(ctxOptions);
|
|
1643
|
+
const url$1 = req.url || "";
|
|
1644
|
+
(0, __wooksjs_event_core.run)(ctx, () => {
|
|
1645
|
+
ctx.seed(httpKind, {
|
|
1646
|
+
req,
|
|
1647
|
+
response: void 0,
|
|
1648
|
+
requestLimits
|
|
1649
|
+
});
|
|
1650
|
+
ctx.set(wsHandler.reqKey, req);
|
|
1651
|
+
ctx.set(wsHandler.socketKey, socket);
|
|
1652
|
+
ctx.set(wsHandler.headKey, head);
|
|
1653
|
+
const handlers = this.wooks.lookupHandlers("UPGRADE", url$1, ctx);
|
|
1654
|
+
if (handlers) this.processUpgradeHandlers(handlers, ctx, socket);
|
|
1655
|
+
else wsHandler.handleUpgrade(req, socket, head);
|
|
1656
|
+
});
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
processUpgradeHandlers(handlers, ctx, socket) {
|
|
1660
|
+
for (let i = 0; i < handlers.length; i++) {
|
|
1661
|
+
const handler = handlers[i];
|
|
1662
|
+
const isLastHandler = i === handlers.length - 1;
|
|
1663
|
+
try {
|
|
1664
|
+
const result = handler();
|
|
1665
|
+
if (result !== null && result !== void 0 && typeof result.then === "function") {
|
|
1666
|
+
result.catch((error) => {
|
|
1667
|
+
this.logger.error(`Upgrade handler error: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
|
|
1668
|
+
socket.destroy();
|
|
1669
|
+
});
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
return;
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
if (!(error instanceof HttpError)) this.logger.error(`Upgrade handler error: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
|
|
1675
|
+
if (isLastHandler) {
|
|
1676
|
+
socket.destroy();
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
processHandlers(handlers, ctx, response) {
|
|
1624
1683
|
for (let i = 0; i < handlers.length; i++) {
|
|
1625
1684
|
const handler = handlers[i];
|
|
1626
1685
|
const isLastHandler = i === handlers.length - 1;
|
|
1627
1686
|
try {
|
|
1628
|
-
const
|
|
1629
|
-
|
|
1630
|
-
|
|
1687
|
+
const result = handler();
|
|
1688
|
+
if (result !== null && result !== void 0 && typeof result.then === "function") return this.processAsyncResult(result, handlers, i, ctx, response);
|
|
1689
|
+
this.respond(result, response, ctx);
|
|
1690
|
+
return;
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
if (!(error instanceof HttpError)) this.logger.error(`Uncaught route handler exception: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
|
|
1693
|
+
if (isLastHandler) {
|
|
1694
|
+
this.respond(error, response, ctx);
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
async processAsyncResult(promise, handlers, startIndex, ctx, response) {
|
|
1701
|
+
try {
|
|
1702
|
+
const result = await promise;
|
|
1703
|
+
await this.respond(result, response, ctx);
|
|
1704
|
+
return result;
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
const isLastHandler = startIndex === handlers.length - 1;
|
|
1707
|
+
if (!(error instanceof HttpError)) this.logger.error(`Uncaught route handler exception: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
|
|
1708
|
+
if (isLastHandler) {
|
|
1709
|
+
await this.respond(error, response, ctx);
|
|
1710
|
+
return error;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
for (let i = startIndex + 1; i < handlers.length; i++) {
|
|
1714
|
+
const handler = handlers[i];
|
|
1715
|
+
const isLastHandler = i === handlers.length - 1;
|
|
1716
|
+
try {
|
|
1717
|
+
const result = await handler();
|
|
1718
|
+
await this.respond(result, response, ctx);
|
|
1631
1719
|
return result;
|
|
1632
1720
|
} catch (error) {
|
|
1633
|
-
if (error instanceof HttpError)
|
|
1721
|
+
if (!(error instanceof HttpError)) this.logger.error(`Uncaught route handler exception: ${ctx.get(httpKind.keys.req)?.url || ""}`, error);
|
|
1634
1722
|
if (isLastHandler) {
|
|
1635
|
-
await this.respond(error);
|
|
1723
|
+
await this.respond(error, response, ctx);
|
|
1636
1724
|
return error;
|
|
1637
1725
|
}
|
|
1638
1726
|
}
|
|
@@ -1653,30 +1741,128 @@ function createHttpApp(opts, wooks$1) {
|
|
|
1653
1741
|
}
|
|
1654
1742
|
|
|
1655
1743
|
//#endregion
|
|
1656
|
-
|
|
1657
|
-
|
|
1744
|
+
//#region packages/event-http/src/utils/security-headers.ts
|
|
1745
|
+
const HEADER_MAP = [
|
|
1746
|
+
[
|
|
1747
|
+
"contentSecurityPolicy",
|
|
1748
|
+
"content-security-policy",
|
|
1749
|
+
"default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'"
|
|
1750
|
+
],
|
|
1751
|
+
[
|
|
1752
|
+
"crossOriginOpenerPolicy",
|
|
1753
|
+
"cross-origin-opener-policy",
|
|
1754
|
+
"same-origin"
|
|
1755
|
+
],
|
|
1756
|
+
[
|
|
1757
|
+
"crossOriginResourcePolicy",
|
|
1758
|
+
"cross-origin-resource-policy",
|
|
1759
|
+
"same-origin"
|
|
1760
|
+
],
|
|
1761
|
+
[
|
|
1762
|
+
"referrerPolicy",
|
|
1763
|
+
"referrer-policy",
|
|
1764
|
+
"no-referrer"
|
|
1765
|
+
],
|
|
1766
|
+
[
|
|
1767
|
+
"strictTransportSecurity",
|
|
1768
|
+
"strict-transport-security",
|
|
1769
|
+
void 0
|
|
1770
|
+
],
|
|
1771
|
+
[
|
|
1772
|
+
"xContentTypeOptions",
|
|
1773
|
+
"x-content-type-options",
|
|
1774
|
+
"nosniff"
|
|
1775
|
+
],
|
|
1776
|
+
[
|
|
1777
|
+
"xFrameOptions",
|
|
1778
|
+
"x-frame-options",
|
|
1779
|
+
"SAMEORIGIN"
|
|
1780
|
+
]
|
|
1781
|
+
];
|
|
1782
|
+
/**
|
|
1783
|
+
* Returns a record of recommended HTTP security headers.
|
|
1784
|
+
*
|
|
1785
|
+
* Each option accepts a `string` (override value) or `false` (disable).
|
|
1786
|
+
* Omitting an option uses the default value.
|
|
1787
|
+
*
|
|
1788
|
+
* `strictTransportSecurity` is opt-in only (no default) — HSTS is dangerous if not on HTTPS.
|
|
1789
|
+
*/
|
|
1790
|
+
function securityHeaders(opts) {
|
|
1791
|
+
const result = {};
|
|
1792
|
+
for (const [optKey, headerName, defaultValue] of HEADER_MAP) {
|
|
1793
|
+
const value = opts?.[optKey];
|
|
1794
|
+
if (value === false) continue;
|
|
1795
|
+
if (typeof value === "string") result[headerName] = value;
|
|
1796
|
+
else if (defaultValue !== void 0) result[headerName] = defaultValue;
|
|
1797
|
+
}
|
|
1798
|
+
return result;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
//#endregion
|
|
1802
|
+
//#region packages/event-http/src/testing.ts
|
|
1803
|
+
/**
|
|
1804
|
+
* Creates a fully initialized HTTP event context for testing.
|
|
1805
|
+
*
|
|
1806
|
+
* Sets up an `EventContext` with a fake `IncomingMessage`, `HttpResponse`, route params,
|
|
1807
|
+
* and optional pre-seeded body. Returns a runner function that executes callbacks inside the context scope.
|
|
1808
|
+
*
|
|
1809
|
+
* @example
|
|
1810
|
+
* ```ts
|
|
1811
|
+
* const run = prepareTestHttpContext({ url: '/users/42', params: { id: '42' } })
|
|
1812
|
+
* run(() => {
|
|
1813
|
+
* const { params } = useRouteParams()
|
|
1814
|
+
* expect(params.id).toBe('42')
|
|
1815
|
+
* })
|
|
1816
|
+
* ```
|
|
1817
|
+
*/
|
|
1818
|
+
function prepareTestHttpContext(options) {
|
|
1819
|
+
const req = new http.IncomingMessage(new net.Socket({}));
|
|
1820
|
+
req.method = options.method || "GET";
|
|
1821
|
+
req.headers = options.headers || {};
|
|
1822
|
+
req.url = options.url;
|
|
1823
|
+
const res = new http.ServerResponse(req);
|
|
1824
|
+
const response = new HttpResponse(res, req, console, options.defaultHeaders);
|
|
1825
|
+
const ctx = new __wooksjs_event_core.EventContext({ logger: console });
|
|
1826
|
+
ctx.seed(httpKind, {
|
|
1827
|
+
req,
|
|
1828
|
+
response,
|
|
1829
|
+
requestLimits: options.requestLimits
|
|
1830
|
+
});
|
|
1831
|
+
if (options.params) ctx.set(__wooksjs_event_core.routeParamsKey, options.params);
|
|
1832
|
+
if (options.rawBody !== void 0) {
|
|
1833
|
+
const buf = buffer.Buffer.isBuffer(options.rawBody) ? options.rawBody : buffer.Buffer.from(options.rawBody);
|
|
1834
|
+
ctx.set(rawBodySlot, Promise.resolve(buf));
|
|
1835
|
+
}
|
|
1836
|
+
return (cb) => (0, __wooksjs_event_core.run)(ctx, cb);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
//#endregion
|
|
1658
1840
|
exports.DEFAULT_LIMITS = DEFAULT_LIMITS;
|
|
1659
1841
|
exports.EHttpStatusCode = EHttpStatusCode;
|
|
1660
1842
|
exports.HttpError = HttpError;
|
|
1661
|
-
exports.
|
|
1843
|
+
exports.HttpResponse = HttpResponse;
|
|
1662
1844
|
exports.WooksHttp = WooksHttp;
|
|
1845
|
+
exports.WooksHttpResponse = WooksHttpResponse;
|
|
1663
1846
|
exports.WooksURLSearchParams = WooksURLSearchParams;
|
|
1664
1847
|
exports.createHttpApp = createHttpApp;
|
|
1665
1848
|
exports.createHttpContext = createHttpContext;
|
|
1666
|
-
exports.
|
|
1849
|
+
exports.httpKind = httpKind;
|
|
1667
1850
|
exports.httpStatusCodes = httpStatusCodes;
|
|
1851
|
+
exports.prepareTestHttpContext = prepareTestHttpContext;
|
|
1852
|
+
exports.rawBodySlot = rawBodySlot;
|
|
1668
1853
|
exports.renderCacheControl = renderCacheControl;
|
|
1854
|
+
exports.securityHeaders = securityHeaders;
|
|
1669
1855
|
exports.useAccept = useAccept;
|
|
1670
1856
|
exports.useAuthorization = useAuthorization;
|
|
1671
1857
|
exports.useCookies = useCookies;
|
|
1672
|
-
|
|
1858
|
+
exports.useHeaders = useHeaders;
|
|
1859
|
+
exports.useHttpContext = useHttpContext;
|
|
1860
|
+
Object.defineProperty(exports, 'useLogger', {
|
|
1673
1861
|
enumerable: true,
|
|
1674
1862
|
get: function () {
|
|
1675
|
-
return __wooksjs_event_core.
|
|
1863
|
+
return __wooksjs_event_core.useLogger;
|
|
1676
1864
|
}
|
|
1677
1865
|
});
|
|
1678
|
-
exports.useHeaders = useHeaders;
|
|
1679
|
-
exports.useHttpContext = useHttpContext;
|
|
1680
1866
|
exports.useRequest = useRequest;
|
|
1681
1867
|
exports.useResponse = useResponse;
|
|
1682
1868
|
Object.defineProperty(exports, 'useRouteParams', {
|
|
@@ -1685,10 +1871,4 @@ Object.defineProperty(exports, 'useRouteParams', {
|
|
|
1685
1871
|
return __wooksjs_event_core.useRouteParams;
|
|
1686
1872
|
}
|
|
1687
1873
|
});
|
|
1688
|
-
exports.useSearchParams = useSearchParams;
|
|
1689
|
-
exports.useSetCacheControl = useSetCacheControl;
|
|
1690
|
-
exports.useSetCookie = useSetCookie;
|
|
1691
|
-
exports.useSetCookies = useSetCookies;
|
|
1692
|
-
exports.useSetHeader = useSetHeader;
|
|
1693
|
-
exports.useSetHeaders = useSetHeaders;
|
|
1694
|
-
exports.useStatus = useStatus;
|
|
1874
|
+
exports.useSearchParams = useSearchParams;
|