@adonisjs/http-server 8.0.0-next.12 → 8.0.0-next.13
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/build/define_config-DpDqx7A4.js +1953 -0
- package/build/factories/http_context.d.ts +4 -4
- package/build/factories/main.d.ts +2 -2
- package/build/factories/main.js +162 -346
- package/build/factories/request.d.ts +3 -3
- package/build/factories/response.d.ts +3 -3
- package/build/helpers-C_2HouOe.js +52 -0
- package/build/helpers-Dt-25M21.js +660 -0
- package/build/index.d.ts +2 -2
- package/build/index.js +157 -375
- package/build/src/client/url_builder.js +3 -12
- package/build/src/exception_handler.d.ts +7 -0
- package/build/src/helpers.js +3 -26
- package/build/src/http_context/main.d.ts +7 -7
- package/build/src/redirect.d.ts +2 -2
- package/build/src/request.d.ts +1 -1
- package/build/src/response.d.ts +2 -2
- package/build/src/server/main.d.ts +5 -5
- package/build/src/types/main.js +1 -0
- package/build/src/types/response.d.ts +6 -1
- package/build/url_builder-piNQy-CF.js +114 -0
- package/package.json +19 -16
- package/build/chunk-2QM3D5BN.js +0 -87
- package/build/chunk-77CSRFCU.js +0 -131
- package/build/chunk-L2UOVWDK.js +0 -4441
- package/build/chunk-QDK57QGB.js +0 -1176
package/build/chunk-L2UOVWDK.js
DELETED
|
@@ -1,4441 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BriskRoute,
|
|
3
|
-
Route,
|
|
4
|
-
RouteGroup,
|
|
5
|
-
RouteResource,
|
|
6
|
-
createSignedURL,
|
|
7
|
-
debug_default,
|
|
8
|
-
default as default2,
|
|
9
|
-
default2 as default3,
|
|
10
|
-
httpExceptionHandler,
|
|
11
|
-
httpMiddleware,
|
|
12
|
-
httpRequest,
|
|
13
|
-
httpResponseSerializer,
|
|
14
|
-
parseRoute,
|
|
15
|
-
safeDecodeURI,
|
|
16
|
-
serializeCookie,
|
|
17
|
-
toRoutesJSON,
|
|
18
|
-
trustProxy
|
|
19
|
-
} from "./chunk-QDK57QGB.js";
|
|
20
|
-
import {
|
|
21
|
-
createUrlBuilder
|
|
22
|
-
} from "./chunk-77CSRFCU.js";
|
|
23
|
-
import {
|
|
24
|
-
__export,
|
|
25
|
-
createURL,
|
|
26
|
-
findRoute
|
|
27
|
-
} from "./chunk-2QM3D5BN.js";
|
|
28
|
-
|
|
29
|
-
// src/qs.ts
|
|
30
|
-
import { parse, stringify } from "qs";
|
|
31
|
-
var Qs = class {
|
|
32
|
-
/**
|
|
33
|
-
* Configuration object containing parse and stringify options for query strings
|
|
34
|
-
*/
|
|
35
|
-
#config;
|
|
36
|
-
/**
|
|
37
|
-
* Creates a new query string parser instance with the provided configuration
|
|
38
|
-
* @param config - Configuration object with parse and stringify options
|
|
39
|
-
*/
|
|
40
|
-
constructor(config) {
|
|
41
|
-
this.#config = config;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Parses a query string into a JavaScript object using the configured options
|
|
45
|
-
* @param value - Query string to parse (e.g., "foo=bar&baz=qux")
|
|
46
|
-
* @returns Parsed object representation of the query string
|
|
47
|
-
*/
|
|
48
|
-
parse = (value) => {
|
|
49
|
-
return parse(value, this.#config.parse);
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Converts a JavaScript object into a query string using the configured options
|
|
53
|
-
* @param value - Object to convert to query string
|
|
54
|
-
* @returns Stringified query string representation of the object
|
|
55
|
-
*/
|
|
56
|
-
stringify = (value) => {
|
|
57
|
-
return stringify(value, this.#config.stringify);
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// src/errors.ts
|
|
62
|
-
var errors_exports = {};
|
|
63
|
-
__export(errors_exports, {
|
|
64
|
-
E_CANNOT_LOOKUP_ROUTE: () => E_CANNOT_LOOKUP_ROUTE,
|
|
65
|
-
E_HTTP_EXCEPTION: () => E_HTTP_EXCEPTION,
|
|
66
|
-
E_HTTP_REQUEST_ABORTED: () => E_HTTP_REQUEST_ABORTED,
|
|
67
|
-
E_ROUTE_NOT_FOUND: () => E_ROUTE_NOT_FOUND
|
|
68
|
-
});
|
|
69
|
-
import { createError, Exception } from "@poppinss/utils/exception";
|
|
70
|
-
var E_ROUTE_NOT_FOUND = createError(
|
|
71
|
-
"Cannot %s:%s",
|
|
72
|
-
"E_ROUTE_NOT_FOUND",
|
|
73
|
-
404
|
|
74
|
-
);
|
|
75
|
-
var E_CANNOT_LOOKUP_ROUTE = createError(
|
|
76
|
-
'Cannot lookup route "%s"',
|
|
77
|
-
"E_CANNOT_LOOKUP_ROUTE",
|
|
78
|
-
500
|
|
79
|
-
);
|
|
80
|
-
var E_HTTP_EXCEPTION = class HttpException extends Exception {
|
|
81
|
-
body;
|
|
82
|
-
static code = "E_HTTP_EXCEPTION";
|
|
83
|
-
/**
|
|
84
|
-
* Creates and returns an instance of the HttpException class.
|
|
85
|
-
*
|
|
86
|
-
* @param body - The response body (string, object, or null/undefined)
|
|
87
|
-
* @param status - HTTP status code for the response
|
|
88
|
-
* @param code - Optional error code (defaults to 'E_HTTP_EXCEPTION')
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```ts
|
|
92
|
-
* const error = HttpException.invoke('Resource not found', 404)
|
|
93
|
-
* const error2 = HttpException.invoke({ message: 'Validation failed' }, 422)
|
|
94
|
-
* ```
|
|
95
|
-
*/
|
|
96
|
-
static invoke(body, status, code = "E_HTTP_EXCEPTION") {
|
|
97
|
-
if (body === null || body === void 0) {
|
|
98
|
-
const error2 = new this("HTTP Exception", { status, code });
|
|
99
|
-
error2.body = "Internal server error";
|
|
100
|
-
return error2;
|
|
101
|
-
}
|
|
102
|
-
if (typeof body === "object") {
|
|
103
|
-
const error2 = new this(body.message || "HTTP Exception", { status, code });
|
|
104
|
-
error2.body = body;
|
|
105
|
-
return error2;
|
|
106
|
-
}
|
|
107
|
-
const error = new this(body, { status, code });
|
|
108
|
-
error.body = body;
|
|
109
|
-
return error;
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
var E_HTTP_REQUEST_ABORTED = class AbortException extends E_HTTP_EXCEPTION {
|
|
113
|
-
handle(error, ctx) {
|
|
114
|
-
ctx.response.status(error.status).send(error.body);
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// src/cookies/drivers/plain.ts
|
|
119
|
-
import base64 from "@poppinss/utils/base64";
|
|
120
|
-
import { MessageBuilder } from "@poppinss/utils";
|
|
121
|
-
function pack(value) {
|
|
122
|
-
if (value === void 0 || value === null) {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
return base64.urlEncode(new MessageBuilder().build(value));
|
|
126
|
-
}
|
|
127
|
-
function canUnpack(encodedValue) {
|
|
128
|
-
return typeof encodedValue === "string";
|
|
129
|
-
}
|
|
130
|
-
function unpack(encodedValue) {
|
|
131
|
-
return new MessageBuilder().verify(base64.urlDecode(encodedValue, "utf-8", false));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// src/cookies/drivers/signed.ts
|
|
135
|
-
function pack2(key, value, encryption) {
|
|
136
|
-
if (value === void 0 || value === null) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
return `s:${encryption.verifier.sign(value, void 0, key)}`;
|
|
140
|
-
}
|
|
141
|
-
function canUnpack2(signedValue) {
|
|
142
|
-
return typeof signedValue === "string" && signedValue.substring(0, 2) === "s:";
|
|
143
|
-
}
|
|
144
|
-
function unpack2(key, signedValue, encryption) {
|
|
145
|
-
const value = signedValue.slice(2);
|
|
146
|
-
if (!value) {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
return encryption.verifier.unsign(value, key);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// src/cookies/drivers/encrypted.ts
|
|
153
|
-
function pack3(key, value, encryption) {
|
|
154
|
-
if (value === void 0 || value === null) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
return `e:${encryption.encrypt(value, void 0, key)}`;
|
|
158
|
-
}
|
|
159
|
-
function canUnpack3(encryptedValue) {
|
|
160
|
-
return typeof encryptedValue === "string" && encryptedValue.substring(0, 2) === "e:";
|
|
161
|
-
}
|
|
162
|
-
function unpack3(key, encryptedValue, encryption) {
|
|
163
|
-
const value = encryptedValue.slice(2);
|
|
164
|
-
if (!value) {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
return encryption.decrypt(value, key);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// src/cookies/client.ts
|
|
171
|
-
var CookieClient = class {
|
|
172
|
-
/**
|
|
173
|
-
* Private encryption instance used for signing and encrypting cookies
|
|
174
|
-
*/
|
|
175
|
-
#encryption;
|
|
176
|
-
/**
|
|
177
|
-
* Create a new instance of CookieClient
|
|
178
|
-
*
|
|
179
|
-
* @param encryption - The encryption instance for cookie operations
|
|
180
|
-
*/
|
|
181
|
-
constructor(encryption) {
|
|
182
|
-
this.#encryption = encryption;
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Encrypt a key value pair to be sent in the cookie header
|
|
186
|
-
*
|
|
187
|
-
* @param key - The cookie key
|
|
188
|
-
* @param value - The value to encrypt
|
|
189
|
-
* @returns The encrypted cookie string or null if encryption fails
|
|
190
|
-
*/
|
|
191
|
-
encrypt(key, value) {
|
|
192
|
-
return pack3(key, value, this.#encryption);
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Sign a key value pair to be sent in the cookie header
|
|
196
|
-
*
|
|
197
|
-
* @param key - The cookie key
|
|
198
|
-
* @param value - The value to sign
|
|
199
|
-
* @returns The signed cookie string or null if signing fails
|
|
200
|
-
*/
|
|
201
|
-
sign(key, value) {
|
|
202
|
-
return pack2(key, value, this.#encryption);
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Encode a key value pair to be sent in the cookie header
|
|
206
|
-
*
|
|
207
|
-
* @param _ - Unused key parameter
|
|
208
|
-
* @param value - The value to encode
|
|
209
|
-
* @param stringify - Whether to stringify the value before encoding
|
|
210
|
-
* @returns The encoded cookie string or null if encoding fails
|
|
211
|
-
*/
|
|
212
|
-
encode(_, value, stringify2 = true) {
|
|
213
|
-
return stringify2 ? pack(value) : value;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Unsign a signed cookie value
|
|
217
|
-
*
|
|
218
|
-
* @param key - The cookie key
|
|
219
|
-
* @param value - The signed cookie value to unsign
|
|
220
|
-
* @returns The original value if valid signature, null otherwise
|
|
221
|
-
*/
|
|
222
|
-
unsign(key, value) {
|
|
223
|
-
return canUnpack2(value) ? unpack2(key, value, this.#encryption) : null;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Decrypt an encrypted cookie value
|
|
227
|
-
*
|
|
228
|
-
* @param key - The cookie key
|
|
229
|
-
* @param value - The encrypted cookie value to decrypt
|
|
230
|
-
* @returns The decrypted value or null if decryption fails
|
|
231
|
-
*/
|
|
232
|
-
decrypt(key, value) {
|
|
233
|
-
return canUnpack3(value) ? unpack3(key, value, this.#encryption) : null;
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Decode an encoded cookie value
|
|
237
|
-
*
|
|
238
|
-
* @param _ - Unused key parameter
|
|
239
|
-
* @param value - The encoded cookie value to decode
|
|
240
|
-
* @param stringified - Whether the value was stringified during encoding
|
|
241
|
-
* @returns The decoded value or null if decoding fails
|
|
242
|
-
*/
|
|
243
|
-
decode(_, value, stringified = true) {
|
|
244
|
-
if (!stringified) {
|
|
245
|
-
return value;
|
|
246
|
-
}
|
|
247
|
-
return canUnpack(value) ? unpack(value) : null;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Parse a cookie value by attempting to decrypt, unsign, or decode it.
|
|
251
|
-
*
|
|
252
|
-
* This method tries different unpacking strategies in order:
|
|
253
|
-
* 1. Unsign if it's a signed cookie
|
|
254
|
-
* 2. Decrypt if it's an encrypted cookie
|
|
255
|
-
* 3. Decode if it's a plain encoded cookie
|
|
256
|
-
*
|
|
257
|
-
* @param key - The cookie key
|
|
258
|
-
* @param value - The cookie value to parse
|
|
259
|
-
*
|
|
260
|
-
* @example
|
|
261
|
-
* ```ts
|
|
262
|
-
* const parsed = client.parse('session', 'e30.abc123def')
|
|
263
|
-
* // Returns the original value if successfully parsed
|
|
264
|
-
* ```
|
|
265
|
-
*/
|
|
266
|
-
parse(key, value) {
|
|
267
|
-
if (canUnpack2(value)) {
|
|
268
|
-
return unpack2(key, value, this.#encryption);
|
|
269
|
-
}
|
|
270
|
-
if (canUnpack3(value)) {
|
|
271
|
-
return unpack3(key, value, this.#encryption);
|
|
272
|
-
}
|
|
273
|
-
if (canUnpack(value)) {
|
|
274
|
-
return unpack(value);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
// src/cookies/parser.ts
|
|
280
|
-
import cookie from "cookie";
|
|
281
|
-
var CookieParser = class {
|
|
282
|
-
/**
|
|
283
|
-
* Cookie client instance for handling cookie operations
|
|
284
|
-
*/
|
|
285
|
-
#client;
|
|
286
|
-
/**
|
|
287
|
-
* A copy of cached cookies, they are cached during a request after
|
|
288
|
-
* initial decoding, unsigning or decrypting.
|
|
289
|
-
*/
|
|
290
|
-
#cachedCookies = {
|
|
291
|
-
signedCookies: {},
|
|
292
|
-
plainCookies: {},
|
|
293
|
-
encryptedCookies: {}
|
|
294
|
-
};
|
|
295
|
-
/**
|
|
296
|
-
* An object of key-value pair collected by parsing
|
|
297
|
-
* the request cookie header.
|
|
298
|
-
*/
|
|
299
|
-
#cookies;
|
|
300
|
-
/**
|
|
301
|
-
* Create a new instance of CookieParser
|
|
302
|
-
*
|
|
303
|
-
* @param cookieHeader - The raw cookie header string from the request
|
|
304
|
-
* @param encryption - The encryption instance for cookie operations
|
|
305
|
-
*/
|
|
306
|
-
constructor(cookieHeader, encryption) {
|
|
307
|
-
this.#client = new CookieClient(encryption);
|
|
308
|
-
this.#cookies = this.#parse(cookieHeader);
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Parses the request `cookie` header
|
|
312
|
-
*
|
|
313
|
-
* @param cookieHeader - The cookie header string to parse
|
|
314
|
-
* @returns Parsed cookies as key-value pairs
|
|
315
|
-
*/
|
|
316
|
-
#parse(cookieHeader) {
|
|
317
|
-
if (!cookieHeader) {
|
|
318
|
-
return {};
|
|
319
|
-
}
|
|
320
|
-
return cookie.parse(cookieHeader);
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Attempts to decode a cookie by the name. When calling this method,
|
|
324
|
-
* you are assuming that the cookie was just stringified in the first
|
|
325
|
-
* place and not signed or encrypted.
|
|
326
|
-
*
|
|
327
|
-
* @param key - The cookie key to decode
|
|
328
|
-
* @param stringified - Whether the cookie value was stringified
|
|
329
|
-
* @returns The decoded cookie value or null if decoding fails
|
|
330
|
-
*/
|
|
331
|
-
decode(key, stringified = true) {
|
|
332
|
-
const value = this.#cookies[key];
|
|
333
|
-
if (value === null || value === void 0) {
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
const cache = this.#cachedCookies.plainCookies;
|
|
337
|
-
if (cache[key] !== void 0) {
|
|
338
|
-
return cache[key];
|
|
339
|
-
}
|
|
340
|
-
const parsed = this.#client.decode(key, value, stringified);
|
|
341
|
-
if (parsed !== null) {
|
|
342
|
-
cache[key] = parsed;
|
|
343
|
-
}
|
|
344
|
-
return parsed;
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* Attempts to unsign a cookie by the name. When calling this method,
|
|
348
|
-
* you are assuming that the cookie was signed in the first place.
|
|
349
|
-
*
|
|
350
|
-
* @param key - The cookie key to unsign
|
|
351
|
-
* @returns The original cookie value or null if unsigning fails
|
|
352
|
-
*/
|
|
353
|
-
unsign(key) {
|
|
354
|
-
const value = this.#cookies[key];
|
|
355
|
-
if (value === null || value === void 0) {
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
const cache = this.#cachedCookies.signedCookies;
|
|
359
|
-
if (cache[key] !== void 0) {
|
|
360
|
-
return cache[key];
|
|
361
|
-
}
|
|
362
|
-
const parsed = this.#client.unsign(key, value);
|
|
363
|
-
if (parsed !== null) {
|
|
364
|
-
cache[key] = parsed;
|
|
365
|
-
}
|
|
366
|
-
return parsed;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Attempts to decrypt a cookie by the name. When calling this method,
|
|
370
|
-
* you are assuming that the cookie was encrypted in the first place.
|
|
371
|
-
*
|
|
372
|
-
* @param key - The cookie key to decrypt
|
|
373
|
-
* @returns The decrypted cookie value or null if decryption fails
|
|
374
|
-
*/
|
|
375
|
-
decrypt(key) {
|
|
376
|
-
const value = this.#cookies[key];
|
|
377
|
-
if (value === null || value === void 0) {
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
const cache = this.#cachedCookies.encryptedCookies;
|
|
381
|
-
if (cache[key] !== void 0) {
|
|
382
|
-
return cache[key];
|
|
383
|
-
}
|
|
384
|
-
const parsed = this.#client.decrypt(key, value);
|
|
385
|
-
if (parsed !== null) {
|
|
386
|
-
cache[key] = parsed;
|
|
387
|
-
}
|
|
388
|
-
return parsed;
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* Returns an object of cookies key-value pair. Do note, the
|
|
392
|
-
* cookies are not decoded, unsigned or decrypted inside this
|
|
393
|
-
* list.
|
|
394
|
-
*
|
|
395
|
-
* @returns Raw cookies as key-value pairs
|
|
396
|
-
*/
|
|
397
|
-
list() {
|
|
398
|
-
return this.#cookies;
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
// src/request.ts
|
|
403
|
-
import fresh from "fresh";
|
|
404
|
-
import typeIs from "type-is";
|
|
405
|
-
import accepts from "accepts";
|
|
406
|
-
import { isIP } from "net";
|
|
407
|
-
import is from "@sindresorhus/is";
|
|
408
|
-
import proxyaddr from "proxy-addr";
|
|
409
|
-
import { safeEqual } from "@poppinss/utils";
|
|
410
|
-
import Macroable from "@poppinss/macroable";
|
|
411
|
-
import lodash from "@poppinss/utils/lodash";
|
|
412
|
-
var Request = class extends Macroable {
|
|
413
|
-
/**
|
|
414
|
-
* Creates a new Request instance wrapping the native Node.js HTTP request
|
|
415
|
-
* @param request - Native Node.js incoming message instance
|
|
416
|
-
* @param response - Native Node.js server response instance
|
|
417
|
-
* @param encryption - Encryption module for cookie and URL signing
|
|
418
|
-
* @param config - Request configuration options
|
|
419
|
-
* @param qsParser - Query string parser instance
|
|
420
|
-
*/
|
|
421
|
-
constructor(request, response, encryption, config, qsParser) {
|
|
422
|
-
super();
|
|
423
|
-
this.request = request;
|
|
424
|
-
this.response = response;
|
|
425
|
-
this.#qsParser = qsParser;
|
|
426
|
-
this.#config = config;
|
|
427
|
-
this.#encryption = encryption;
|
|
428
|
-
this.parsedUrl = safeDecodeURI(request.url, false);
|
|
429
|
-
this.#parseQueryString();
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Query string parser
|
|
433
|
-
*/
|
|
434
|
-
#qsParser;
|
|
435
|
-
/**
|
|
436
|
-
* Encryption module to verify signed URLs and unsign/decrypt
|
|
437
|
-
* cookies
|
|
438
|
-
*/
|
|
439
|
-
#encryption;
|
|
440
|
-
/**
|
|
441
|
-
* Request config
|
|
442
|
-
*/
|
|
443
|
-
#config;
|
|
444
|
-
/**
|
|
445
|
-
* Request body set using `setBody` method
|
|
446
|
-
*/
|
|
447
|
-
#requestBody = {};
|
|
448
|
-
/**
|
|
449
|
-
* A merged copy of `request body` and `querystring`
|
|
450
|
-
*/
|
|
451
|
-
#requestData = {};
|
|
452
|
-
/**
|
|
453
|
-
* Original merged copy of `request body` and `querystring`.
|
|
454
|
-
* Further mutation to this object are not allowed
|
|
455
|
-
*/
|
|
456
|
-
#originalRequestData = {};
|
|
457
|
-
/**
|
|
458
|
-
* Parsed query string
|
|
459
|
-
*/
|
|
460
|
-
#requestQs = {};
|
|
461
|
-
/**
|
|
462
|
-
* Raw request body as text
|
|
463
|
-
*/
|
|
464
|
-
#rawRequestBody;
|
|
465
|
-
/**
|
|
466
|
-
* Cached copy of `accepts` fn to do content
|
|
467
|
-
* negotiation.
|
|
468
|
-
*/
|
|
469
|
-
#lazyAccepts;
|
|
470
|
-
/**
|
|
471
|
-
* Copy of lazily parsed signed and plain cookies.
|
|
472
|
-
*/
|
|
473
|
-
#cookieParser;
|
|
474
|
-
/**
|
|
475
|
-
* Parsed URL with query string stored as a string and decode flag
|
|
476
|
-
*/
|
|
477
|
-
parsedUrl;
|
|
478
|
-
/**
|
|
479
|
-
* HTTP context reference - creates a circular reference when set by the context
|
|
480
|
-
*/
|
|
481
|
-
ctx;
|
|
482
|
-
/**
|
|
483
|
-
* Parses the query string from the parsed URL and updates internal state.
|
|
484
|
-
*
|
|
485
|
-
* This method extracts query parameters from the URL and merges them into
|
|
486
|
-
* the request data object, also creating a frozen copy for original data reference.
|
|
487
|
-
*/
|
|
488
|
-
#parseQueryString() {
|
|
489
|
-
if (this.parsedUrl.query) {
|
|
490
|
-
this.updateQs(this.#qsParser.parse(this.parsedUrl.query));
|
|
491
|
-
this.#originalRequestData = { ...this.#requestData };
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Initiates the cookie parser lazily when first needed.
|
|
496
|
-
*
|
|
497
|
-
* Creates a CookieParser instance with the current request's cookie header
|
|
498
|
-
* and the configured encryption service for handling signed/encrypted cookies.
|
|
499
|
-
*/
|
|
500
|
-
#initiateCookieParser() {
|
|
501
|
-
if (!this.#cookieParser) {
|
|
502
|
-
this.#cookieParser = new CookieParser(this.header("cookie"), this.#encryption);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Lazily initiates the `accepts` module for content negotiation.
|
|
507
|
-
*
|
|
508
|
-
* Creates an accepts instance that parses the request headers only when
|
|
509
|
-
* one of the content-negotiation methods (like accepts, acceptsLanguages) are used.
|
|
510
|
-
* This improves performance by avoiding unnecessary header parsing.
|
|
511
|
-
*/
|
|
512
|
-
#initiateAccepts() {
|
|
513
|
-
this.#lazyAccepts = this.#lazyAccepts || accepts(this.request);
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* Returns the request ID from the `x-request-id` header.
|
|
517
|
-
*
|
|
518
|
-
* If the header doesn't exist and request ID generation is enabled,
|
|
519
|
-
* a new UUID will be generated and added to the request headers.
|
|
520
|
-
*
|
|
521
|
-
* @example
|
|
522
|
-
* ```ts
|
|
523
|
-
* const requestId = request.id()
|
|
524
|
-
* console.log(requestId) // '550e8400-e29b-41d4-a716-446655440000'
|
|
525
|
-
* ```
|
|
526
|
-
*/
|
|
527
|
-
id() {
|
|
528
|
-
let requestId = this.header("x-request-id");
|
|
529
|
-
if (!requestId && this.#config.generateRequestId) {
|
|
530
|
-
requestId = this.#config.createRequestId();
|
|
531
|
-
this.request.headers["x-request-id"] = requestId;
|
|
532
|
-
}
|
|
533
|
-
return requestId;
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* Set initial request body. A copy of the input will be maintained as the original
|
|
537
|
-
* request body. Since the request body and query string is subject to mutations, we
|
|
538
|
-
* keep one original reference to flash old data (whenever required).
|
|
539
|
-
*
|
|
540
|
-
* This method is supposed to be invoked by the body parser and must be called only
|
|
541
|
-
* once. For further mutations make use of `updateBody` method.
|
|
542
|
-
* @param body - Parsed request body data
|
|
543
|
-
* @returns {void}
|
|
544
|
-
*/
|
|
545
|
-
setInitialBody(body) {
|
|
546
|
-
if (this.#originalRequestData && Object.isFrozen(this.#originalRequestData)) {
|
|
547
|
-
throw new Error('Cannot re-set initial body. Use "request.updateBody" instead');
|
|
548
|
-
}
|
|
549
|
-
this.updateBody(body);
|
|
550
|
-
this.#originalRequestData = Object.freeze(lodash.cloneDeep(this.#requestData));
|
|
551
|
-
}
|
|
552
|
-
/**
|
|
553
|
-
* Update the request body with new data object. The `all` property
|
|
554
|
-
* will be re-computed by merging the query string and request
|
|
555
|
-
* body.
|
|
556
|
-
* @param body - New request body data to set
|
|
557
|
-
* @returns {void}
|
|
558
|
-
*/
|
|
559
|
-
updateBody(body) {
|
|
560
|
-
this.#requestBody = body;
|
|
561
|
-
this.#requestData = { ...this.#requestBody, ...this.#requestQs };
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Update the request raw body. Bodyparser sets this when unable to parse
|
|
565
|
-
* the request body or when request is multipart/form-data.
|
|
566
|
-
* @param rawBody - Raw request body as string
|
|
567
|
-
* @returns {void}
|
|
568
|
-
*/
|
|
569
|
-
updateRawBody(rawBody) {
|
|
570
|
-
this.#rawRequestBody = rawBody;
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Update the query string with the new data object. The `all` property
|
|
574
|
-
* will be re-computed by merging the query and the request body.
|
|
575
|
-
* @param data - New query string data to set
|
|
576
|
-
* @returns {void}
|
|
577
|
-
*/
|
|
578
|
-
updateQs(data) {
|
|
579
|
-
this.#requestQs = data;
|
|
580
|
-
this.#requestData = { ...this.#requestBody, ...this.#requestQs };
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* Returns route params
|
|
584
|
-
* @returns {Record<string, any>} Object containing route parameters
|
|
585
|
-
*/
|
|
586
|
-
params() {
|
|
587
|
-
return this.ctx?.params || {};
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Returns the query string object by reference
|
|
591
|
-
* @returns {Record<string, any>} Object containing parsed query string parameters
|
|
592
|
-
*/
|
|
593
|
-
qs() {
|
|
594
|
-
return this.#requestQs;
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* Returns reference to the request body
|
|
598
|
-
* @returns {Record<string, any>} Object containing parsed request body
|
|
599
|
-
*/
|
|
600
|
-
body() {
|
|
601
|
-
return this.#requestBody;
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Returns reference to the merged copy of request body
|
|
605
|
-
* and query string
|
|
606
|
-
* @returns {Record<string, any>} Object containing merged request body and query parameters
|
|
607
|
-
*/
|
|
608
|
-
all() {
|
|
609
|
-
return this.#requestData;
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Returns reference to the merged copy of original request
|
|
613
|
-
* query string and body
|
|
614
|
-
* @returns {Record<string, any>} Object containing original merged request data
|
|
615
|
-
*/
|
|
616
|
-
original() {
|
|
617
|
-
return this.#originalRequestData;
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Returns the request raw body (if exists), or returns `null`.
|
|
621
|
-
*
|
|
622
|
-
* Ideally you must be dealing with the parsed body accessed using [[input]], [[all]] or
|
|
623
|
-
* [[post]] methods. The `raw` body is always a string.
|
|
624
|
-
* @returns {string | null} Raw request body as string or null if not set
|
|
625
|
-
*/
|
|
626
|
-
raw() {
|
|
627
|
-
return this.#rawRequestBody || null;
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Returns value for a given key from the request body or query string.
|
|
631
|
-
* The `defaultValue` is used when original value is `undefined`.
|
|
632
|
-
*
|
|
633
|
-
* @example
|
|
634
|
-
* ```js
|
|
635
|
-
* request.input('username')
|
|
636
|
-
*
|
|
637
|
-
* // with default value
|
|
638
|
-
* request.input('username', 'virk')
|
|
639
|
-
* ```
|
|
640
|
-
* @param key - Key to lookup in request data
|
|
641
|
-
* @param defaultValue - Default value when key is not found
|
|
642
|
-
* @returns Value from request data or default value
|
|
643
|
-
*/
|
|
644
|
-
input(key, defaultValue) {
|
|
645
|
-
return lodash.get(this.#requestData, key, defaultValue);
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
|
-
* Returns value for a given key from route params
|
|
649
|
-
*
|
|
650
|
-
* @example
|
|
651
|
-
* ```js
|
|
652
|
-
* request.param('id')
|
|
653
|
-
*
|
|
654
|
-
* // with default value
|
|
655
|
-
* request.param('id', 1)
|
|
656
|
-
* ```
|
|
657
|
-
* @param key - Parameter key to lookup
|
|
658
|
-
* @param defaultValue - Default value when parameter is not found
|
|
659
|
-
* @returns Value from route parameters or default value
|
|
660
|
-
*/
|
|
661
|
-
param(key, defaultValue) {
|
|
662
|
-
return lodash.get(this.params(), key, defaultValue);
|
|
663
|
-
}
|
|
664
|
-
/**
|
|
665
|
-
* Get everything from the request body except the given keys.
|
|
666
|
-
*
|
|
667
|
-
* @example
|
|
668
|
-
* ```js
|
|
669
|
-
* request.except(['_csrf'])
|
|
670
|
-
* ```
|
|
671
|
-
* @param keys - Array of keys to exclude from the result
|
|
672
|
-
* @returns {Record<string, any>} Object with all request data except specified keys
|
|
673
|
-
*/
|
|
674
|
-
except(keys) {
|
|
675
|
-
return lodash.omit(this.#requestData, keys);
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Get value for specified keys.
|
|
679
|
-
*
|
|
680
|
-
* @example
|
|
681
|
-
* ```js
|
|
682
|
-
* request.only(['username', 'age'])
|
|
683
|
-
* ```
|
|
684
|
-
* @param keys - Array of keys to include in the result
|
|
685
|
-
* @returns {{ [K in T]: any }} Object with only the specified keys from request data
|
|
686
|
-
*/
|
|
687
|
-
only(keys) {
|
|
688
|
-
return lodash.pick(this.#requestData, keys);
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Returns the HTTP request method. This is the original
|
|
692
|
-
* request method. For spoofed request method, make
|
|
693
|
-
* use of [[method]].
|
|
694
|
-
*
|
|
695
|
-
* @example
|
|
696
|
-
* ```js
|
|
697
|
-
* request.intended()
|
|
698
|
-
* ```
|
|
699
|
-
* @returns {string} Original HTTP method from the request
|
|
700
|
-
*/
|
|
701
|
-
intended() {
|
|
702
|
-
return this.request.method;
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* Returns the request HTTP method by taking method spoofing into account.
|
|
706
|
-
*
|
|
707
|
-
* Method spoofing works when all of the following are true.
|
|
708
|
-
*
|
|
709
|
-
* 1. `app.http.allowMethodSpoofing` config value is true.
|
|
710
|
-
* 2. request query string has `_method`.
|
|
711
|
-
* 3. The [[intended]] request method is `POST`.
|
|
712
|
-
*
|
|
713
|
-
* @example
|
|
714
|
-
* ```js
|
|
715
|
-
* request.method()
|
|
716
|
-
* ```
|
|
717
|
-
* @returns {string} HTTP method (potentially spoofed)
|
|
718
|
-
*/
|
|
719
|
-
method() {
|
|
720
|
-
if (this.#config.allowMethodSpoofing && this.intended() === "POST") {
|
|
721
|
-
return this.input("_method", this.intended()).toUpperCase();
|
|
722
|
-
}
|
|
723
|
-
return this.intended();
|
|
724
|
-
}
|
|
725
|
-
/**
|
|
726
|
-
* Returns a copy of headers as an object
|
|
727
|
-
* @returns {IncomingHttpHeaders} Object containing all HTTP headers
|
|
728
|
-
*/
|
|
729
|
-
headers() {
|
|
730
|
-
return this.request.headers;
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* Returns value for a given header key. The default value is
|
|
734
|
-
* used when original value is `undefined`.
|
|
735
|
-
* @param key - Header name to lookup
|
|
736
|
-
* @param defaultValue - Default value when header is not found
|
|
737
|
-
* @returns {string | undefined} Header value or default value if not found
|
|
738
|
-
*/
|
|
739
|
-
header(key, defaultValue) {
|
|
740
|
-
key = key.toLowerCase();
|
|
741
|
-
const headers = this.headers();
|
|
742
|
-
switch (key) {
|
|
743
|
-
case "referer":
|
|
744
|
-
case "referrer":
|
|
745
|
-
return headers.referrer || headers.referer || defaultValue;
|
|
746
|
-
default:
|
|
747
|
-
return headers[key] || defaultValue;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Returns the ip address of the user. This method is optimize to fetch
|
|
752
|
-
* ip address even when running your AdonisJs app behind a proxy.
|
|
753
|
-
*
|
|
754
|
-
* You can also define your own custom function to compute the ip address by
|
|
755
|
-
* defining `app.http.getIp` as a function inside the config file.
|
|
756
|
-
*
|
|
757
|
-
* ```js
|
|
758
|
-
* {
|
|
759
|
-
* http: {
|
|
760
|
-
* getIp (request) {
|
|
761
|
-
* // I am using nginx as a proxy server and want to trust 'x-real-ip'
|
|
762
|
-
* return request.header('x-real-ip')
|
|
763
|
-
* }
|
|
764
|
-
* }
|
|
765
|
-
* }
|
|
766
|
-
* ```
|
|
767
|
-
*
|
|
768
|
-
* You can control the behavior of trusting the proxy values by defining it
|
|
769
|
-
* inside the `config/app.js` file.
|
|
770
|
-
*
|
|
771
|
-
* ```js
|
|
772
|
-
* {
|
|
773
|
-
* http: {
|
|
774
|
-
* trustProxy: '127.0.0.1'
|
|
775
|
-
* }
|
|
776
|
-
* }
|
|
777
|
-
* ```
|
|
778
|
-
*
|
|
779
|
-
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
780
|
-
* @returns {string} Client IP address
|
|
781
|
-
*/
|
|
782
|
-
ip() {
|
|
783
|
-
const ipFn = this.#config.getIp;
|
|
784
|
-
if (typeof ipFn === "function") {
|
|
785
|
-
return ipFn(this);
|
|
786
|
-
}
|
|
787
|
-
return proxyaddr(this.request, this.#config.trustProxy);
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Returns an array of ip addresses from most to least trusted one.
|
|
791
|
-
* This method is optimize to fetch ip address even when running
|
|
792
|
-
* your AdonisJs app behind a proxy.
|
|
793
|
-
*
|
|
794
|
-
* You can control the behavior of trusting the proxy values by defining it
|
|
795
|
-
* inside the `config/app.js` file.
|
|
796
|
-
*
|
|
797
|
-
* ```js
|
|
798
|
-
* {
|
|
799
|
-
* http: {
|
|
800
|
-
* trustProxy: '127.0.0.1'
|
|
801
|
-
* }
|
|
802
|
-
* }
|
|
803
|
-
* ```
|
|
804
|
-
*
|
|
805
|
-
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
806
|
-
* @returns {string[]} Array of IP addresses from most to least trusted
|
|
807
|
-
*/
|
|
808
|
-
ips() {
|
|
809
|
-
return proxyaddr.all(this.request, this.#config.trustProxy);
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Returns the request protocol by checking for the URL protocol or
|
|
813
|
-
* `X-Forwarded-Proto` header.
|
|
814
|
-
*
|
|
815
|
-
* If the `trust` is evaluated to `false`, then URL protocol is returned,
|
|
816
|
-
* otherwise `X-Forwarded-Proto` header is used (if exists).
|
|
817
|
-
*
|
|
818
|
-
* You can control the behavior of trusting the proxy values by defining it
|
|
819
|
-
* inside the `config/app.js` file.
|
|
820
|
-
*
|
|
821
|
-
* ```js
|
|
822
|
-
* {
|
|
823
|
-
* http: {
|
|
824
|
-
* trustProxy: '127.0.0.1'
|
|
825
|
-
* }
|
|
826
|
-
* }
|
|
827
|
-
* ```
|
|
828
|
-
*
|
|
829
|
-
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
830
|
-
* @returns {string} Request protocol ('http' or 'https')
|
|
831
|
-
*/
|
|
832
|
-
protocol() {
|
|
833
|
-
if ("encrypted" in this.request.socket) {
|
|
834
|
-
return "https";
|
|
835
|
-
}
|
|
836
|
-
if (trustProxy(this.request.socket.remoteAddress, this.#config.trustProxy)) {
|
|
837
|
-
const forwardedProtocol = this.header("X-Forwarded-Proto");
|
|
838
|
-
return forwardedProtocol ? forwardedProtocol.split(/\s*,\s*/)[0] : "http";
|
|
839
|
-
}
|
|
840
|
-
return "http";
|
|
841
|
-
}
|
|
842
|
-
/**
|
|
843
|
-
* Returns a boolean telling if request is served over `https`
|
|
844
|
-
* or not. Check [[protocol]] method to know how protocol is
|
|
845
|
-
* fetched.
|
|
846
|
-
* @returns {boolean} True if request is served over HTTPS
|
|
847
|
-
*/
|
|
848
|
-
secure() {
|
|
849
|
-
return this.protocol() === "https";
|
|
850
|
-
}
|
|
851
|
-
/**
|
|
852
|
-
* Returns the request host. If proxy headers are trusted, then
|
|
853
|
-
* `X-Forwarded-Host` is given priority over the `Host` header.
|
|
854
|
-
*
|
|
855
|
-
* You can control the behavior of trusting the proxy values by defining it
|
|
856
|
-
* inside the `config/app.js` file.
|
|
857
|
-
*
|
|
858
|
-
* ```js
|
|
859
|
-
* {
|
|
860
|
-
* http: {
|
|
861
|
-
* trustProxy: '127.0.0.1'
|
|
862
|
-
* }
|
|
863
|
-
* }
|
|
864
|
-
* ```
|
|
865
|
-
*
|
|
866
|
-
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
867
|
-
* @returns {string | null} Request host or null if not found
|
|
868
|
-
*/
|
|
869
|
-
host() {
|
|
870
|
-
let host = this.header("host");
|
|
871
|
-
if (trustProxy(this.request.socket.remoteAddress, this.#config.trustProxy)) {
|
|
872
|
-
host = this.header("X-Forwarded-Host") || host;
|
|
873
|
-
}
|
|
874
|
-
if (!host) {
|
|
875
|
-
return null;
|
|
876
|
-
}
|
|
877
|
-
return host;
|
|
878
|
-
}
|
|
879
|
-
/**
|
|
880
|
-
* Returns the request hostname. If proxy headers are trusted, then
|
|
881
|
-
* `X-Forwarded-Host` is given priority over the `Host` header.
|
|
882
|
-
*
|
|
883
|
-
* You can control the behavior of trusting the proxy values by defining it
|
|
884
|
-
* inside the `config/app.js` file.
|
|
885
|
-
*
|
|
886
|
-
* ```js
|
|
887
|
-
* {
|
|
888
|
-
* http: {
|
|
889
|
-
* trustProxy: '127.0.0.1'
|
|
890
|
-
* }
|
|
891
|
-
* }
|
|
892
|
-
* ```
|
|
893
|
-
*
|
|
894
|
-
* The value of trustProxy is passed directly to [proxy-addr](https://www.npmjs.com/package/proxy-addr)
|
|
895
|
-
* @returns {string | null} Request hostname (without port) or null if not found
|
|
896
|
-
*/
|
|
897
|
-
hostname() {
|
|
898
|
-
const host = this.host();
|
|
899
|
-
if (!host) {
|
|
900
|
-
return null;
|
|
901
|
-
}
|
|
902
|
-
const offset = host[0] === "[" ? host.indexOf("]") + 1 : 0;
|
|
903
|
-
const index = host.indexOf(":", offset);
|
|
904
|
-
return index !== -1 ? host.substring(0, index) : host;
|
|
905
|
-
}
|
|
906
|
-
/**
|
|
907
|
-
* Returns an array of subdomains for the given host. An empty array is
|
|
908
|
-
* returned if [[hostname]] is `null` or is an IP address.
|
|
909
|
-
*
|
|
910
|
-
* Also `www` is not considered as a subdomain
|
|
911
|
-
* @returns {string[]} Array of subdomains (excluding www)
|
|
912
|
-
*/
|
|
913
|
-
subdomains() {
|
|
914
|
-
const hostname = this.hostname();
|
|
915
|
-
if (!hostname || isIP(hostname)) {
|
|
916
|
-
return [];
|
|
917
|
-
}
|
|
918
|
-
const offset = this.#config.subdomainOffset;
|
|
919
|
-
const subdomains = hostname.split(".").reverse().slice(offset);
|
|
920
|
-
if (subdomains[subdomains.length - 1] === "www") {
|
|
921
|
-
subdomains.splice(subdomains.length - 1, 1);
|
|
922
|
-
}
|
|
923
|
-
return subdomains;
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* Returns a boolean telling, if request `X-Requested-With === 'xmlhttprequest'`
|
|
927
|
-
* or not.
|
|
928
|
-
* @returns {boolean} True if request is an AJAX request
|
|
929
|
-
*/
|
|
930
|
-
ajax() {
|
|
931
|
-
const xRequestedWith = this.header("X-Requested-With", "");
|
|
932
|
-
return xRequestedWith.toLowerCase() === "xmlhttprequest";
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Returns a boolean telling, if request has `X-Pjax` header
|
|
936
|
-
* set or not
|
|
937
|
-
* @returns {boolean} True if request is a PJAX request
|
|
938
|
-
*/
|
|
939
|
-
pjax() {
|
|
940
|
-
return !!this.header("X-Pjax");
|
|
941
|
-
}
|
|
942
|
-
/**
|
|
943
|
-
* Returns the request relative URL.
|
|
944
|
-
*
|
|
945
|
-
* @example
|
|
946
|
-
* ```js
|
|
947
|
-
* request.url()
|
|
948
|
-
*
|
|
949
|
-
* // include query string
|
|
950
|
-
* request.url(true)
|
|
951
|
-
* ```
|
|
952
|
-
* @param includeQueryString - Whether to include query string in the URL
|
|
953
|
-
* @returns {string} Request pathname, optionally with query string
|
|
954
|
-
*/
|
|
955
|
-
url(includeQueryString) {
|
|
956
|
-
const pathname = this.parsedUrl.pathname;
|
|
957
|
-
return includeQueryString && this.parsedUrl.query ? `${pathname}?${this.parsedUrl.query}` : pathname;
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* Returns the complete HTTP url by combining
|
|
961
|
-
* [[protocol]]://[[hostname]]/[[url]]
|
|
962
|
-
*
|
|
963
|
-
* @example
|
|
964
|
-
* ```js
|
|
965
|
-
* request.completeUrl()
|
|
966
|
-
*
|
|
967
|
-
* // include query string
|
|
968
|
-
* request.completeUrl(true)
|
|
969
|
-
* ```
|
|
970
|
-
* @param includeQueryString - Whether to include query string in the URL
|
|
971
|
-
* @returns {string} Complete URL including protocol and host
|
|
972
|
-
*/
|
|
973
|
-
completeUrl(includeQueryString) {
|
|
974
|
-
const protocol = this.protocol();
|
|
975
|
-
const hostname = this.host();
|
|
976
|
-
return `${protocol}://${hostname}${this.url(includeQueryString)}`;
|
|
977
|
-
}
|
|
978
|
-
/**
|
|
979
|
-
* Find if the current HTTP request is for the given route or the routes
|
|
980
|
-
* @param routeIdentifier - Route name, pattern, or handler reference to match
|
|
981
|
-
* @returns {boolean} True if the request matches any of the given route identifiers
|
|
982
|
-
*/
|
|
983
|
-
matchesRoute(routeIdentifier) {
|
|
984
|
-
if (!this.ctx || !this.ctx.route) {
|
|
985
|
-
return false;
|
|
986
|
-
}
|
|
987
|
-
const route = this.ctx.route;
|
|
988
|
-
return !!(Array.isArray(routeIdentifier) ? routeIdentifier : [routeIdentifier]).find(
|
|
989
|
-
(identifier) => {
|
|
990
|
-
if (route.pattern === identifier || route.name === identifier) {
|
|
991
|
-
return true;
|
|
992
|
-
}
|
|
993
|
-
if (typeof route.handler === "function") {
|
|
994
|
-
return false;
|
|
995
|
-
}
|
|
996
|
-
return route.handler.reference === identifier;
|
|
997
|
-
}
|
|
998
|
-
);
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Returns the best matching content type of the request by
|
|
1002
|
-
* matching against the given types.
|
|
1003
|
-
*
|
|
1004
|
-
* The content type is picked from the `content-type` header and request
|
|
1005
|
-
* must have body.
|
|
1006
|
-
*
|
|
1007
|
-
* The method response highly depends upon the types array values. Described below:
|
|
1008
|
-
*
|
|
1009
|
-
* | Type(s) | Return value |
|
|
1010
|
-
* |----------|---------------|
|
|
1011
|
-
* | ['json'] | json |
|
|
1012
|
-
* | ['application/*'] | application/json |
|
|
1013
|
-
* | ['vnd+json'] | application/json |
|
|
1014
|
-
*
|
|
1015
|
-
* @example
|
|
1016
|
-
* ```js
|
|
1017
|
-
* const bodyType = request.is(['json', 'xml'])
|
|
1018
|
-
*
|
|
1019
|
-
* if (bodyType === 'json') {
|
|
1020
|
-
* // process JSON
|
|
1021
|
-
* }
|
|
1022
|
-
*
|
|
1023
|
-
* if (bodyType === 'xml') {
|
|
1024
|
-
* // process XML
|
|
1025
|
-
* }
|
|
1026
|
-
* ```
|
|
1027
|
-
* @param types - Array of content types to match against
|
|
1028
|
-
* @returns {string | null} Best matching content type or null if no match
|
|
1029
|
-
*/
|
|
1030
|
-
is(types) {
|
|
1031
|
-
return typeIs(this.request, types) || null;
|
|
1032
|
-
}
|
|
1033
|
-
/**
|
|
1034
|
-
* Returns the best type using `Accept` header and
|
|
1035
|
-
* by matching it against the given types.
|
|
1036
|
-
*
|
|
1037
|
-
* If nothing is matched, then `null` will be returned
|
|
1038
|
-
*
|
|
1039
|
-
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1040
|
-
* docs too.
|
|
1041
|
-
*
|
|
1042
|
-
* @example
|
|
1043
|
-
* ```js
|
|
1044
|
-
* switch (request.accepts(['json', 'html'])) {
|
|
1045
|
-
* case 'json':
|
|
1046
|
-
* return response.json(user)
|
|
1047
|
-
* case 'html':
|
|
1048
|
-
* return view.render('user', { user })
|
|
1049
|
-
* default:
|
|
1050
|
-
* // decide yourself
|
|
1051
|
-
* }
|
|
1052
|
-
* ```
|
|
1053
|
-
* @param types - Array of types to match against Accept header
|
|
1054
|
-
* @returns {T | null} Best matching accept type or null if no match
|
|
1055
|
-
*/
|
|
1056
|
-
accepts(types) {
|
|
1057
|
-
this.#initiateAccepts();
|
|
1058
|
-
return this.#lazyAccepts.type(types) || null;
|
|
1059
|
-
}
|
|
1060
|
-
/**
|
|
1061
|
-
* Return the types that the request accepts, in the order of the
|
|
1062
|
-
* client's preference (most preferred first).
|
|
1063
|
-
*
|
|
1064
|
-
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1065
|
-
* docs too.
|
|
1066
|
-
* @returns {string[]} Array of accepted types in preference order
|
|
1067
|
-
*/
|
|
1068
|
-
types() {
|
|
1069
|
-
this.#initiateAccepts();
|
|
1070
|
-
return this.#lazyAccepts.types();
|
|
1071
|
-
}
|
|
1072
|
-
/**
|
|
1073
|
-
* Returns the best language using `Accept-language` header
|
|
1074
|
-
* and by matching it against the given languages.
|
|
1075
|
-
*
|
|
1076
|
-
* If nothing is matched, then `null` will be returned
|
|
1077
|
-
*
|
|
1078
|
-
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1079
|
-
* docs too.
|
|
1080
|
-
*
|
|
1081
|
-
* @example
|
|
1082
|
-
* ```js
|
|
1083
|
-
* switch (request.language(['fr', 'de'])) {
|
|
1084
|
-
* case 'fr':
|
|
1085
|
-
* return view.render('about', { lang: 'fr' })
|
|
1086
|
-
* case 'de':
|
|
1087
|
-
* return view.render('about', { lang: 'de' })
|
|
1088
|
-
* default:
|
|
1089
|
-
* return view.render('about', { lang: 'en' })
|
|
1090
|
-
* }
|
|
1091
|
-
* ```
|
|
1092
|
-
* @param languages - Array of languages to match against Accept-Language header
|
|
1093
|
-
* @returns {T | null} Best matching language or null if no match
|
|
1094
|
-
*/
|
|
1095
|
-
language(languages) {
|
|
1096
|
-
this.#initiateAccepts();
|
|
1097
|
-
return this.#lazyAccepts.language(languages) || null;
|
|
1098
|
-
}
|
|
1099
|
-
/**
|
|
1100
|
-
* Return the languages that the request accepts, in the order of the
|
|
1101
|
-
* client's preference (most preferred first).
|
|
1102
|
-
*
|
|
1103
|
-
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1104
|
-
* docs too.
|
|
1105
|
-
* @returns {string[]} Array of accepted languages in preference order
|
|
1106
|
-
*/
|
|
1107
|
-
languages() {
|
|
1108
|
-
this.#initiateAccepts();
|
|
1109
|
-
return this.#lazyAccepts.languages();
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* Returns the best charset using `Accept-charset` header
|
|
1113
|
-
* and by matching it against the given charsets.
|
|
1114
|
-
*
|
|
1115
|
-
* If nothing is matched, then `null` will be returned
|
|
1116
|
-
*
|
|
1117
|
-
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1118
|
-
* docs too.
|
|
1119
|
-
*
|
|
1120
|
-
* @example
|
|
1121
|
-
* ```js
|
|
1122
|
-
* switch (request.charset(['utf-8', 'ISO-8859-1'])) {
|
|
1123
|
-
* case 'utf-8':
|
|
1124
|
-
* // make utf-8 friendly response
|
|
1125
|
-
* case 'ISO-8859-1':
|
|
1126
|
-
* // make ISO-8859-1 friendly response
|
|
1127
|
-
* }
|
|
1128
|
-
* ```
|
|
1129
|
-
* @param charsets - Array of charsets to match against Accept-Charset header
|
|
1130
|
-
* @returns {T | null} Best matching charset or null if no match
|
|
1131
|
-
*/
|
|
1132
|
-
charset(charsets) {
|
|
1133
|
-
this.#initiateAccepts();
|
|
1134
|
-
return this.#lazyAccepts.charset(charsets) || null;
|
|
1135
|
-
}
|
|
1136
|
-
/**
|
|
1137
|
-
* Return the charsets that the request accepts, in the order of the
|
|
1138
|
-
* client's preference (most preferred first).
|
|
1139
|
-
*
|
|
1140
|
-
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1141
|
-
* docs too.
|
|
1142
|
-
* @returns {string[]} Array of accepted charsets in preference order
|
|
1143
|
-
*/
|
|
1144
|
-
charsets() {
|
|
1145
|
-
this.#initiateAccepts();
|
|
1146
|
-
return this.#lazyAccepts.charsets();
|
|
1147
|
-
}
|
|
1148
|
-
/**
|
|
1149
|
-
* Returns the best encoding using `Accept-encoding` header
|
|
1150
|
-
* and by matching it against the given encodings.
|
|
1151
|
-
*
|
|
1152
|
-
* If nothing is matched, then `null` will be returned
|
|
1153
|
-
*
|
|
1154
|
-
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1155
|
-
* docs too.
|
|
1156
|
-
* @param encodings - Array of encodings to match against Accept-Encoding header
|
|
1157
|
-
* @returns {T | null} Best matching encoding or null if no match
|
|
1158
|
-
*/
|
|
1159
|
-
encoding(encodings) {
|
|
1160
|
-
this.#initiateAccepts();
|
|
1161
|
-
return this.#lazyAccepts.encoding(encodings) || null;
|
|
1162
|
-
}
|
|
1163
|
-
/**
|
|
1164
|
-
* Return the encodings that the request accepts, in the order of the
|
|
1165
|
-
* client's preference (most preferred first).
|
|
1166
|
-
*
|
|
1167
|
-
* Make sure to check [accepts](https://www.npmjs.com/package/accepts) package
|
|
1168
|
-
* docs too.
|
|
1169
|
-
* @returns {string[]} Array of accepted encodings in preference order
|
|
1170
|
-
*/
|
|
1171
|
-
encodings() {
|
|
1172
|
-
this.#initiateAccepts();
|
|
1173
|
-
return this.#lazyAccepts.encodings();
|
|
1174
|
-
}
|
|
1175
|
-
/**
|
|
1176
|
-
* Returns a boolean telling if request has body
|
|
1177
|
-
* @returns {boolean} True if request contains a body
|
|
1178
|
-
*/
|
|
1179
|
-
hasBody() {
|
|
1180
|
-
return typeIs.hasBody(this.request);
|
|
1181
|
-
}
|
|
1182
|
-
/**
|
|
1183
|
-
* Returns a boolean telling if the new response etag evaluates same
|
|
1184
|
-
* as the request header `if-none-match`. In case of `true`, the
|
|
1185
|
-
* server must return `304` response, telling the browser to
|
|
1186
|
-
* use the client cache.
|
|
1187
|
-
*
|
|
1188
|
-
* You won't have to deal with this method directly, since AdonisJs will
|
|
1189
|
-
* handle this for you when `http.etag = true` inside `config/app.js` file.
|
|
1190
|
-
*
|
|
1191
|
-
* However, this is how you can use it manually.
|
|
1192
|
-
*
|
|
1193
|
-
* ```js
|
|
1194
|
-
* const responseBody = view.render('some-view')
|
|
1195
|
-
*
|
|
1196
|
-
* // sets the HTTP etag header for response
|
|
1197
|
-
* response.setEtag(responseBody)
|
|
1198
|
-
*
|
|
1199
|
-
* if (request.fresh()) {
|
|
1200
|
-
* response.sendStatus(304)
|
|
1201
|
-
* } else {
|
|
1202
|
-
* response.send(responseBody)
|
|
1203
|
-
* }
|
|
1204
|
-
* ```
|
|
1205
|
-
* @returns {boolean} True if client cache is fresh (should return 304)
|
|
1206
|
-
*/
|
|
1207
|
-
fresh() {
|
|
1208
|
-
if (["GET", "HEAD"].indexOf(this.intended()) === -1) {
|
|
1209
|
-
return false;
|
|
1210
|
-
}
|
|
1211
|
-
const status = this.response.statusCode;
|
|
1212
|
-
if (status >= 200 && status < 300 || status === 304) {
|
|
1213
|
-
return fresh(this.headers(), this.response.getHeaders());
|
|
1214
|
-
}
|
|
1215
|
-
return false;
|
|
1216
|
-
}
|
|
1217
|
-
/**
|
|
1218
|
-
* Opposite of [[fresh]]
|
|
1219
|
-
* @returns {boolean} True if client cache is stale (should send new response)
|
|
1220
|
-
*/
|
|
1221
|
-
stale() {
|
|
1222
|
-
return !this.fresh();
|
|
1223
|
-
}
|
|
1224
|
-
/**
|
|
1225
|
-
* Returns all parsed and signed cookies. Signed cookies ensures
|
|
1226
|
-
* that their value isn't tampered.
|
|
1227
|
-
* @returns {{ [key: string]: any }} Object containing all parsed cookies
|
|
1228
|
-
*/
|
|
1229
|
-
cookiesList() {
|
|
1230
|
-
this.#initiateCookieParser();
|
|
1231
|
-
return this.#cookieParser.list();
|
|
1232
|
-
}
|
|
1233
|
-
/**
|
|
1234
|
-
* Returns value for a given key from signed cookies. Optional
|
|
1235
|
-
* defaultValue is returned when actual value is undefined.
|
|
1236
|
-
* @param key - Cookie name to lookup
|
|
1237
|
-
* @param defaultValue - Default value when cookie is not found
|
|
1238
|
-
* @returns Cookie value or default value if not found
|
|
1239
|
-
*/
|
|
1240
|
-
cookie(key, defaultValue) {
|
|
1241
|
-
this.#initiateCookieParser();
|
|
1242
|
-
return this.#cookieParser.unsign(key) || defaultValue;
|
|
1243
|
-
}
|
|
1244
|
-
/**
|
|
1245
|
-
* Returns value for a given key from encrypted cookies. Optional
|
|
1246
|
-
* defaultValue is returned when actual value is undefined.
|
|
1247
|
-
* @param key - Cookie name to lookup
|
|
1248
|
-
* @param defaultValue - Default value when cookie is not found
|
|
1249
|
-
* @returns Decrypted cookie value or default value if not found
|
|
1250
|
-
*/
|
|
1251
|
-
encryptedCookie(key, defaultValue) {
|
|
1252
|
-
this.#initiateCookieParser();
|
|
1253
|
-
return this.#cookieParser.decrypt(key) || defaultValue;
|
|
1254
|
-
}
|
|
1255
|
-
plainCookie(key, defaultValueOrOptions, encoded) {
|
|
1256
|
-
this.#initiateCookieParser();
|
|
1257
|
-
if (is.object(defaultValueOrOptions)) {
|
|
1258
|
-
return this.#cookieParser.decode(key, defaultValueOrOptions?.encoded) || defaultValueOrOptions.defaultValue;
|
|
1259
|
-
}
|
|
1260
|
-
return this.#cookieParser.decode(key, encoded) || defaultValueOrOptions;
|
|
1261
|
-
}
|
|
1262
|
-
/**
|
|
1263
|
-
* Returns a boolean telling if a signed url has a valid signature
|
|
1264
|
-
* or not.
|
|
1265
|
-
* @param purpose - Optional purpose for signature verification
|
|
1266
|
-
* @returns {boolean} True if the signed URL has a valid signature
|
|
1267
|
-
*/
|
|
1268
|
-
hasValidSignature(purpose) {
|
|
1269
|
-
const { signature, ...rest } = this.qs();
|
|
1270
|
-
if (!signature) {
|
|
1271
|
-
return false;
|
|
1272
|
-
}
|
|
1273
|
-
const signedUrl = this.#encryption.verifier.unsign(signature, purpose);
|
|
1274
|
-
if (!signedUrl) {
|
|
1275
|
-
return false;
|
|
1276
|
-
}
|
|
1277
|
-
const queryString = this.#qsParser.stringify(rest);
|
|
1278
|
-
return queryString ? safeEqual(signedUrl, `${this.url()}?${queryString}`) : safeEqual(signedUrl, this.url());
|
|
1279
|
-
}
|
|
1280
|
-
/**
|
|
1281
|
-
* Serializes request to JSON format
|
|
1282
|
-
* @returns Object representation of the request
|
|
1283
|
-
*/
|
|
1284
|
-
serialize() {
|
|
1285
|
-
return {
|
|
1286
|
-
id: this.id(),
|
|
1287
|
-
url: this.url(),
|
|
1288
|
-
query: this.parsedUrl.query,
|
|
1289
|
-
body: this.all(),
|
|
1290
|
-
params: this.params(),
|
|
1291
|
-
headers: this.headers(),
|
|
1292
|
-
method: this.method(),
|
|
1293
|
-
protocol: this.protocol(),
|
|
1294
|
-
cookies: this.cookiesList(),
|
|
1295
|
-
hostname: this.hostname(),
|
|
1296
|
-
ip: this.ip(),
|
|
1297
|
-
subdomains: this.ctx?.subdomains || {}
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
/**
|
|
1301
|
-
* toJSON copy of the request
|
|
1302
|
-
* @returns JSON representation of the request
|
|
1303
|
-
*/
|
|
1304
|
-
toJSON() {
|
|
1305
|
-
return this.serialize();
|
|
1306
|
-
}
|
|
1307
|
-
};
|
|
1308
|
-
|
|
1309
|
-
// src/redirect.ts
|
|
1310
|
-
var Redirect = class {
|
|
1311
|
-
/**
|
|
1312
|
-
* Flag indicating whether to forward the existing query string from the current request
|
|
1313
|
-
*/
|
|
1314
|
-
#forwardQueryString = false;
|
|
1315
|
-
/**
|
|
1316
|
-
* HTTP status code to use for the redirect response (defaults to 302)
|
|
1317
|
-
*/
|
|
1318
|
-
#statusCode = 302;
|
|
1319
|
-
/**
|
|
1320
|
-
* Custom query string parameters to include in the redirect URL
|
|
1321
|
-
*/
|
|
1322
|
-
#queryString = {};
|
|
1323
|
-
/**
|
|
1324
|
-
* Reference to the Node.js incoming HTTP request
|
|
1325
|
-
*/
|
|
1326
|
-
#request;
|
|
1327
|
-
/**
|
|
1328
|
-
* Reference to the AdonisJS response instance
|
|
1329
|
-
*/
|
|
1330
|
-
#response;
|
|
1331
|
-
/**
|
|
1332
|
-
* Reference to the AdonisJS router instance for URL building
|
|
1333
|
-
*/
|
|
1334
|
-
#router;
|
|
1335
|
-
/**
|
|
1336
|
-
* Query string parser instance
|
|
1337
|
-
*/
|
|
1338
|
-
#qs;
|
|
1339
|
-
/**
|
|
1340
|
-
* Creates a new Redirect instance for handling HTTP redirects
|
|
1341
|
-
* @param request - Node.js incoming HTTP request
|
|
1342
|
-
* @param response - AdonisJS response instance
|
|
1343
|
-
* @param router - AdonisJS router instance
|
|
1344
|
-
* @param qs - Query string parser instance
|
|
1345
|
-
*/
|
|
1346
|
-
constructor(request, response, router, qs) {
|
|
1347
|
-
this.#request = request;
|
|
1348
|
-
this.#response = response;
|
|
1349
|
-
this.#router = router;
|
|
1350
|
-
this.#qs = qs;
|
|
1351
|
-
}
|
|
1352
|
-
/**
|
|
1353
|
-
* Sends the redirect response by setting required headers and status code
|
|
1354
|
-
* @param url - Target URL for redirection
|
|
1355
|
-
* @param query - Query string parameters to append
|
|
1356
|
-
*/
|
|
1357
|
-
#sendResponse(url, query) {
|
|
1358
|
-
const stringified = this.#qs.stringify(query);
|
|
1359
|
-
url = stringified ? `${url}?${stringified}` : url;
|
|
1360
|
-
debug_default('redirecting to url "%s"', url);
|
|
1361
|
-
this.#response.location(default2(url));
|
|
1362
|
-
this.#response.safeStatus(this.#statusCode);
|
|
1363
|
-
this.#response.type("text/plain; charset=utf-8");
|
|
1364
|
-
this.#response.send(`Redirecting to ${url}`);
|
|
1365
|
-
}
|
|
1366
|
-
/**
|
|
1367
|
-
* Extracts and returns the referrer URL from request headers
|
|
1368
|
-
* @returns {string} The referrer URL or '/' if not found
|
|
1369
|
-
*/
|
|
1370
|
-
#getReferrerUrl() {
|
|
1371
|
-
let url = this.#request.headers["referer"] || this.#request.headers["referrer"] || "/";
|
|
1372
|
-
return Array.isArray(url) ? url[0] : url;
|
|
1373
|
-
}
|
|
1374
|
-
/**
|
|
1375
|
-
* Sets a custom HTTP status code for the redirect response
|
|
1376
|
-
* @param statusCode - HTTP status code to use (e.g., 301, 302, 307)
|
|
1377
|
-
* @returns {this} The Redirect instance for method chaining
|
|
1378
|
-
*/
|
|
1379
|
-
status(statusCode) {
|
|
1380
|
-
this.#statusCode = statusCode;
|
|
1381
|
-
return this;
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Clears any query string values previously added using the withQs method
|
|
1385
|
-
* @returns {this} The Redirect instance for method chaining
|
|
1386
|
-
*/
|
|
1387
|
-
clearQs() {
|
|
1388
|
-
this.#forwardQueryString = false;
|
|
1389
|
-
this.#queryString = {};
|
|
1390
|
-
return this;
|
|
1391
|
-
}
|
|
1392
|
-
withQs(name, value) {
|
|
1393
|
-
if (typeof name === "undefined") {
|
|
1394
|
-
this.#forwardQueryString = true;
|
|
1395
|
-
return this;
|
|
1396
|
-
}
|
|
1397
|
-
if (typeof name === "string") {
|
|
1398
|
-
this.#queryString[name] = value;
|
|
1399
|
-
return this;
|
|
1400
|
-
}
|
|
1401
|
-
Object.assign(this.#queryString, name);
|
|
1402
|
-
return this;
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Redirects to the previous path using the Referer header
|
|
1406
|
-
* Falls back to '/' if no referrer is found
|
|
1407
|
-
*/
|
|
1408
|
-
back() {
|
|
1409
|
-
let query = {};
|
|
1410
|
-
const referrerUrl = this.#getReferrerUrl();
|
|
1411
|
-
const url = safeDecodeURI(referrerUrl, false);
|
|
1412
|
-
debug_default('referrer url "%s"', referrerUrl);
|
|
1413
|
-
debug_default('referrer base url "%s"', url.pathname);
|
|
1414
|
-
if (this.#forwardQueryString) {
|
|
1415
|
-
query = this.#qs.parse(url.query || "");
|
|
1416
|
-
}
|
|
1417
|
-
Object.assign(query, this.#queryString);
|
|
1418
|
-
this.#sendResponse(url.pathname || "", query);
|
|
1419
|
-
}
|
|
1420
|
-
/**
|
|
1421
|
-
* Redirects to a route using its identifier (name, pattern, or handler reference)
|
|
1422
|
-
* @param args - Route identifier, parameters, and options for URL building
|
|
1423
|
-
*/
|
|
1424
|
-
toRoute(...args) {
|
|
1425
|
-
const [identifier, params, options] = args;
|
|
1426
|
-
if (options && options.qs) {
|
|
1427
|
-
this.withQs(options.qs);
|
|
1428
|
-
options.qs = void 0;
|
|
1429
|
-
}
|
|
1430
|
-
const url = this.#router.urlBuilder.urlFor(identifier, params, options);
|
|
1431
|
-
return this.toPath(url);
|
|
1432
|
-
}
|
|
1433
|
-
/**
|
|
1434
|
-
* Redirects to a specific URL path
|
|
1435
|
-
* @param url - Target URL path for redirection
|
|
1436
|
-
*/
|
|
1437
|
-
toPath(url) {
|
|
1438
|
-
let query = {};
|
|
1439
|
-
if (this.#forwardQueryString) {
|
|
1440
|
-
query = this.#qs.parse(safeDecodeURI(this.#request.url, false).query || "");
|
|
1441
|
-
}
|
|
1442
|
-
Object.assign(query, this.#queryString);
|
|
1443
|
-
this.#sendResponse(url, query);
|
|
1444
|
-
}
|
|
1445
|
-
};
|
|
1446
|
-
|
|
1447
|
-
// src/response_status.ts
|
|
1448
|
-
var ResponseStatus = {
|
|
1449
|
-
Continue: 100,
|
|
1450
|
-
SwitchingProtocols: 101,
|
|
1451
|
-
Processing: 102,
|
|
1452
|
-
EarlyHints: 103,
|
|
1453
|
-
Ok: 200,
|
|
1454
|
-
Created: 201,
|
|
1455
|
-
Accepted: 202,
|
|
1456
|
-
NonAuthoritativeInformation: 203,
|
|
1457
|
-
NoContent: 204,
|
|
1458
|
-
ResetContent: 205,
|
|
1459
|
-
PartialContent: 206,
|
|
1460
|
-
MultiStatus: 207,
|
|
1461
|
-
AlreadyReported: 208,
|
|
1462
|
-
IMUsed: 226,
|
|
1463
|
-
MultipleChoices: 300,
|
|
1464
|
-
MovedPermanently: 301,
|
|
1465
|
-
Found: 302,
|
|
1466
|
-
SeeOther: 303,
|
|
1467
|
-
NotModified: 304,
|
|
1468
|
-
UseProxy: 305,
|
|
1469
|
-
TemporaryRedirect: 307,
|
|
1470
|
-
PermanentRedirect: 308,
|
|
1471
|
-
BadRequest: 400,
|
|
1472
|
-
Unauthorized: 401,
|
|
1473
|
-
PaymentRequired: 402,
|
|
1474
|
-
Forbidden: 403,
|
|
1475
|
-
NotFound: 404,
|
|
1476
|
-
MethodNotAllowed: 405,
|
|
1477
|
-
NotAcceptable: 406,
|
|
1478
|
-
ProxyAuthenticationRequired: 407,
|
|
1479
|
-
RequestTimeout: 408,
|
|
1480
|
-
Conflict: 409,
|
|
1481
|
-
Gone: 410,
|
|
1482
|
-
LengthRequired: 411,
|
|
1483
|
-
PreconditionFailed: 412,
|
|
1484
|
-
PayloadTooLarge: 413,
|
|
1485
|
-
URITooLong: 414,
|
|
1486
|
-
UnsupportedMediaType: 415,
|
|
1487
|
-
RangeNotSatisfiable: 416,
|
|
1488
|
-
ExpectationFailed: 417,
|
|
1489
|
-
ImATeapot: 418,
|
|
1490
|
-
MisdirectedRequest: 421,
|
|
1491
|
-
UnprocessableEntity: 422,
|
|
1492
|
-
Locked: 423,
|
|
1493
|
-
FailedDependency: 424,
|
|
1494
|
-
TooEarly: 425,
|
|
1495
|
-
UpgradeRequired: 426,
|
|
1496
|
-
PreconditionRequired: 428,
|
|
1497
|
-
TooManyRequests: 429,
|
|
1498
|
-
RequestHeaderFieldsTooLarge: 431,
|
|
1499
|
-
UnavailableForLegalReasons: 451,
|
|
1500
|
-
InternalServerError: 500,
|
|
1501
|
-
NotImplemented: 501,
|
|
1502
|
-
BadGateway: 502,
|
|
1503
|
-
ServiceUnavailable: 503,
|
|
1504
|
-
GatewayTimeout: 504,
|
|
1505
|
-
HTTPVersionNotSupported: 505,
|
|
1506
|
-
VariantAlsoNegotiates: 506,
|
|
1507
|
-
InsufficientStorage: 507,
|
|
1508
|
-
LoopDetected: 508,
|
|
1509
|
-
NotExtended: 510,
|
|
1510
|
-
NetworkAuthenticationRequired: 511
|
|
1511
|
-
};
|
|
1512
|
-
|
|
1513
|
-
// src/cookies/serializer.ts
|
|
1514
|
-
var CookieSerializer = class {
|
|
1515
|
-
/**
|
|
1516
|
-
* Cookie client instance for handling cookie operations
|
|
1517
|
-
*/
|
|
1518
|
-
#client;
|
|
1519
|
-
/**
|
|
1520
|
-
* Create a new instance of CookieSerializer
|
|
1521
|
-
*
|
|
1522
|
-
* @param encryption - The encryption instance for cookie operations
|
|
1523
|
-
*/
|
|
1524
|
-
constructor(encryption) {
|
|
1525
|
-
this.#client = new CookieClient(encryption);
|
|
1526
|
-
}
|
|
1527
|
-
/**
|
|
1528
|
-
* Encodes value as a plain cookie. By default, the plain value will be converted
|
|
1529
|
-
* to a string using "JSON.stringify" method and then encoded as a base64 string.
|
|
1530
|
-
*
|
|
1531
|
-
* You can disable cookie stringifaction by setting `options.stringify = false`.
|
|
1532
|
-
*
|
|
1533
|
-
* ```ts
|
|
1534
|
-
* serializer.encode('name', 'virk')
|
|
1535
|
-
* serializer.encode('name', 'virk', { stringify: false })
|
|
1536
|
-
* ```
|
|
1537
|
-
*
|
|
1538
|
-
* @param key - The cookie key
|
|
1539
|
-
* @param value - The value to encode
|
|
1540
|
-
* @param options - Cookie encoding options
|
|
1541
|
-
* @returns The serialized cookie string or null if encoding fails
|
|
1542
|
-
*/
|
|
1543
|
-
encode(key, value, options) {
|
|
1544
|
-
const stringify2 = options?.stringify ?? options?.encode;
|
|
1545
|
-
const packedValue = this.#client.encode(key, value, stringify2);
|
|
1546
|
-
if (packedValue === null || packedValue === void 0) {
|
|
1547
|
-
return null;
|
|
1548
|
-
}
|
|
1549
|
-
return serializeCookie(key, packedValue, options);
|
|
1550
|
-
}
|
|
1551
|
-
/**
|
|
1552
|
-
* Sign a key-value pair to a signed cookie. The signed value has a
|
|
1553
|
-
* verification hash attached to it to detect data tampering.
|
|
1554
|
-
*
|
|
1555
|
-
* @param key - The cookie key
|
|
1556
|
-
* @param value - The value to sign
|
|
1557
|
-
* @param options - Cookie options
|
|
1558
|
-
* @returns The serialized signed cookie string or null if signing fails
|
|
1559
|
-
*/
|
|
1560
|
-
sign(key, value, options) {
|
|
1561
|
-
const packedValue = this.#client.sign(key, value);
|
|
1562
|
-
if (packedValue === null) {
|
|
1563
|
-
return null;
|
|
1564
|
-
}
|
|
1565
|
-
return serializeCookie(key, packedValue, options);
|
|
1566
|
-
}
|
|
1567
|
-
/**
|
|
1568
|
-
* Encrypts a key-value pair to an encrypted cookie.
|
|
1569
|
-
*
|
|
1570
|
-
* @param key - The cookie key
|
|
1571
|
-
* @param value - The value to encrypt
|
|
1572
|
-
* @param options - Cookie options
|
|
1573
|
-
* @returns The serialized encrypted cookie string or null if encryption fails
|
|
1574
|
-
*/
|
|
1575
|
-
encrypt(key, value, options) {
|
|
1576
|
-
const packedValue = this.#client.encrypt(key, value);
|
|
1577
|
-
if (packedValue === null) {
|
|
1578
|
-
return null;
|
|
1579
|
-
}
|
|
1580
|
-
return serializeCookie(key, packedValue, options);
|
|
1581
|
-
}
|
|
1582
|
-
};
|
|
1583
|
-
|
|
1584
|
-
// src/response.ts
|
|
1585
|
-
import etag from "etag";
|
|
1586
|
-
import vary from "vary";
|
|
1587
|
-
import fresh2 from "fresh";
|
|
1588
|
-
import destroy from "destroy";
|
|
1589
|
-
import { extname } from "path";
|
|
1590
|
-
import { Buffer } from "buffer";
|
|
1591
|
-
import onFinished from "on-finished";
|
|
1592
|
-
import { stat } from "fs/promises";
|
|
1593
|
-
import Macroable2 from "@poppinss/macroable";
|
|
1594
|
-
import { createReadStream } from "fs";
|
|
1595
|
-
import contentDisposition from "content-disposition";
|
|
1596
|
-
import { safeStringify } from "@poppinss/utils/json";
|
|
1597
|
-
import { RuntimeException } from "@poppinss/utils/exception";
|
|
1598
|
-
var CACHEABLE_HTTP_METHODS = ["GET", "HEAD"];
|
|
1599
|
-
var Response = class extends Macroable2 {
|
|
1600
|
-
/**
|
|
1601
|
-
* Creates a new Response instance
|
|
1602
|
-
*
|
|
1603
|
-
* @param request - Node.js IncomingMessage instance
|
|
1604
|
-
* @param response - Node.js ServerResponse instance
|
|
1605
|
-
* @param encryption - Encryption service for cookie handling
|
|
1606
|
-
* @param config - Response configuration settings
|
|
1607
|
-
* @param router - Router instance for URL generation
|
|
1608
|
-
* @param qs - Query string parser
|
|
1609
|
-
*/
|
|
1610
|
-
constructor(request, response, encryption, config, router, qs) {
|
|
1611
|
-
super();
|
|
1612
|
-
this.request = request;
|
|
1613
|
-
this.response = response;
|
|
1614
|
-
this.#qs = qs;
|
|
1615
|
-
this.#config = config;
|
|
1616
|
-
this.#router = router;
|
|
1617
|
-
this.#cookieSerializer = new CookieSerializer(encryption);
|
|
1618
|
-
}
|
|
1619
|
-
/**
|
|
1620
|
-
* Query string parser instance used for URL manipulation
|
|
1621
|
-
*/
|
|
1622
|
-
#qs;
|
|
1623
|
-
/**
|
|
1624
|
-
* Collection of outgoing HTTP headers to be sent with the response
|
|
1625
|
-
*/
|
|
1626
|
-
#headers = {};
|
|
1627
|
-
/**
|
|
1628
|
-
* Flag indicating whether an explicit status code has been set
|
|
1629
|
-
*/
|
|
1630
|
-
#hasExplicitStatus = false;
|
|
1631
|
-
/**
|
|
1632
|
-
* Cookie serializer instance for handling cookie encryption, signing, and encoding
|
|
1633
|
-
*/
|
|
1634
|
-
#cookieSerializer;
|
|
1635
|
-
/**
|
|
1636
|
-
* Router instance used for generating redirect URLs from route definitions
|
|
1637
|
-
*/
|
|
1638
|
-
#router;
|
|
1639
|
-
/**
|
|
1640
|
-
* Configuration object containing response-related settings
|
|
1641
|
-
*/
|
|
1642
|
-
#config;
|
|
1643
|
-
/**
|
|
1644
|
-
* Indicates whether the response has any content (body, stream, or file) ready to be sent
|
|
1645
|
-
*/
|
|
1646
|
-
get hasLazyBody() {
|
|
1647
|
-
return !!(this.lazyBody.content || this.lazyBody.fileToStream || this.lazyBody.stream);
|
|
1648
|
-
}
|
|
1649
|
-
/**
|
|
1650
|
-
* Indicates whether the response has non-stream content set
|
|
1651
|
-
*/
|
|
1652
|
-
get hasContent() {
|
|
1653
|
-
return !!this.lazyBody.content;
|
|
1654
|
-
}
|
|
1655
|
-
/**
|
|
1656
|
-
* Indicates whether the response body is set as a readable stream
|
|
1657
|
-
*/
|
|
1658
|
-
get hasStream() {
|
|
1659
|
-
return !!this.lazyBody.stream;
|
|
1660
|
-
}
|
|
1661
|
-
/**
|
|
1662
|
-
* Indicates whether the response is configured to stream a file
|
|
1663
|
-
*/
|
|
1664
|
-
get hasFileToStream() {
|
|
1665
|
-
return !!this.lazyBody.fileToStream;
|
|
1666
|
-
}
|
|
1667
|
-
/**
|
|
1668
|
-
* The response content data
|
|
1669
|
-
*/
|
|
1670
|
-
get content() {
|
|
1671
|
-
return this.lazyBody.content;
|
|
1672
|
-
}
|
|
1673
|
-
/**
|
|
1674
|
-
* The readable stream instance configured for the response
|
|
1675
|
-
*/
|
|
1676
|
-
get outgoingStream() {
|
|
1677
|
-
return this.lazyBody.stream?.[0];
|
|
1678
|
-
}
|
|
1679
|
-
/**
|
|
1680
|
-
* Configuration for file streaming including path and etag generation flag
|
|
1681
|
-
*/
|
|
1682
|
-
get fileToStream() {
|
|
1683
|
-
return this.lazyBody.fileToStream ? {
|
|
1684
|
-
path: this.lazyBody.fileToStream[0],
|
|
1685
|
-
generateEtag: this.lazyBody.fileToStream[1]
|
|
1686
|
-
} : void 0;
|
|
1687
|
-
}
|
|
1688
|
-
/**
|
|
1689
|
-
* Lazy body container that holds response content until ready to send.
|
|
1690
|
-
* Contains different types of response data: content, stream, or fileToStream.
|
|
1691
|
-
*/
|
|
1692
|
-
lazyBody = {};
|
|
1693
|
-
/**
|
|
1694
|
-
* HTTP context reference (creates circular dependency with HttpContext)
|
|
1695
|
-
*/
|
|
1696
|
-
ctx;
|
|
1697
|
-
/**
|
|
1698
|
-
* Indicates whether the response has been completely sent
|
|
1699
|
-
*/
|
|
1700
|
-
get finished() {
|
|
1701
|
-
return this.response.writableFinished;
|
|
1702
|
-
}
|
|
1703
|
-
/**
|
|
1704
|
-
* Indicates whether response headers have been sent to the client
|
|
1705
|
-
*/
|
|
1706
|
-
get headersSent() {
|
|
1707
|
-
return this.response.headersSent;
|
|
1708
|
-
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Indicates whether the response is still pending (headers and body can still be modified)
|
|
1711
|
-
*/
|
|
1712
|
-
get isPending() {
|
|
1713
|
-
return !this.headersSent && !this.finished;
|
|
1714
|
-
}
|
|
1715
|
-
/**
|
|
1716
|
-
* Normalizes header value to a string or an array of strings
|
|
1717
|
-
*
|
|
1718
|
-
* @param value - The header value to normalize
|
|
1719
|
-
* @returns Normalized header value
|
|
1720
|
-
*/
|
|
1721
|
-
#castHeaderValue(value) {
|
|
1722
|
-
return Array.isArray(value) ? value.map(String) : String(value);
|
|
1723
|
-
}
|
|
1724
|
-
/**
|
|
1725
|
-
* Ends the response by flushing headers and writing body
|
|
1726
|
-
*
|
|
1727
|
-
* @param body - Optional response body
|
|
1728
|
-
* @param statusCode - Optional status code
|
|
1729
|
-
*/
|
|
1730
|
-
#endResponse(body, statusCode) {
|
|
1731
|
-
this.writeHead(statusCode);
|
|
1732
|
-
const res = this.response;
|
|
1733
|
-
res.end(body, null, null);
|
|
1734
|
-
}
|
|
1735
|
-
/**
|
|
1736
|
-
* Determines the data type of the content for serialization
|
|
1737
|
-
*
|
|
1738
|
-
* Supported types:
|
|
1739
|
-
* - Dates
|
|
1740
|
-
* - Arrays
|
|
1741
|
-
* - Booleans
|
|
1742
|
-
* - Objects
|
|
1743
|
-
* - Strings
|
|
1744
|
-
* - Buffer
|
|
1745
|
-
*
|
|
1746
|
-
* @param content - The content to analyze
|
|
1747
|
-
* @returns The determined data type as string
|
|
1748
|
-
*/
|
|
1749
|
-
#getDataType(content) {
|
|
1750
|
-
const dataType = typeof content;
|
|
1751
|
-
if (dataType === "number" || dataType === "boolean" || dataType === "string" || dataType === "bigint") {
|
|
1752
|
-
return dataType;
|
|
1753
|
-
}
|
|
1754
|
-
if (dataType === "object") {
|
|
1755
|
-
if (content instanceof Uint8Array) {
|
|
1756
|
-
return "buffer";
|
|
1757
|
-
}
|
|
1758
|
-
if (content instanceof RegExp) {
|
|
1759
|
-
return "regexp";
|
|
1760
|
-
}
|
|
1761
|
-
if (content instanceof Date) {
|
|
1762
|
-
return "date";
|
|
1763
|
-
}
|
|
1764
|
-
return "object";
|
|
1765
|
-
}
|
|
1766
|
-
throw new RuntimeException(`Cannot serialize "${dataType}" to HTTP response`);
|
|
1767
|
-
}
|
|
1768
|
-
/**
|
|
1769
|
-
* Writes the response body with appropriate headers and content type detection
|
|
1770
|
-
*
|
|
1771
|
-
* Automatically sets:
|
|
1772
|
-
* - Content-Type based on content analysis
|
|
1773
|
-
* - Content-Length header
|
|
1774
|
-
* - ETag header (if enabled)
|
|
1775
|
-
* - Status code 204 for empty bodies
|
|
1776
|
-
*
|
|
1777
|
-
* @param content - The response content
|
|
1778
|
-
* @param generateEtag - Whether to generate ETag header
|
|
1779
|
-
* @param jsonpCallbackName - Optional JSONP callback name
|
|
1780
|
-
*/
|
|
1781
|
-
writeBody(content, generateEtag, jsonpCallbackName) {
|
|
1782
|
-
const hasEmptyBody = content === null || content === void 0 || content === "";
|
|
1783
|
-
if (hasEmptyBody) {
|
|
1784
|
-
this.safeStatus(204);
|
|
1785
|
-
}
|
|
1786
|
-
const statusCode = this.response.statusCode;
|
|
1787
|
-
if (statusCode && (statusCode < ResponseStatus.Ok || statusCode === ResponseStatus.NoContent || statusCode === ResponseStatus.NotModified)) {
|
|
1788
|
-
this.removeHeader("Content-Type");
|
|
1789
|
-
this.removeHeader("Content-Length");
|
|
1790
|
-
this.removeHeader("Transfer-Encoding");
|
|
1791
|
-
this.#endResponse();
|
|
1792
|
-
return;
|
|
1793
|
-
}
|
|
1794
|
-
if (hasEmptyBody) {
|
|
1795
|
-
this.removeHeader("Content-Length");
|
|
1796
|
-
this.#endResponse();
|
|
1797
|
-
return;
|
|
1798
|
-
}
|
|
1799
|
-
const dataType = this.#getDataType(content);
|
|
1800
|
-
let contentType;
|
|
1801
|
-
switch (dataType) {
|
|
1802
|
-
case "string":
|
|
1803
|
-
contentType = content.trimStart().startsWith("<") ? "text/html; charset=utf-8" : "text/plain; charset=utf-8";
|
|
1804
|
-
break;
|
|
1805
|
-
case "number":
|
|
1806
|
-
case "boolean":
|
|
1807
|
-
case "bigint":
|
|
1808
|
-
case "regexp":
|
|
1809
|
-
content = String(content);
|
|
1810
|
-
contentType = "text/plain; charset=utf-8";
|
|
1811
|
-
break;
|
|
1812
|
-
case "date":
|
|
1813
|
-
content = content.toISOString();
|
|
1814
|
-
contentType = "text/plain; charset=utf-8";
|
|
1815
|
-
break;
|
|
1816
|
-
case "buffer":
|
|
1817
|
-
contentType = "application/octet-stream; charset=utf-8";
|
|
1818
|
-
break;
|
|
1819
|
-
case "object":
|
|
1820
|
-
content = safeStringify(content);
|
|
1821
|
-
contentType = "application/json; charset=utf-8";
|
|
1822
|
-
break;
|
|
1823
|
-
}
|
|
1824
|
-
if (jsonpCallbackName) {
|
|
1825
|
-
content = content.replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1826
|
-
content = `/**/ typeof ${jsonpCallbackName} === 'function' && ${jsonpCallbackName}(${content});`;
|
|
1827
|
-
}
|
|
1828
|
-
if (generateEtag) {
|
|
1829
|
-
this.setEtag(content);
|
|
1830
|
-
}
|
|
1831
|
-
if (generateEtag && this.fresh()) {
|
|
1832
|
-
this.removeHeader("Content-Type");
|
|
1833
|
-
this.removeHeader("Content-Length");
|
|
1834
|
-
this.removeHeader("Transfer-Encoding");
|
|
1835
|
-
this.#endResponse(null, ResponseStatus.NotModified);
|
|
1836
|
-
return;
|
|
1837
|
-
}
|
|
1838
|
-
this.setRequestId();
|
|
1839
|
-
this.header("Content-Length", Buffer.byteLength(content));
|
|
1840
|
-
if (jsonpCallbackName) {
|
|
1841
|
-
this.header("X-Content-Type-Options", "nosniff");
|
|
1842
|
-
this.safeHeader("Content-Type", "text/javascript; charset=utf-8");
|
|
1843
|
-
} else {
|
|
1844
|
-
this.safeHeader("Content-type", contentType);
|
|
1845
|
-
}
|
|
1846
|
-
this.#endResponse(content);
|
|
1847
|
-
}
|
|
1848
|
-
/**
|
|
1849
|
-
* Streams the response body and handles error cleanup
|
|
1850
|
-
*
|
|
1851
|
-
* Manages stream lifecycle including:
|
|
1852
|
-
* - Error handling with custom callbacks
|
|
1853
|
-
* - Proper stream cleanup to prevent memory leaks
|
|
1854
|
-
* - Response finalization
|
|
1855
|
-
*
|
|
1856
|
-
* @param body - The readable stream to pipe
|
|
1857
|
-
* @param errorCallback - Optional custom error handler
|
|
1858
|
-
* @returns Promise that resolves when streaming is complete
|
|
1859
|
-
*/
|
|
1860
|
-
streamBody(body, errorCallback) {
|
|
1861
|
-
return new Promise((resolve) => {
|
|
1862
|
-
let finished = false;
|
|
1863
|
-
body.on("error", (error) => {
|
|
1864
|
-
if (finished) {
|
|
1865
|
-
return;
|
|
1866
|
-
}
|
|
1867
|
-
finished = true;
|
|
1868
|
-
destroy(body);
|
|
1869
|
-
this.type("text");
|
|
1870
|
-
if (!this.headersSent) {
|
|
1871
|
-
if (typeof errorCallback === "function") {
|
|
1872
|
-
this.#endResponse(...errorCallback(error));
|
|
1873
|
-
} else {
|
|
1874
|
-
this.#endResponse(
|
|
1875
|
-
error.code === "ENOENT" ? "File not found" : "Cannot process file",
|
|
1876
|
-
error.code === "ENOENT" ? ResponseStatus.NotFound : ResponseStatus.InternalServerError
|
|
1877
|
-
);
|
|
1878
|
-
}
|
|
1879
|
-
} else {
|
|
1880
|
-
this.response.destroy();
|
|
1881
|
-
}
|
|
1882
|
-
resolve();
|
|
1883
|
-
});
|
|
1884
|
-
body.on("end", () => {
|
|
1885
|
-
if (!this.headersSent) {
|
|
1886
|
-
this.#endResponse();
|
|
1887
|
-
}
|
|
1888
|
-
resolve();
|
|
1889
|
-
});
|
|
1890
|
-
onFinished(this.response, () => {
|
|
1891
|
-
finished = true;
|
|
1892
|
-
destroy(body);
|
|
1893
|
-
});
|
|
1894
|
-
this.relayHeaders();
|
|
1895
|
-
body.pipe(this.response);
|
|
1896
|
-
});
|
|
1897
|
-
}
|
|
1898
|
-
/**
|
|
1899
|
-
* Streams a file for download with proper headers and caching support
|
|
1900
|
-
*
|
|
1901
|
-
* Sets appropriate headers:
|
|
1902
|
-
* - Last-Modified based on file stats
|
|
1903
|
-
* - Content-Type based on file extension
|
|
1904
|
-
* - Content-Length from file size
|
|
1905
|
-
* - ETag (if enabled)
|
|
1906
|
-
*
|
|
1907
|
-
* Handles HEAD requests and cache validation (304 responses).
|
|
1908
|
-
*
|
|
1909
|
-
* @param filePath - Path to the file to stream
|
|
1910
|
-
* @param generateEtag - Whether to generate ETag header
|
|
1911
|
-
* @param errorCallback - Optional custom error handler
|
|
1912
|
-
*/
|
|
1913
|
-
async streamFileForDownload(filePath, generateEtag, errorCallback) {
|
|
1914
|
-
try {
|
|
1915
|
-
const stats = await stat(filePath);
|
|
1916
|
-
if (!stats || !stats.isFile()) {
|
|
1917
|
-
throw new TypeError("response.download only accepts path to a file");
|
|
1918
|
-
}
|
|
1919
|
-
this.header("Last-Modified", stats.mtime.toUTCString());
|
|
1920
|
-
this.type(extname(filePath));
|
|
1921
|
-
if (generateEtag) {
|
|
1922
|
-
this.setEtag(stats, true);
|
|
1923
|
-
}
|
|
1924
|
-
if (this.request.method === "HEAD") {
|
|
1925
|
-
this.#endResponse(
|
|
1926
|
-
null,
|
|
1927
|
-
generateEtag && this.fresh() ? ResponseStatus.NotModified : ResponseStatus.Ok
|
|
1928
|
-
);
|
|
1929
|
-
return;
|
|
1930
|
-
}
|
|
1931
|
-
if (generateEtag && this.fresh()) {
|
|
1932
|
-
this.#endResponse(null, ResponseStatus.NotModified);
|
|
1933
|
-
return;
|
|
1934
|
-
}
|
|
1935
|
-
this.header("Content-length", stats.size);
|
|
1936
|
-
return this.streamBody(createReadStream(filePath), errorCallback);
|
|
1937
|
-
} catch (error) {
|
|
1938
|
-
this.type("text");
|
|
1939
|
-
this.removeHeader("Etag");
|
|
1940
|
-
if (typeof errorCallback === "function") {
|
|
1941
|
-
this.#endResponse(...errorCallback(error));
|
|
1942
|
-
} else {
|
|
1943
|
-
this.#endResponse(
|
|
1944
|
-
error.code === "ENOENT" ? "File not found" : "Cannot process file",
|
|
1945
|
-
error.code === "ENOENT" ? ResponseStatus.NotFound : ResponseStatus.InternalServerError
|
|
1946
|
-
);
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
1950
|
-
/**
|
|
1951
|
-
* Registers a callback to be called when the response is finished
|
|
1952
|
-
*
|
|
1953
|
-
* The callback is executed when the response has been completely sent.
|
|
1954
|
-
* Uses the "on-finished" package internally.
|
|
1955
|
-
*
|
|
1956
|
-
* @param callback - Function to call when response is finished
|
|
1957
|
-
*/
|
|
1958
|
-
onFinish(callback) {
|
|
1959
|
-
onFinished(this.response, callback);
|
|
1960
|
-
}
|
|
1961
|
-
/**
|
|
1962
|
-
* Transfers all buffered headers to the underlying Node.js response object
|
|
1963
|
-
*/
|
|
1964
|
-
relayHeaders() {
|
|
1965
|
-
if (!this.headersSent) {
|
|
1966
|
-
for (let key in this.#headers) {
|
|
1967
|
-
const value = this.#headers[key];
|
|
1968
|
-
if (value) {
|
|
1969
|
-
this.response.setHeader(key, value);
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
/**
|
|
1975
|
-
* Writes the response status code and headers
|
|
1976
|
-
*
|
|
1977
|
-
* @param statusCode - Optional status code to set
|
|
1978
|
-
* @returns The Response instance for chaining
|
|
1979
|
-
*/
|
|
1980
|
-
writeHead(statusCode) {
|
|
1981
|
-
this.response.writeHead(statusCode || this.response.statusCode, this.#headers);
|
|
1982
|
-
return this;
|
|
1983
|
-
}
|
|
1984
|
-
/**
|
|
1985
|
-
* Gets the value of a response header
|
|
1986
|
-
*
|
|
1987
|
-
* @param key - Header name
|
|
1988
|
-
* @returns The header value
|
|
1989
|
-
*/
|
|
1990
|
-
getHeader(key) {
|
|
1991
|
-
const value = this.#headers[key.toLowerCase()];
|
|
1992
|
-
return value === void 0 ? this.response.getHeader(key) : value;
|
|
1993
|
-
}
|
|
1994
|
-
/**
|
|
1995
|
-
* Gets all response headers as an object
|
|
1996
|
-
*
|
|
1997
|
-
* @returns Object containing all headers
|
|
1998
|
-
*/
|
|
1999
|
-
getHeaders() {
|
|
2000
|
-
return {
|
|
2001
|
-
...this.response.getHeaders(),
|
|
2002
|
-
...this.#headers
|
|
2003
|
-
};
|
|
2004
|
-
}
|
|
2005
|
-
/**
|
|
2006
|
-
* Sets a response header (replaces existing value)
|
|
2007
|
-
*
|
|
2008
|
-
* @param key - Header name
|
|
2009
|
-
* @param value - Header value (ignored if null/undefined)
|
|
2010
|
-
* @returns The Response instance for chaining
|
|
2011
|
-
*
|
|
2012
|
-
* @example
|
|
2013
|
-
* ```ts
|
|
2014
|
-
* response.header('Content-Type', 'application/json')
|
|
2015
|
-
* ```
|
|
2016
|
-
*/
|
|
2017
|
-
header(key, value) {
|
|
2018
|
-
if (value === null || value === void 0) {
|
|
2019
|
-
return this;
|
|
2020
|
-
}
|
|
2021
|
-
this.#headers[key.toLowerCase()] = this.#castHeaderValue(value);
|
|
2022
|
-
return this;
|
|
2023
|
-
}
|
|
2024
|
-
/**
|
|
2025
|
-
* Appends a value to an existing response header
|
|
2026
|
-
*
|
|
2027
|
-
* @param key - Header name
|
|
2028
|
-
* @param value - Header value to append (ignored if null/undefined)
|
|
2029
|
-
* @returns The Response instance for chaining
|
|
2030
|
-
*
|
|
2031
|
-
* @example
|
|
2032
|
-
* ```ts
|
|
2033
|
-
* response.append('Set-Cookie', 'session=abc123')
|
|
2034
|
-
* ```
|
|
2035
|
-
*/
|
|
2036
|
-
append(key, value) {
|
|
2037
|
-
if (value === null || value === void 0) {
|
|
2038
|
-
return this;
|
|
2039
|
-
}
|
|
2040
|
-
key = key.toLowerCase();
|
|
2041
|
-
let existingHeader = this.getHeader(key);
|
|
2042
|
-
let casted = this.#castHeaderValue(value);
|
|
2043
|
-
if (!existingHeader) {
|
|
2044
|
-
this.#headers[key] = casted;
|
|
2045
|
-
return this;
|
|
2046
|
-
}
|
|
2047
|
-
existingHeader = this.#castHeaderValue(existingHeader);
|
|
2048
|
-
casted = Array.isArray(existingHeader) ? existingHeader.concat(casted) : [existingHeader].concat(casted);
|
|
2049
|
-
this.#headers[key] = casted;
|
|
2050
|
-
return this;
|
|
2051
|
-
}
|
|
2052
|
-
/**
|
|
2053
|
-
* Sets a header only if it doesn't already exist
|
|
2054
|
-
*
|
|
2055
|
-
* @param key - Header name
|
|
2056
|
-
* @param value - Header value
|
|
2057
|
-
* @returns The Response instance for chaining
|
|
2058
|
-
*/
|
|
2059
|
-
safeHeader(key, value) {
|
|
2060
|
-
if (!this.getHeader(key)) {
|
|
2061
|
-
this.header(key, value);
|
|
2062
|
-
}
|
|
2063
|
-
return this;
|
|
2064
|
-
}
|
|
2065
|
-
/**
|
|
2066
|
-
* Removes a response header
|
|
2067
|
-
*
|
|
2068
|
-
* @param key - Header name to remove
|
|
2069
|
-
* @returns The Response instance for chaining
|
|
2070
|
-
*/
|
|
2071
|
-
removeHeader(key) {
|
|
2072
|
-
key = key.toLowerCase();
|
|
2073
|
-
this.response.removeHeader(key);
|
|
2074
|
-
if (this.#headers[key]) {
|
|
2075
|
-
delete this.#headers[key.toLowerCase()];
|
|
2076
|
-
}
|
|
2077
|
-
return this;
|
|
2078
|
-
}
|
|
2079
|
-
/**
|
|
2080
|
-
* Gets the current response status code
|
|
2081
|
-
*
|
|
2082
|
-
* @returns The HTTP status code
|
|
2083
|
-
*/
|
|
2084
|
-
getStatus() {
|
|
2085
|
-
return this.response.statusCode;
|
|
2086
|
-
}
|
|
2087
|
-
/**
|
|
2088
|
-
* Sets the response status code
|
|
2089
|
-
*
|
|
2090
|
-
* @param code - HTTP status code
|
|
2091
|
-
* @returns The Response instance for chaining
|
|
2092
|
-
*/
|
|
2093
|
-
status(code) {
|
|
2094
|
-
this.#hasExplicitStatus = true;
|
|
2095
|
-
this.response.statusCode = code;
|
|
2096
|
-
return this;
|
|
2097
|
-
}
|
|
2098
|
-
/**
|
|
2099
|
-
* Sets the status code only if not explicitly set already
|
|
2100
|
-
*
|
|
2101
|
-
* @param code - HTTP status code
|
|
2102
|
-
* @returns The Response instance for chaining
|
|
2103
|
-
*/
|
|
2104
|
-
safeStatus(code) {
|
|
2105
|
-
if (this.#hasExplicitStatus) {
|
|
2106
|
-
return this;
|
|
2107
|
-
}
|
|
2108
|
-
this.response.statusCode = code;
|
|
2109
|
-
return this;
|
|
2110
|
-
}
|
|
2111
|
-
/**
|
|
2112
|
-
* Sets the Content-Type header based on mime type lookup
|
|
2113
|
-
*
|
|
2114
|
-
* @param type - File extension or mime type
|
|
2115
|
-
* @param charset - Optional character encoding
|
|
2116
|
-
* @returns The Response instance for chaining
|
|
2117
|
-
*
|
|
2118
|
-
* @example
|
|
2119
|
-
* ```ts
|
|
2120
|
-
* response.type('.json') // Content-Type: application/json
|
|
2121
|
-
* response.type('html', 'utf-8') // Content-Type: text/html; charset=utf-8
|
|
2122
|
-
* ```
|
|
2123
|
-
*/
|
|
2124
|
-
type(type, charset) {
|
|
2125
|
-
type = charset ? `${type}; charset=${charset}` : type;
|
|
2126
|
-
this.header("Content-Type", default3.contentType(type));
|
|
2127
|
-
return this;
|
|
2128
|
-
}
|
|
2129
|
-
/**
|
|
2130
|
-
* Sets the Vary HTTP header for cache control
|
|
2131
|
-
*
|
|
2132
|
-
* @param field - Header field name(s) to vary on
|
|
2133
|
-
* @returns The Response instance for chaining
|
|
2134
|
-
*/
|
|
2135
|
-
vary(field) {
|
|
2136
|
-
vary(this.response, field);
|
|
2137
|
-
return this;
|
|
2138
|
-
}
|
|
2139
|
-
/**
|
|
2140
|
-
* Sets the ETag header by computing a hash from the response body
|
|
2141
|
-
*
|
|
2142
|
-
* @param body - The response body to hash
|
|
2143
|
-
* @param weak - Whether to generate a weak ETag
|
|
2144
|
-
* @returns The Response instance for chaining
|
|
2145
|
-
*/
|
|
2146
|
-
setEtag(body, weak = false) {
|
|
2147
|
-
this.header("Etag", etag(body, { weak }));
|
|
2148
|
-
return this;
|
|
2149
|
-
}
|
|
2150
|
-
/**
|
|
2151
|
-
* Sets the X-Request-Id header by copying from the incoming request
|
|
2152
|
-
*
|
|
2153
|
-
* @returns The Response instance for chaining
|
|
2154
|
-
*/
|
|
2155
|
-
setRequestId() {
|
|
2156
|
-
const requestId = this.request.headers["x-request-id"];
|
|
2157
|
-
if (requestId) {
|
|
2158
|
-
this.header("X-Request-Id", requestId);
|
|
2159
|
-
}
|
|
2160
|
-
return this;
|
|
2161
|
-
}
|
|
2162
|
-
/**
|
|
2163
|
-
* Checks if the response is fresh (client cache is valid)
|
|
2164
|
-
*
|
|
2165
|
-
* Compares ETags and modified dates between request and response
|
|
2166
|
-
* to determine if a 304 Not Modified response should be sent.
|
|
2167
|
-
*
|
|
2168
|
-
* @returns True if client cache is fresh, false otherwise
|
|
2169
|
-
*
|
|
2170
|
-
* @example
|
|
2171
|
-
* ```ts
|
|
2172
|
-
* response.setEtag(content)
|
|
2173
|
-
* if (response.fresh()) {
|
|
2174
|
-
* response.status(304).send(null)
|
|
2175
|
-
* } else {
|
|
2176
|
-
* response.send(content)
|
|
2177
|
-
* }
|
|
2178
|
-
* ```
|
|
2179
|
-
*/
|
|
2180
|
-
fresh() {
|
|
2181
|
-
if (this.request.method && !CACHEABLE_HTTP_METHODS.includes(this.request.method)) {
|
|
2182
|
-
return false;
|
|
2183
|
-
}
|
|
2184
|
-
const status = this.response.statusCode;
|
|
2185
|
-
if (status >= ResponseStatus.Ok && status < ResponseStatus.MultipleChoices || status === ResponseStatus.NotModified) {
|
|
2186
|
-
return fresh2(this.request.headers, this.#headers);
|
|
2187
|
-
}
|
|
2188
|
-
return false;
|
|
2189
|
-
}
|
|
2190
|
-
/**
|
|
2191
|
-
* Gets the response body content
|
|
2192
|
-
*
|
|
2193
|
-
* @returns The response body or null if not set or is a stream
|
|
2194
|
-
*/
|
|
2195
|
-
getBody() {
|
|
2196
|
-
if (this.lazyBody.content) {
|
|
2197
|
-
return this.lazyBody.content[0];
|
|
2198
|
-
}
|
|
2199
|
-
return null;
|
|
2200
|
-
}
|
|
2201
|
-
/**
|
|
2202
|
-
* Sends the response body with optional ETag generation
|
|
2203
|
-
*
|
|
2204
|
-
* @param body - The response body
|
|
2205
|
-
* @param generateEtag - Whether to generate ETag header (defaults to config)
|
|
2206
|
-
*/
|
|
2207
|
-
send(body, generateEtag = this.#config.etag) {
|
|
2208
|
-
this.lazyBody.content = [body, generateEtag];
|
|
2209
|
-
}
|
|
2210
|
-
/**
|
|
2211
|
-
* Sends a JSON response (alias for send)
|
|
2212
|
-
*
|
|
2213
|
-
* @param body - The response body to serialize as JSON
|
|
2214
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2215
|
-
*/
|
|
2216
|
-
json(body, generateEtag = this.#config.etag) {
|
|
2217
|
-
return this.send(body, generateEtag);
|
|
2218
|
-
}
|
|
2219
|
-
/**
|
|
2220
|
-
* Sends a JSONP response with callback wrapping
|
|
2221
|
-
*
|
|
2222
|
-
* Callback name resolution priority:
|
|
2223
|
-
* 1. Explicit callbackName parameter
|
|
2224
|
-
* 2. Query string parameter
|
|
2225
|
-
* 3. Config value
|
|
2226
|
-
* 4. Default "callback"
|
|
2227
|
-
*
|
|
2228
|
-
* @param body - The response body
|
|
2229
|
-
* @param callbackName - JSONP callback function name
|
|
2230
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2231
|
-
*/
|
|
2232
|
-
jsonp(body, callbackName = this.#config.jsonpCallbackName, generateEtag = this.#config.etag) {
|
|
2233
|
-
this.lazyBody.content = [body, generateEtag, callbackName];
|
|
2234
|
-
}
|
|
2235
|
-
/**
|
|
2236
|
-
* Pipes a readable stream to the response with graceful error handling
|
|
2237
|
-
*
|
|
2238
|
-
* @param body - The readable stream to pipe
|
|
2239
|
-
* @param errorCallback - Optional custom error handler
|
|
2240
|
-
*
|
|
2241
|
-
* @example
|
|
2242
|
-
* ```ts
|
|
2243
|
-
* // Auto error handling
|
|
2244
|
-
* response.stream(fs.createReadStream('file.txt'))
|
|
2245
|
-
*
|
|
2246
|
-
* // Custom error handling
|
|
2247
|
-
* response.stream(stream, (error) => {
|
|
2248
|
-
* return error.code === 'ENOENT' ? ['Not found', 404] : ['Error', 500]
|
|
2249
|
-
* })
|
|
2250
|
-
* ```
|
|
2251
|
-
*/
|
|
2252
|
-
stream(body, errorCallback) {
|
|
2253
|
-
if (typeof body.pipe !== "function" || !body.readable || typeof body.read !== "function") {
|
|
2254
|
-
throw new TypeError("response.stream accepts a readable stream only");
|
|
2255
|
-
}
|
|
2256
|
-
this.lazyBody.stream = [body, errorCallback];
|
|
2257
|
-
}
|
|
2258
|
-
/**
|
|
2259
|
-
* Downloads a file by streaming it with appropriate headers
|
|
2260
|
-
*
|
|
2261
|
-
* Automatically sets:
|
|
2262
|
-
* - Content-Type from file extension
|
|
2263
|
-
* - Content-Length from file size
|
|
2264
|
-
* - Last-Modified from file stats
|
|
2265
|
-
* - ETag (if enabled)
|
|
2266
|
-
*
|
|
2267
|
-
* @param filePath - Path to the file to download
|
|
2268
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2269
|
-
* @param errorCallback - Optional custom error handler
|
|
2270
|
-
*
|
|
2271
|
-
* @example
|
|
2272
|
-
* ```ts
|
|
2273
|
-
* response.download('/path/to/file.pdf')
|
|
2274
|
-
* response.download('/images/photo.jpg', true, (err) => ['Custom error', 500])
|
|
2275
|
-
* ```
|
|
2276
|
-
*/
|
|
2277
|
-
download(filePath, generateEtag = this.#config.etag, errorCallback) {
|
|
2278
|
-
this.lazyBody.fileToStream = [filePath, generateEtag, errorCallback];
|
|
2279
|
-
}
|
|
2280
|
-
/**
|
|
2281
|
-
* Forces file download by setting Content-Disposition header
|
|
2282
|
-
*
|
|
2283
|
-
* @param filePath - Path to the file to download
|
|
2284
|
-
* @param name - Optional filename for download (defaults to original filename)
|
|
2285
|
-
* @param disposition - Content-Disposition type (defaults to 'attachment')
|
|
2286
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2287
|
-
* @param errorCallback - Optional custom error handler
|
|
2288
|
-
*/
|
|
2289
|
-
attachment(filePath, name, disposition, generateEtag, errorCallback) {
|
|
2290
|
-
name = name || filePath;
|
|
2291
|
-
this.header("Content-Disposition", contentDisposition(name, { type: disposition }));
|
|
2292
|
-
return this.download(filePath, generateEtag, errorCallback);
|
|
2293
|
-
}
|
|
2294
|
-
/**
|
|
2295
|
-
* Sets the Location header for redirects
|
|
2296
|
-
*
|
|
2297
|
-
* @param url - The URL to redirect to
|
|
2298
|
-
* @returns The Response instance for chaining
|
|
2299
|
-
*
|
|
2300
|
-
* @example
|
|
2301
|
-
* ```ts
|
|
2302
|
-
* response.location('/dashboard')
|
|
2303
|
-
* ```
|
|
2304
|
-
*/
|
|
2305
|
-
location(url) {
|
|
2306
|
-
this.header("Location", url);
|
|
2307
|
-
return this;
|
|
2308
|
-
}
|
|
2309
|
-
redirect(path, forwardQueryString = false, statusCode = ResponseStatus.Found) {
|
|
2310
|
-
const handler = new Redirect(this.request, this, this.#router, this.#qs);
|
|
2311
|
-
if (forwardQueryString) {
|
|
2312
|
-
handler.withQs();
|
|
2313
|
-
}
|
|
2314
|
-
if (path === "back") {
|
|
2315
|
-
return handler.status(statusCode).back();
|
|
2316
|
-
}
|
|
2317
|
-
if (path) {
|
|
2318
|
-
return handler.status(statusCode).toPath(path);
|
|
2319
|
-
}
|
|
2320
|
-
return handler;
|
|
2321
|
-
}
|
|
2322
|
-
/**
|
|
2323
|
-
* Aborts the request with a custom response body and status code
|
|
2324
|
-
*
|
|
2325
|
-
* @param body - Response body for the aborted request
|
|
2326
|
-
* @param status - HTTP status code (defaults to 400)
|
|
2327
|
-
* @throws Always throws an HTTP exception
|
|
2328
|
-
*/
|
|
2329
|
-
abort(body, status) {
|
|
2330
|
-
throw E_HTTP_REQUEST_ABORTED.invoke(body, status || ResponseStatus.BadRequest);
|
|
2331
|
-
}
|
|
2332
|
-
/**
|
|
2333
|
-
* Conditionally aborts the request if the condition is truthy
|
|
2334
|
-
*
|
|
2335
|
-
* @param condition - Condition to evaluate
|
|
2336
|
-
* @param body - Response body for the aborted request
|
|
2337
|
-
* @param status - HTTP status code (defaults to 400)
|
|
2338
|
-
*/
|
|
2339
|
-
abortIf(condition, body, status) {
|
|
2340
|
-
if (condition) {
|
|
2341
|
-
this.abort(body, status);
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
/**
|
|
2345
|
-
* Conditionally aborts the request if the condition is falsy
|
|
2346
|
-
*
|
|
2347
|
-
* @param condition - Condition to evaluate
|
|
2348
|
-
* @param body - Response body for the aborted request
|
|
2349
|
-
* @param status - HTTP status code (defaults to 400)
|
|
2350
|
-
*/
|
|
2351
|
-
abortUnless(condition, body, status) {
|
|
2352
|
-
if (!condition) {
|
|
2353
|
-
this.abort(body, status);
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
/**
|
|
2357
|
-
* Sets a signed cookie in the response
|
|
2358
|
-
*
|
|
2359
|
-
* @param key - Cookie name
|
|
2360
|
-
* @param value - Cookie value
|
|
2361
|
-
* @param options - Cookie options (overrides config defaults)
|
|
2362
|
-
* @returns The Response instance for chaining
|
|
2363
|
-
*/
|
|
2364
|
-
cookie(key, value, options) {
|
|
2365
|
-
options = Object.assign({}, this.#config.cookie, options);
|
|
2366
|
-
const serialized = this.#cookieSerializer.sign(key, value, options);
|
|
2367
|
-
if (!serialized) {
|
|
2368
|
-
return this;
|
|
2369
|
-
}
|
|
2370
|
-
this.append("set-cookie", serialized);
|
|
2371
|
-
return this;
|
|
2372
|
-
}
|
|
2373
|
-
/**
|
|
2374
|
-
* Sets an encrypted cookie in the response
|
|
2375
|
-
*
|
|
2376
|
-
* @param key - Cookie name
|
|
2377
|
-
* @param value - Cookie value
|
|
2378
|
-
* @param options - Cookie options (overrides config defaults)
|
|
2379
|
-
* @returns The Response instance for chaining
|
|
2380
|
-
*/
|
|
2381
|
-
encryptedCookie(key, value, options) {
|
|
2382
|
-
options = Object.assign({}, this.#config.cookie, options);
|
|
2383
|
-
const serialized = this.#cookieSerializer.encrypt(key, value, options);
|
|
2384
|
-
if (!serialized) {
|
|
2385
|
-
return this;
|
|
2386
|
-
}
|
|
2387
|
-
this.append("set-cookie", serialized);
|
|
2388
|
-
return this;
|
|
2389
|
-
}
|
|
2390
|
-
/**
|
|
2391
|
-
* Sets a plain (unsigned/unencrypted) cookie in the response
|
|
2392
|
-
*
|
|
2393
|
-
* @param key - Cookie name
|
|
2394
|
-
* @param value - Cookie value
|
|
2395
|
-
* @param options - Cookie options including encode flag
|
|
2396
|
-
* @returns The Response instance for chaining
|
|
2397
|
-
*/
|
|
2398
|
-
plainCookie(key, value, options) {
|
|
2399
|
-
options = Object.assign({}, this.#config.cookie, options);
|
|
2400
|
-
const serialized = this.#cookieSerializer.encode(key, value, options);
|
|
2401
|
-
if (!serialized) {
|
|
2402
|
-
return this;
|
|
2403
|
-
}
|
|
2404
|
-
this.append("set-cookie", serialized);
|
|
2405
|
-
return this;
|
|
2406
|
-
}
|
|
2407
|
-
/**
|
|
2408
|
-
* Clears an existing cookie by setting it to expire
|
|
2409
|
-
*
|
|
2410
|
-
* @param key - Cookie name to clear
|
|
2411
|
-
* @param options - Cookie options (should match original cookie options)
|
|
2412
|
-
* @returns The Response instance for chaining
|
|
2413
|
-
*/
|
|
2414
|
-
clearCookie(key, options) {
|
|
2415
|
-
options = Object.assign({}, this.#config.cookie, options);
|
|
2416
|
-
options.expires = /* @__PURE__ */ new Date(1);
|
|
2417
|
-
options.maxAge = -1;
|
|
2418
|
-
const serialized = this.#cookieSerializer.encode(key, "", { ...options, encode: false });
|
|
2419
|
-
this.append("set-cookie", serialized);
|
|
2420
|
-
return this;
|
|
2421
|
-
}
|
|
2422
|
-
/**
|
|
2423
|
-
* Finalizes and sends the response
|
|
2424
|
-
*
|
|
2425
|
-
* Writes the buffered body (content, stream, or file) to the client.
|
|
2426
|
-
* This method is idempotent - calling it multiple times has no effect.
|
|
2427
|
-
*/
|
|
2428
|
-
finish() {
|
|
2429
|
-
if (!this.isPending) {
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
if (this.content) {
|
|
2433
|
-
httpResponseSerializer.traceSync(this.writeBody, void 0, this, ...this.content);
|
|
2434
|
-
return;
|
|
2435
|
-
}
|
|
2436
|
-
if (this.lazyBody.stream) {
|
|
2437
|
-
this.streamBody(...this.lazyBody.stream);
|
|
2438
|
-
return;
|
|
2439
|
-
}
|
|
2440
|
-
if (this.lazyBody.fileToStream) {
|
|
2441
|
-
this.streamFileForDownload(...this.lazyBody.fileToStream);
|
|
2442
|
-
return;
|
|
2443
|
-
}
|
|
2444
|
-
this.#endResponse();
|
|
2445
|
-
}
|
|
2446
|
-
/**
|
|
2447
|
-
* Sends a 100 Continue response
|
|
2448
|
-
*/
|
|
2449
|
-
continue() {
|
|
2450
|
-
this.status(ResponseStatus.Continue);
|
|
2451
|
-
return this.send(null, false);
|
|
2452
|
-
}
|
|
2453
|
-
/**
|
|
2454
|
-
* Sends a 101 Switching Protocols response
|
|
2455
|
-
*/
|
|
2456
|
-
switchingProtocols() {
|
|
2457
|
-
this.status(ResponseStatus.SwitchingProtocols);
|
|
2458
|
-
return this.send(null, false);
|
|
2459
|
-
}
|
|
2460
|
-
/**
|
|
2461
|
-
* Sends a 200 OK response
|
|
2462
|
-
*
|
|
2463
|
-
* @param body - Response body
|
|
2464
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2465
|
-
*/
|
|
2466
|
-
ok(body, generateEtag) {
|
|
2467
|
-
this.status(ResponseStatus.Ok);
|
|
2468
|
-
return this.send(body, generateEtag);
|
|
2469
|
-
}
|
|
2470
|
-
/**
|
|
2471
|
-
* Sends a 201 Created response
|
|
2472
|
-
*
|
|
2473
|
-
* @param body - Response body
|
|
2474
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2475
|
-
*/
|
|
2476
|
-
created(body, generateEtag) {
|
|
2477
|
-
this.status(ResponseStatus.Created);
|
|
2478
|
-
return this.send(body, generateEtag);
|
|
2479
|
-
}
|
|
2480
|
-
/**
|
|
2481
|
-
* Sends a 202 Accepted response
|
|
2482
|
-
*
|
|
2483
|
-
* @param body - Response body
|
|
2484
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2485
|
-
*/
|
|
2486
|
-
accepted(body, generateEtag) {
|
|
2487
|
-
this.status(ResponseStatus.Accepted);
|
|
2488
|
-
return this.send(body, generateEtag);
|
|
2489
|
-
}
|
|
2490
|
-
/**
|
|
2491
|
-
* Sends a 203 Non-Authoritative Information response
|
|
2492
|
-
*
|
|
2493
|
-
* @param body - Response body
|
|
2494
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2495
|
-
*/
|
|
2496
|
-
nonAuthoritativeInformation(body, generateEtag) {
|
|
2497
|
-
this.status(ResponseStatus.NonAuthoritativeInformation);
|
|
2498
|
-
return this.send(body, generateEtag);
|
|
2499
|
-
}
|
|
2500
|
-
/**
|
|
2501
|
-
* Sends a 204 No Content response
|
|
2502
|
-
*/
|
|
2503
|
-
noContent() {
|
|
2504
|
-
this.status(ResponseStatus.NoContent);
|
|
2505
|
-
return this.send(null, false);
|
|
2506
|
-
}
|
|
2507
|
-
/**
|
|
2508
|
-
* Sends a 205 Reset Content response
|
|
2509
|
-
*/
|
|
2510
|
-
resetContent() {
|
|
2511
|
-
this.status(ResponseStatus.ResetContent);
|
|
2512
|
-
return this.send(null, false);
|
|
2513
|
-
}
|
|
2514
|
-
/**
|
|
2515
|
-
* Sends a 206 Partial Content response
|
|
2516
|
-
*
|
|
2517
|
-
* @param body - Response body
|
|
2518
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2519
|
-
*/
|
|
2520
|
-
partialContent(body, generateEtag) {
|
|
2521
|
-
this.status(ResponseStatus.PartialContent);
|
|
2522
|
-
return this.send(body, generateEtag);
|
|
2523
|
-
}
|
|
2524
|
-
/**
|
|
2525
|
-
* Sends a 300 Multiple Choices response
|
|
2526
|
-
*
|
|
2527
|
-
* @param body - Response body
|
|
2528
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2529
|
-
*/
|
|
2530
|
-
multipleChoices(body, generateEtag) {
|
|
2531
|
-
this.status(ResponseStatus.MultipleChoices);
|
|
2532
|
-
return this.send(body, generateEtag);
|
|
2533
|
-
}
|
|
2534
|
-
/**
|
|
2535
|
-
* Sends a 301 Moved Permanently response
|
|
2536
|
-
*
|
|
2537
|
-
* @param body - Response body
|
|
2538
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2539
|
-
*/
|
|
2540
|
-
movedPermanently(body, generateEtag) {
|
|
2541
|
-
this.status(ResponseStatus.MovedPermanently);
|
|
2542
|
-
return this.send(body, generateEtag);
|
|
2543
|
-
}
|
|
2544
|
-
/**
|
|
2545
|
-
* Sends a 302 Found (Moved Temporarily) response
|
|
2546
|
-
*
|
|
2547
|
-
* @param body - Response body
|
|
2548
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2549
|
-
*/
|
|
2550
|
-
movedTemporarily(body, generateEtag) {
|
|
2551
|
-
this.status(ResponseStatus.Found);
|
|
2552
|
-
return this.send(body, generateEtag);
|
|
2553
|
-
}
|
|
2554
|
-
/**
|
|
2555
|
-
* Sends a 303 See Other response
|
|
2556
|
-
*
|
|
2557
|
-
* @param body - Response body
|
|
2558
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2559
|
-
*/
|
|
2560
|
-
seeOther(body, generateEtag) {
|
|
2561
|
-
this.status(ResponseStatus.SeeOther);
|
|
2562
|
-
return this.send(body, generateEtag);
|
|
2563
|
-
}
|
|
2564
|
-
/**
|
|
2565
|
-
* Sends a 304 Not Modified response
|
|
2566
|
-
*
|
|
2567
|
-
* @param body - Response body
|
|
2568
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2569
|
-
*/
|
|
2570
|
-
notModified(body, generateEtag) {
|
|
2571
|
-
this.status(ResponseStatus.NotModified);
|
|
2572
|
-
return this.send(body, generateEtag);
|
|
2573
|
-
}
|
|
2574
|
-
/**
|
|
2575
|
-
* Sends a 305 Use Proxy response
|
|
2576
|
-
*
|
|
2577
|
-
* @param body - Response body
|
|
2578
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2579
|
-
*/
|
|
2580
|
-
useProxy(body, generateEtag) {
|
|
2581
|
-
this.status(ResponseStatus.UseProxy);
|
|
2582
|
-
return this.send(body, generateEtag);
|
|
2583
|
-
}
|
|
2584
|
-
/**
|
|
2585
|
-
* Sends a 307 Temporary Redirect response
|
|
2586
|
-
*
|
|
2587
|
-
* @param body - Response body
|
|
2588
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2589
|
-
*/
|
|
2590
|
-
temporaryRedirect(body, generateEtag) {
|
|
2591
|
-
this.status(ResponseStatus.TemporaryRedirect);
|
|
2592
|
-
return this.send(body, generateEtag);
|
|
2593
|
-
}
|
|
2594
|
-
/**
|
|
2595
|
-
* Sends a 400 Bad Request response
|
|
2596
|
-
*
|
|
2597
|
-
* @param body - Response body
|
|
2598
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2599
|
-
*/
|
|
2600
|
-
badRequest(body, generateEtag) {
|
|
2601
|
-
this.status(ResponseStatus.BadRequest);
|
|
2602
|
-
return this.send(body, generateEtag);
|
|
2603
|
-
}
|
|
2604
|
-
/**
|
|
2605
|
-
* Sends a 401 Unauthorized response
|
|
2606
|
-
*
|
|
2607
|
-
* @param body - Response body
|
|
2608
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2609
|
-
*/
|
|
2610
|
-
unauthorized(body, generateEtag) {
|
|
2611
|
-
this.status(ResponseStatus.Unauthorized);
|
|
2612
|
-
return this.send(body, generateEtag);
|
|
2613
|
-
}
|
|
2614
|
-
/**
|
|
2615
|
-
* Sends a 402 Payment Required response
|
|
2616
|
-
*
|
|
2617
|
-
* @param body - Response body
|
|
2618
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2619
|
-
*/
|
|
2620
|
-
paymentRequired(body, generateEtag) {
|
|
2621
|
-
this.status(ResponseStatus.PaymentRequired);
|
|
2622
|
-
return this.send(body, generateEtag);
|
|
2623
|
-
}
|
|
2624
|
-
/**
|
|
2625
|
-
* Sends a 403 Forbidden response
|
|
2626
|
-
*
|
|
2627
|
-
* @param body - Response body
|
|
2628
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2629
|
-
*/
|
|
2630
|
-
forbidden(body, generateEtag) {
|
|
2631
|
-
this.status(ResponseStatus.Forbidden);
|
|
2632
|
-
return this.send(body, generateEtag);
|
|
2633
|
-
}
|
|
2634
|
-
/**
|
|
2635
|
-
* Sends a 404 Not Found response
|
|
2636
|
-
*
|
|
2637
|
-
* @param body - Response body
|
|
2638
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2639
|
-
*/
|
|
2640
|
-
notFound(body, generateEtag) {
|
|
2641
|
-
this.status(ResponseStatus.NotFound);
|
|
2642
|
-
return this.send(body, generateEtag);
|
|
2643
|
-
}
|
|
2644
|
-
/**
|
|
2645
|
-
* Sends a 405 Method Not Allowed response
|
|
2646
|
-
*
|
|
2647
|
-
* @param body - Response body
|
|
2648
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2649
|
-
*/
|
|
2650
|
-
methodNotAllowed(body, generateEtag) {
|
|
2651
|
-
this.status(ResponseStatus.MethodNotAllowed);
|
|
2652
|
-
return this.send(body, generateEtag);
|
|
2653
|
-
}
|
|
2654
|
-
/**
|
|
2655
|
-
* Sends a 406 Not Acceptable response
|
|
2656
|
-
*
|
|
2657
|
-
* @param body - Response body
|
|
2658
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2659
|
-
*/
|
|
2660
|
-
notAcceptable(body, generateEtag) {
|
|
2661
|
-
this.status(ResponseStatus.NotAcceptable);
|
|
2662
|
-
return this.send(body, generateEtag);
|
|
2663
|
-
}
|
|
2664
|
-
/**
|
|
2665
|
-
* Sends a 407 Proxy Authentication Required response
|
|
2666
|
-
*
|
|
2667
|
-
* @param body - Response body
|
|
2668
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2669
|
-
*/
|
|
2670
|
-
proxyAuthenticationRequired(body, generateEtag) {
|
|
2671
|
-
this.status(ResponseStatus.ProxyAuthenticationRequired);
|
|
2672
|
-
return this.send(body, generateEtag);
|
|
2673
|
-
}
|
|
2674
|
-
/**
|
|
2675
|
-
* Sends a 408 Request Timeout response
|
|
2676
|
-
*
|
|
2677
|
-
* @param body - Response body
|
|
2678
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2679
|
-
*/
|
|
2680
|
-
requestTimeout(body, generateEtag) {
|
|
2681
|
-
this.status(ResponseStatus.RequestTimeout);
|
|
2682
|
-
return this.send(body, generateEtag);
|
|
2683
|
-
}
|
|
2684
|
-
/**
|
|
2685
|
-
* Sends a 409 Conflict response
|
|
2686
|
-
*
|
|
2687
|
-
* @param body - Response body
|
|
2688
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2689
|
-
*/
|
|
2690
|
-
conflict(body, generateEtag) {
|
|
2691
|
-
this.status(ResponseStatus.Conflict);
|
|
2692
|
-
return this.send(body, generateEtag);
|
|
2693
|
-
}
|
|
2694
|
-
/**
|
|
2695
|
-
* Sends a 410 Gone response
|
|
2696
|
-
*
|
|
2697
|
-
* @param body - Response body
|
|
2698
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2699
|
-
*/
|
|
2700
|
-
gone(body, generateEtag) {
|
|
2701
|
-
this.status(ResponseStatus.Gone);
|
|
2702
|
-
return this.send(body, generateEtag);
|
|
2703
|
-
}
|
|
2704
|
-
/**
|
|
2705
|
-
* Sends a 411 Length Required response
|
|
2706
|
-
*
|
|
2707
|
-
* @param body - Response body
|
|
2708
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2709
|
-
*/
|
|
2710
|
-
lengthRequired(body, generateEtag) {
|
|
2711
|
-
this.status(ResponseStatus.LengthRequired);
|
|
2712
|
-
return this.send(body, generateEtag);
|
|
2713
|
-
}
|
|
2714
|
-
/**
|
|
2715
|
-
* Sends a 412 Precondition Failed response
|
|
2716
|
-
*
|
|
2717
|
-
* @param body - Response body
|
|
2718
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2719
|
-
*/
|
|
2720
|
-
preconditionFailed(body, generateEtag) {
|
|
2721
|
-
this.status(ResponseStatus.PreconditionFailed);
|
|
2722
|
-
return this.send(body, generateEtag);
|
|
2723
|
-
}
|
|
2724
|
-
/**
|
|
2725
|
-
* Sends a 413 Payload Too Large response
|
|
2726
|
-
*
|
|
2727
|
-
* @param body - Response body
|
|
2728
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2729
|
-
*/
|
|
2730
|
-
requestEntityTooLarge(body, generateEtag) {
|
|
2731
|
-
this.status(ResponseStatus.PayloadTooLarge);
|
|
2732
|
-
return this.send(body, generateEtag);
|
|
2733
|
-
}
|
|
2734
|
-
/**
|
|
2735
|
-
* Sends a 414 URI Too Long response
|
|
2736
|
-
*
|
|
2737
|
-
* @param body - Response body
|
|
2738
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2739
|
-
*/
|
|
2740
|
-
requestUriTooLong(body, generateEtag) {
|
|
2741
|
-
this.status(ResponseStatus.URITooLong);
|
|
2742
|
-
return this.send(body, generateEtag);
|
|
2743
|
-
}
|
|
2744
|
-
/**
|
|
2745
|
-
* Sends a 415 Unsupported Media Type response
|
|
2746
|
-
*
|
|
2747
|
-
* @param body - Response body
|
|
2748
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2749
|
-
*/
|
|
2750
|
-
unsupportedMediaType(body, generateEtag) {
|
|
2751
|
-
this.status(ResponseStatus.UnsupportedMediaType);
|
|
2752
|
-
return this.send(body, generateEtag);
|
|
2753
|
-
}
|
|
2754
|
-
/**
|
|
2755
|
-
* Sends a 416 Range Not Satisfiable response
|
|
2756
|
-
*
|
|
2757
|
-
* @param body - Response body
|
|
2758
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2759
|
-
*/
|
|
2760
|
-
requestedRangeNotSatisfiable(body, generateEtag) {
|
|
2761
|
-
this.status(ResponseStatus.RangeNotSatisfiable);
|
|
2762
|
-
return this.send(body, generateEtag);
|
|
2763
|
-
}
|
|
2764
|
-
/**
|
|
2765
|
-
* Sends a 417 Expectation Failed response
|
|
2766
|
-
*
|
|
2767
|
-
* @param body - Response body
|
|
2768
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2769
|
-
*/
|
|
2770
|
-
expectationFailed(body, generateEtag) {
|
|
2771
|
-
this.status(ResponseStatus.ExpectationFailed);
|
|
2772
|
-
return this.send(body, generateEtag);
|
|
2773
|
-
}
|
|
2774
|
-
/**
|
|
2775
|
-
* Sends a 422 Unprocessable Entity response
|
|
2776
|
-
*
|
|
2777
|
-
* @param body - Response body
|
|
2778
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2779
|
-
*/
|
|
2780
|
-
unprocessableEntity(body, generateEtag) {
|
|
2781
|
-
this.status(ResponseStatus.UnprocessableEntity);
|
|
2782
|
-
return this.send(body, generateEtag);
|
|
2783
|
-
}
|
|
2784
|
-
/**
|
|
2785
|
-
* Sends a 429 Too Many Requests response
|
|
2786
|
-
*
|
|
2787
|
-
* @param body - Response body
|
|
2788
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2789
|
-
*/
|
|
2790
|
-
tooManyRequests(body, generateEtag) {
|
|
2791
|
-
this.status(ResponseStatus.TooManyRequests);
|
|
2792
|
-
return this.send(body, generateEtag);
|
|
2793
|
-
}
|
|
2794
|
-
/**
|
|
2795
|
-
* Sends a 500 Internal Server Error response
|
|
2796
|
-
*
|
|
2797
|
-
* @param body - Response body
|
|
2798
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2799
|
-
*/
|
|
2800
|
-
internalServerError(body, generateEtag) {
|
|
2801
|
-
this.status(ResponseStatus.InternalServerError);
|
|
2802
|
-
return this.send(body, generateEtag);
|
|
2803
|
-
}
|
|
2804
|
-
/**
|
|
2805
|
-
* Sends a 501 Not Implemented response
|
|
2806
|
-
*
|
|
2807
|
-
* @param body - Response body
|
|
2808
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2809
|
-
*/
|
|
2810
|
-
notImplemented(body, generateEtag) {
|
|
2811
|
-
this.status(ResponseStatus.NotImplemented);
|
|
2812
|
-
return this.send(body, generateEtag);
|
|
2813
|
-
}
|
|
2814
|
-
/**
|
|
2815
|
-
* Sends a 502 Bad Gateway response
|
|
2816
|
-
*
|
|
2817
|
-
* @param body - Response body
|
|
2818
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2819
|
-
*/
|
|
2820
|
-
badGateway(body, generateEtag) {
|
|
2821
|
-
this.status(ResponseStatus.BadGateway);
|
|
2822
|
-
return this.send(body, generateEtag);
|
|
2823
|
-
}
|
|
2824
|
-
/**
|
|
2825
|
-
* Sends a 503 Service Unavailable response
|
|
2826
|
-
*
|
|
2827
|
-
* @param body - Response body
|
|
2828
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2829
|
-
*/
|
|
2830
|
-
serviceUnavailable(body, generateEtag) {
|
|
2831
|
-
this.status(ResponseStatus.ServiceUnavailable);
|
|
2832
|
-
return this.send(body, generateEtag);
|
|
2833
|
-
}
|
|
2834
|
-
/**
|
|
2835
|
-
* Sends a 504 Gateway Timeout response
|
|
2836
|
-
*
|
|
2837
|
-
* @param body - Response body
|
|
2838
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2839
|
-
*/
|
|
2840
|
-
gatewayTimeout(body, generateEtag) {
|
|
2841
|
-
this.status(ResponseStatus.GatewayTimeout);
|
|
2842
|
-
return this.send(body, generateEtag);
|
|
2843
|
-
}
|
|
2844
|
-
/**
|
|
2845
|
-
* Sends a 505 HTTP Version Not Supported response
|
|
2846
|
-
*
|
|
2847
|
-
* @param body - Response body
|
|
2848
|
-
* @param generateEtag - Whether to generate ETag header
|
|
2849
|
-
*/
|
|
2850
|
-
httpVersionNotSupported(body, generateEtag) {
|
|
2851
|
-
this.status(ResponseStatus.HTTPVersionNotSupported);
|
|
2852
|
-
return this.send(body, generateEtag);
|
|
2853
|
-
}
|
|
2854
|
-
};
|
|
2855
|
-
|
|
2856
|
-
// src/router/main.ts
|
|
2857
|
-
import is2 from "@sindresorhus/is";
|
|
2858
|
-
import { moduleImporter as moduleImporter2 } from "@adonisjs/fold";
|
|
2859
|
-
import { RuntimeException as RuntimeException3 } from "@poppinss/utils/exception";
|
|
2860
|
-
|
|
2861
|
-
// src/router/store.ts
|
|
2862
|
-
import matchit from "@poppinss/matchit";
|
|
2863
|
-
import { RuntimeException as RuntimeException2 } from "@poppinss/utils/exception";
|
|
2864
|
-
var RoutesStore = class {
|
|
2865
|
-
/**
|
|
2866
|
-
* A flag to know if routes for explicit domains
|
|
2867
|
-
* have been registered
|
|
2868
|
-
*/
|
|
2869
|
-
usingDomains = false;
|
|
2870
|
-
/**
|
|
2871
|
-
* Tree of registered routes and their matchit tokens
|
|
2872
|
-
*/
|
|
2873
|
-
tree = { tokens: [], domains: {} };
|
|
2874
|
-
/**
|
|
2875
|
-
* Returns the domain node for a given domain.
|
|
2876
|
-
*/
|
|
2877
|
-
#getDomainNode(domain) {
|
|
2878
|
-
if (!this.tree.domains[domain]) {
|
|
2879
|
-
this.tree.tokens.push(parseRoute(domain));
|
|
2880
|
-
this.tree.domains[domain] = {};
|
|
2881
|
-
}
|
|
2882
|
-
return this.tree.domains[domain];
|
|
2883
|
-
}
|
|
2884
|
-
/**
|
|
2885
|
-
* Returns the method node for a given domain and method.
|
|
2886
|
-
*/
|
|
2887
|
-
#getMethodNode(domain, method) {
|
|
2888
|
-
const domainNode = this.#getDomainNode(domain);
|
|
2889
|
-
if (!domainNode[method]) {
|
|
2890
|
-
domainNode[method] = { tokens: [], routes: {}, routeKeys: {} };
|
|
2891
|
-
}
|
|
2892
|
-
return domainNode[method];
|
|
2893
|
-
}
|
|
2894
|
-
/**
|
|
2895
|
-
* Collects route params
|
|
2896
|
-
*/
|
|
2897
|
-
#collectRouteParams(route, tokens) {
|
|
2898
|
-
const collectedParams = /* @__PURE__ */ new Set();
|
|
2899
|
-
for (let token of tokens) {
|
|
2900
|
-
if ([1, 3].includes(token.type)) {
|
|
2901
|
-
if (collectedParams.has(token.val)) {
|
|
2902
|
-
throw new RuntimeException2(`Duplicate param "${token.val}" found in "${route.pattern}"`);
|
|
2903
|
-
} else {
|
|
2904
|
-
collectedParams.add(token.val);
|
|
2905
|
-
}
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
const params = [...collectedParams];
|
|
2909
|
-
collectedParams.clear();
|
|
2910
|
-
return params;
|
|
2911
|
-
}
|
|
2912
|
-
/**
|
|
2913
|
-
* Register route for a given domain and method
|
|
2914
|
-
*/
|
|
2915
|
-
#registerRoute(domain, method, tokens, route) {
|
|
2916
|
-
const methodRoutes = this.#getMethodNode(domain, method);
|
|
2917
|
-
if (methodRoutes.routes[route.pattern]) {
|
|
2918
|
-
throw new RuntimeException2(
|
|
2919
|
-
`Duplicate route found. "${method}: ${route.pattern}" route already exists`
|
|
2920
|
-
);
|
|
2921
|
-
}
|
|
2922
|
-
if (debug_default.enabled) {
|
|
2923
|
-
debug_default("registering route to the store %O", route);
|
|
2924
|
-
debug_default("route middleware %O", route.middleware.all().entries());
|
|
2925
|
-
}
|
|
2926
|
-
methodRoutes.tokens.push(tokens);
|
|
2927
|
-
methodRoutes.routes[route.pattern] = route;
|
|
2928
|
-
methodRoutes.routeKeys[route.pattern] = domain !== "root" ? `${domain}-${method}-${route.pattern}` : `${method}-${route.pattern}`;
|
|
2929
|
-
}
|
|
2930
|
-
/**
|
|
2931
|
-
* Add a route to the store
|
|
2932
|
-
*
|
|
2933
|
-
* ```ts
|
|
2934
|
-
* store.add({
|
|
2935
|
-
* pattern: 'post/:id',
|
|
2936
|
-
* methods: ['GET'],
|
|
2937
|
-
* matchers: {},
|
|
2938
|
-
* meta: {},
|
|
2939
|
-
* handler: function handler () {
|
|
2940
|
-
* }
|
|
2941
|
-
* })
|
|
2942
|
-
* ```
|
|
2943
|
-
* @param route - The route to add to the store
|
|
2944
|
-
* @returns Current RoutesStore instance for method chaining
|
|
2945
|
-
*/
|
|
2946
|
-
add(route) {
|
|
2947
|
-
if (route.domain !== "root") {
|
|
2948
|
-
this.usingDomains = true;
|
|
2949
|
-
}
|
|
2950
|
-
const routeNode = { ...route };
|
|
2951
|
-
routeNode.meta.params = this.#collectRouteParams(routeNode, route.tokens);
|
|
2952
|
-
route.methods.forEach((method) => {
|
|
2953
|
-
this.#registerRoute(route.domain, method, route.tokens, routeNode);
|
|
2954
|
-
});
|
|
2955
|
-
return this;
|
|
2956
|
-
}
|
|
2957
|
-
/**
|
|
2958
|
-
* Matches the url, method and optionally domain to pull the matching
|
|
2959
|
-
* route. `null` is returned when unable to match the URL against
|
|
2960
|
-
* registered routes.
|
|
2961
|
-
*
|
|
2962
|
-
* The domain parameter has to be a registered pattern and not the fully
|
|
2963
|
-
* qualified runtime domain. You must call `matchDomain` first to fetch
|
|
2964
|
-
* the pattern for qualified domain
|
|
2965
|
-
* @param url - The URL to match
|
|
2966
|
-
* @param method - HTTP method
|
|
2967
|
-
* @param shouldDecodeParam - Whether to decode parameters
|
|
2968
|
-
* @param domain - Optional domain tokens and hostname
|
|
2969
|
-
* @returns Matched route or null if no match found
|
|
2970
|
-
*/
|
|
2971
|
-
match(url, method, shouldDecodeParam, domain) {
|
|
2972
|
-
const domainName = domain?.tokens[0]?.old || "root";
|
|
2973
|
-
const matchedDomain = this.tree.domains[domainName];
|
|
2974
|
-
if (!matchedDomain) {
|
|
2975
|
-
return null;
|
|
2976
|
-
}
|
|
2977
|
-
const matchedMethod = this.tree.domains[domainName][method];
|
|
2978
|
-
if (!matchedMethod) {
|
|
2979
|
-
return null;
|
|
2980
|
-
}
|
|
2981
|
-
const matchedRoute = matchit.match(url, matchedMethod.tokens);
|
|
2982
|
-
if (!matchedRoute.length) {
|
|
2983
|
-
return null;
|
|
2984
|
-
}
|
|
2985
|
-
const route = matchedMethod.routes[matchedRoute[0].old];
|
|
2986
|
-
return {
|
|
2987
|
-
route,
|
|
2988
|
-
routeKey: matchedMethod.routeKeys[route.pattern],
|
|
2989
|
-
params: matchit.exec(url, matchedRoute, shouldDecodeParam),
|
|
2990
|
-
subdomains: domain?.hostname ? matchit.exec(domain.hostname, domain.tokens) : {}
|
|
2991
|
-
};
|
|
2992
|
-
}
|
|
2993
|
-
/**
|
|
2994
|
-
* Match hostname against registered domains.
|
|
2995
|
-
* @param hostname - The hostname to match
|
|
2996
|
-
* @returns Array of matched domain tokens
|
|
2997
|
-
*/
|
|
2998
|
-
matchDomain(hostname) {
|
|
2999
|
-
if (!hostname || !this.usingDomains) {
|
|
3000
|
-
return [];
|
|
3001
|
-
}
|
|
3002
|
-
return matchit.match(hostname, this.tree.tokens);
|
|
3003
|
-
}
|
|
3004
|
-
};
|
|
3005
|
-
|
|
3006
|
-
// src/router/legacy/url_builder.ts
|
|
3007
|
-
var UrlBuilder = class {
|
|
3008
|
-
/**
|
|
3009
|
-
* The parameters to apply on the route
|
|
3010
|
-
*/
|
|
3011
|
-
#params = {};
|
|
3012
|
-
/**
|
|
3013
|
-
* Query string to append to the route
|
|
3014
|
-
*/
|
|
3015
|
-
#qs = {};
|
|
3016
|
-
/**
|
|
3017
|
-
* Should we perform the route lookup or just build the
|
|
3018
|
-
* given pattern as it is.
|
|
3019
|
-
*/
|
|
3020
|
-
#shouldPerformLookup = true;
|
|
3021
|
-
/**
|
|
3022
|
-
* BaseURL to append to the constructored URL
|
|
3023
|
-
*/
|
|
3024
|
-
#baseUrl;
|
|
3025
|
-
/**
|
|
3026
|
-
* Route finder for finding route pattern
|
|
3027
|
-
*/
|
|
3028
|
-
#router;
|
|
3029
|
-
/**
|
|
3030
|
-
* Domain to use for URL generation
|
|
3031
|
-
*/
|
|
3032
|
-
#domain;
|
|
3033
|
-
/**
|
|
3034
|
-
* Creates a new UrlBuilder instance
|
|
3035
|
-
* @param router - The router instance
|
|
3036
|
-
* @param domain - Optional domain for URL generation
|
|
3037
|
-
*/
|
|
3038
|
-
constructor(router, domain) {
|
|
3039
|
-
this.#router = router;
|
|
3040
|
-
this.#domain = domain;
|
|
3041
|
-
}
|
|
3042
|
-
/**
|
|
3043
|
-
* Prefix a custom base URL to the final URI
|
|
3044
|
-
* @deprecated Instead use "@adonisjs/core/services/url_builder" instead
|
|
3045
|
-
*/
|
|
3046
|
-
prefixUrl(url) {
|
|
3047
|
-
this.#baseUrl = url;
|
|
3048
|
-
return this;
|
|
3049
|
-
}
|
|
3050
|
-
/**
|
|
3051
|
-
* Disable route lookup. Calling this method considers
|
|
3052
|
-
* the "identifier" as the route pattern
|
|
3053
|
-
* @deprecated Instead use "@adonisjs/core/services/url_builder" instead
|
|
3054
|
-
*/
|
|
3055
|
-
disableRouteLookup() {
|
|
3056
|
-
this.#shouldPerformLookup = false;
|
|
3057
|
-
return this;
|
|
3058
|
-
}
|
|
3059
|
-
/**
|
|
3060
|
-
* Append query string to the final URI
|
|
3061
|
-
* @deprecated Instead use "@adonisjs/core/services/url_builder" instead
|
|
3062
|
-
*/
|
|
3063
|
-
qs(queryString) {
|
|
3064
|
-
if (!queryString) {
|
|
3065
|
-
return this;
|
|
3066
|
-
}
|
|
3067
|
-
this.#qs = queryString;
|
|
3068
|
-
return this;
|
|
3069
|
-
}
|
|
3070
|
-
/**
|
|
3071
|
-
* Specify params to apply to the route pattern
|
|
3072
|
-
* @deprecated Instead use "@adonisjs/core/services/url_builder" instead
|
|
3073
|
-
*/
|
|
3074
|
-
params(params) {
|
|
3075
|
-
if (!params) {
|
|
3076
|
-
return this;
|
|
3077
|
-
}
|
|
3078
|
-
this.#params = params;
|
|
3079
|
-
return this;
|
|
3080
|
-
}
|
|
3081
|
-
/**
|
|
3082
|
-
* Generate URL for the given route identifier. The identifier can be the
|
|
3083
|
-
* route name, controller.method name or the route pattern
|
|
3084
|
-
* itself.
|
|
3085
|
-
*
|
|
3086
|
-
* @deprecated Instead use "@adonisjs/core/services/url_builder" instead
|
|
3087
|
-
* @param identifier - Route identifier to generate URL for
|
|
3088
|
-
* @returns Generated URL string
|
|
3089
|
-
*/
|
|
3090
|
-
make(identifier) {
|
|
3091
|
-
return this.#router.makeUrl(identifier, this.#params, {
|
|
3092
|
-
prefixUrl: this.#baseUrl,
|
|
3093
|
-
disableRouteLookup: !this.#shouldPerformLookup,
|
|
3094
|
-
domain: this.#domain,
|
|
3095
|
-
qs: this.#qs
|
|
3096
|
-
});
|
|
3097
|
-
}
|
|
3098
|
-
/**
|
|
3099
|
-
* Generate a signed URL for the given route identifier. The identifier can be the
|
|
3100
|
-
* route name, controller.method name or the route pattern
|
|
3101
|
-
* itself.
|
|
3102
|
-
*
|
|
3103
|
-
* @deprecated Instead use "@adonisjs/core/services/url_builder" instead
|
|
3104
|
-
*
|
|
3105
|
-
*/
|
|
3106
|
-
makeSigned(identifier, options) {
|
|
3107
|
-
return this.#router.makeSignedUrl(identifier, this.#params, {
|
|
3108
|
-
prefixUrl: this.#baseUrl,
|
|
3109
|
-
disableRouteLookup: !this.#shouldPerformLookup,
|
|
3110
|
-
domain: this.#domain,
|
|
3111
|
-
qs: this.#qs,
|
|
3112
|
-
...options
|
|
3113
|
-
});
|
|
3114
|
-
}
|
|
3115
|
-
};
|
|
3116
|
-
|
|
3117
|
-
// src/router/matchers.ts
|
|
3118
|
-
import Macroable3 from "@poppinss/macroable";
|
|
3119
|
-
var RouteMatchers = class extends Macroable3 {
|
|
3120
|
-
/**
|
|
3121
|
-
* Enforce value to be a number and also casts it to number data
|
|
3122
|
-
* type
|
|
3123
|
-
* @returns Route matcher configuration for numeric values
|
|
3124
|
-
*/
|
|
3125
|
-
number() {
|
|
3126
|
-
return { match: /^[0-9]+$/, cast: (value) => Number(value) };
|
|
3127
|
-
}
|
|
3128
|
-
/**
|
|
3129
|
-
* Enforce value to be formatted as uuid
|
|
3130
|
-
* @returns Route matcher configuration for UUID values
|
|
3131
|
-
*/
|
|
3132
|
-
uuid() {
|
|
3133
|
-
return {
|
|
3134
|
-
match: /^[0-9a-zA-F]{8}-[0-9a-zA-F]{4}-[0-9a-zA-F]{4}-[0-9a-zA-F]{4}-[0-9a-zA-F]{12}$/,
|
|
3135
|
-
cast: (value) => value.toLowerCase()
|
|
3136
|
-
};
|
|
3137
|
-
}
|
|
3138
|
-
/**
|
|
3139
|
-
* Enforce value to be formatted as slug
|
|
3140
|
-
* @returns Route matcher configuration for slug values
|
|
3141
|
-
*/
|
|
3142
|
-
slug() {
|
|
3143
|
-
return { match: /^[^\s-_](?!.*?[-_]{2,})([a-z0-9-\\]{1,})[^\s]*[^-_\s]$/ };
|
|
3144
|
-
}
|
|
3145
|
-
};
|
|
3146
|
-
|
|
3147
|
-
// src/define_middleware.ts
|
|
3148
|
-
import { moduleImporter } from "@adonisjs/fold";
|
|
3149
|
-
function middlewareReferenceBuilder(name, middleware) {
|
|
3150
|
-
const handler = moduleImporter(middleware, "handle").toHandleMethod();
|
|
3151
|
-
return function(...args) {
|
|
3152
|
-
return {
|
|
3153
|
-
...handler,
|
|
3154
|
-
name,
|
|
3155
|
-
reference: middleware,
|
|
3156
|
-
args: args[0]
|
|
3157
|
-
};
|
|
3158
|
-
};
|
|
3159
|
-
}
|
|
3160
|
-
function defineNamedMiddleware(collection) {
|
|
3161
|
-
return Object.keys(collection).reduce(
|
|
3162
|
-
(result, key) => {
|
|
3163
|
-
result[key] = middlewareReferenceBuilder(key, collection[key]);
|
|
3164
|
-
return result;
|
|
3165
|
-
},
|
|
3166
|
-
{}
|
|
3167
|
-
);
|
|
3168
|
-
}
|
|
3169
|
-
|
|
3170
|
-
// src/router/signed_url_builder.ts
|
|
3171
|
-
function createSignedUrlBuilder(router, encryption, searchParamsStringifier) {
|
|
3172
|
-
let domainsList;
|
|
3173
|
-
function createSignedUrlForRoute(identifier, params, options, method) {
|
|
3174
|
-
if (!domainsList) {
|
|
3175
|
-
domainsList = Object.keys(router.toJSON()).filter((domain2) => domain2 !== "root");
|
|
3176
|
-
}
|
|
3177
|
-
const domain = domainsList.find((name) => identifier.startsWith(`${name}@`));
|
|
3178
|
-
const routeIdentifier = domain ? identifier.replace(new RegExp(`^${domain}@`), "") : identifier;
|
|
3179
|
-
const route = router.findOrFail(routeIdentifier, domain, method, true);
|
|
3180
|
-
return createSignedURL(
|
|
3181
|
-
route.name ?? route.pattern,
|
|
3182
|
-
route.tokens,
|
|
3183
|
-
searchParamsStringifier,
|
|
3184
|
-
encryption,
|
|
3185
|
-
params,
|
|
3186
|
-
options
|
|
3187
|
-
);
|
|
3188
|
-
}
|
|
3189
|
-
const signedRoute = function route(...[identifier, params, options]) {
|
|
3190
|
-
return createSignedUrlForRoute(identifier, params, options);
|
|
3191
|
-
};
|
|
3192
|
-
signedRoute.get = function routeGet(...[identifier, params, options]) {
|
|
3193
|
-
const method = "GET";
|
|
3194
|
-
const url = createSignedUrlForRoute(identifier, params, options, method);
|
|
3195
|
-
return {
|
|
3196
|
-
url,
|
|
3197
|
-
method,
|
|
3198
|
-
toString() {
|
|
3199
|
-
return url;
|
|
3200
|
-
},
|
|
3201
|
-
form: {
|
|
3202
|
-
action: url,
|
|
3203
|
-
method
|
|
3204
|
-
}
|
|
3205
|
-
};
|
|
3206
|
-
};
|
|
3207
|
-
signedRoute.post = function routePost(...[identifier, params, options]) {
|
|
3208
|
-
const method = "POST";
|
|
3209
|
-
const url = createSignedUrlForRoute(identifier, params, options, method);
|
|
3210
|
-
return {
|
|
3211
|
-
url,
|
|
3212
|
-
method,
|
|
3213
|
-
toString() {
|
|
3214
|
-
return url;
|
|
3215
|
-
},
|
|
3216
|
-
form: {
|
|
3217
|
-
action: url,
|
|
3218
|
-
method
|
|
3219
|
-
}
|
|
3220
|
-
};
|
|
3221
|
-
};
|
|
3222
|
-
signedRoute.put = function routePut(...[identifier, params, options]) {
|
|
3223
|
-
const method = "PUT";
|
|
3224
|
-
const url = createSignedUrlForRoute(identifier, params, options, method);
|
|
3225
|
-
return {
|
|
3226
|
-
url,
|
|
3227
|
-
method,
|
|
3228
|
-
toString() {
|
|
3229
|
-
return url;
|
|
3230
|
-
},
|
|
3231
|
-
form: {
|
|
3232
|
-
action: url,
|
|
3233
|
-
method
|
|
3234
|
-
}
|
|
3235
|
-
};
|
|
3236
|
-
};
|
|
3237
|
-
signedRoute.patch = function routePatch(...[identifier, params, options]) {
|
|
3238
|
-
const method = "PATCH";
|
|
3239
|
-
const url = createSignedUrlForRoute(identifier, params, options, method);
|
|
3240
|
-
return {
|
|
3241
|
-
url,
|
|
3242
|
-
method,
|
|
3243
|
-
toString() {
|
|
3244
|
-
return url;
|
|
3245
|
-
},
|
|
3246
|
-
form: {
|
|
3247
|
-
action: url,
|
|
3248
|
-
method
|
|
3249
|
-
}
|
|
3250
|
-
};
|
|
3251
|
-
};
|
|
3252
|
-
signedRoute.delete = function routeDelete(...[identifier, params, options]) {
|
|
3253
|
-
const method = "DELETE";
|
|
3254
|
-
const url = createSignedUrlForRoute(identifier, params, options, method);
|
|
3255
|
-
return {
|
|
3256
|
-
url,
|
|
3257
|
-
method,
|
|
3258
|
-
toString() {
|
|
3259
|
-
return url;
|
|
3260
|
-
},
|
|
3261
|
-
form: {
|
|
3262
|
-
action: url,
|
|
3263
|
-
method
|
|
3264
|
-
}
|
|
3265
|
-
};
|
|
3266
|
-
};
|
|
3267
|
-
signedRoute.method = function routeGet(method, ...[identifier, params, options]) {
|
|
3268
|
-
const url = createSignedUrlForRoute(identifier, params, options, method);
|
|
3269
|
-
return {
|
|
3270
|
-
url,
|
|
3271
|
-
method,
|
|
3272
|
-
toString() {
|
|
3273
|
-
return url;
|
|
3274
|
-
},
|
|
3275
|
-
form: {
|
|
3276
|
-
action: url,
|
|
3277
|
-
method
|
|
3278
|
-
}
|
|
3279
|
-
};
|
|
3280
|
-
};
|
|
3281
|
-
return signedRoute;
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
// src/router/main.ts
|
|
3285
|
-
var Router = class {
|
|
3286
|
-
/**
|
|
3287
|
-
* Flag to avoid re-comitting routes to the store
|
|
3288
|
-
*/
|
|
3289
|
-
#commited = false;
|
|
3290
|
-
/**
|
|
3291
|
-
* Application is needed to resolve string based controller expressions
|
|
3292
|
-
*/
|
|
3293
|
-
#app;
|
|
3294
|
-
/**
|
|
3295
|
-
* Store with tokenized routes
|
|
3296
|
-
*/
|
|
3297
|
-
#store = new RoutesStore();
|
|
3298
|
-
/**
|
|
3299
|
-
* Encryption for making signed URLs
|
|
3300
|
-
*/
|
|
3301
|
-
#encryption;
|
|
3302
|
-
/**
|
|
3303
|
-
* Global matchers to test route params against regular expressions.
|
|
3304
|
-
*/
|
|
3305
|
-
#globalMatchers = {};
|
|
3306
|
-
/**
|
|
3307
|
-
* Middleware store to be shared with the routes
|
|
3308
|
-
*/
|
|
3309
|
-
#middleware = [];
|
|
3310
|
-
/**
|
|
3311
|
-
* A boolean to tell the router that a group is in
|
|
3312
|
-
* open state right now
|
|
3313
|
-
*/
|
|
3314
|
-
#openedGroups = [];
|
|
3315
|
-
/**
|
|
3316
|
-
* Collection of routes to be committed with the store, including
|
|
3317
|
-
* route resource and route group.
|
|
3318
|
-
*/
|
|
3319
|
-
#routesToBeCommitted = [];
|
|
3320
|
-
/**
|
|
3321
|
-
* A flag to know if routes for explicit domains have been registered.
|
|
3322
|
-
* The boolean is computed after calling the "commit" method.
|
|
3323
|
-
*/
|
|
3324
|
-
usingDomains = false;
|
|
3325
|
-
/**
|
|
3326
|
-
* Shortcut methods for commonly used route matchers
|
|
3327
|
-
*/
|
|
3328
|
-
matchers = new RouteMatchers();
|
|
3329
|
-
/**
|
|
3330
|
-
* Check if routes have been committed to the store. Once
|
|
3331
|
-
* routes are committed, defining new set of routes will
|
|
3332
|
-
* have no impact
|
|
3333
|
-
*/
|
|
3334
|
-
get commited() {
|
|
3335
|
-
return this.#commited;
|
|
3336
|
-
}
|
|
3337
|
-
/**
|
|
3338
|
-
* Query string parser for making URLs
|
|
3339
|
-
*/
|
|
3340
|
-
qs;
|
|
3341
|
-
/**
|
|
3342
|
-
* The URLBuilder offers a type-safe API for creating URL for pre-registered
|
|
3343
|
-
* routes or the route patterns.
|
|
3344
|
-
*
|
|
3345
|
-
* We recommend using the URLBuilder over the "makeUrl" and "makeSignedUrl"
|
|
3346
|
-
* methods.
|
|
3347
|
-
*/
|
|
3348
|
-
urlBuilder;
|
|
3349
|
-
/**
|
|
3350
|
-
* List of route references kept for lookup.
|
|
3351
|
-
*/
|
|
3352
|
-
routes = {};
|
|
3353
|
-
/**
|
|
3354
|
-
* Creates a new Router instance
|
|
3355
|
-
* @param app - The AdonisJS application instance
|
|
3356
|
-
* @param encryption - Encryption service for signed URLs
|
|
3357
|
-
* @param qsParser - Query string parser for URL generation
|
|
3358
|
-
*/
|
|
3359
|
-
constructor(app, encryption, qsParser) {
|
|
3360
|
-
this.#app = app;
|
|
3361
|
-
this.#encryption = encryption;
|
|
3362
|
-
this.qs = qsParser;
|
|
3363
|
-
this.urlBuilder = {
|
|
3364
|
-
urlFor: createUrlBuilder(() => this.toJSON(), this.qs.stringify),
|
|
3365
|
-
signedUrlFor: createSignedUrlBuilder(this, this.#encryption, this.qs.stringify)
|
|
3366
|
-
};
|
|
3367
|
-
}
|
|
3368
|
-
/**
|
|
3369
|
-
* Register route JSON payload
|
|
3370
|
-
*/
|
|
3371
|
-
register(route) {
|
|
3372
|
-
this.routes[route.domain] = this.routes[route.domain] || [];
|
|
3373
|
-
this.routes[route.domain].push(route);
|
|
3374
|
-
}
|
|
3375
|
-
/**
|
|
3376
|
-
* Push a give router entity to the list of routes or the
|
|
3377
|
-
* recently opened group.
|
|
3378
|
-
*/
|
|
3379
|
-
#pushToRoutes(entity) {
|
|
3380
|
-
const openedGroup = this.#openedGroups[this.#openedGroups.length - 1];
|
|
3381
|
-
if (openedGroup) {
|
|
3382
|
-
openedGroup.routes.push(entity);
|
|
3383
|
-
return;
|
|
3384
|
-
}
|
|
3385
|
-
this.#routesToBeCommitted.push(entity);
|
|
3386
|
-
}
|
|
3387
|
-
/**
|
|
3388
|
-
* Parses the route pattern
|
|
3389
|
-
* @param pattern - The route pattern to parse
|
|
3390
|
-
* @param matchers - Optional route matchers
|
|
3391
|
-
*/
|
|
3392
|
-
parsePattern(pattern, matchers) {
|
|
3393
|
-
return parseRoute(pattern, matchers);
|
|
3394
|
-
}
|
|
3395
|
-
/**
|
|
3396
|
-
* Define an array of middleware to use on all the routes.
|
|
3397
|
-
* Calling this method multiple times pushes to the
|
|
3398
|
-
* existing list of middleware
|
|
3399
|
-
* @param middleware - Array of middleware classes to apply globally
|
|
3400
|
-
* @returns Current Router instance for method chaining
|
|
3401
|
-
*/
|
|
3402
|
-
use(middleware) {
|
|
3403
|
-
middleware.forEach(
|
|
3404
|
-
(one) => this.#middleware.push({
|
|
3405
|
-
reference: one,
|
|
3406
|
-
...moduleImporter2(one, "handle").toHandleMethod()
|
|
3407
|
-
})
|
|
3408
|
-
);
|
|
3409
|
-
return this;
|
|
3410
|
-
}
|
|
3411
|
-
/**
|
|
3412
|
-
* Define a collection of named middleware. The defined collection is
|
|
3413
|
-
* not registered anywhere, but instead converted in a new collection
|
|
3414
|
-
* of functions you can apply on the routes, or router groups.
|
|
3415
|
-
* @param collection - Object mapping middleware names to middleware classes
|
|
3416
|
-
* @returns Named middleware functions
|
|
3417
|
-
*/
|
|
3418
|
-
named(collection) {
|
|
3419
|
-
return defineNamedMiddleware(collection);
|
|
3420
|
-
}
|
|
3421
|
-
/**
|
|
3422
|
-
* Add route for a given pattern and methods
|
|
3423
|
-
* @param pattern - The route pattern
|
|
3424
|
-
* @param methods - Array of HTTP methods
|
|
3425
|
-
* @param handler - Route handler (function, string, or controller tuple)
|
|
3426
|
-
* @returns The created route instance
|
|
3427
|
-
*/
|
|
3428
|
-
route(pattern, methods, handler) {
|
|
3429
|
-
const route = new Route(this.#app, this.#middleware, {
|
|
3430
|
-
pattern,
|
|
3431
|
-
methods,
|
|
3432
|
-
handler,
|
|
3433
|
-
globalMatchers: this.#globalMatchers
|
|
3434
|
-
});
|
|
3435
|
-
this.#pushToRoutes(route);
|
|
3436
|
-
return route;
|
|
3437
|
-
}
|
|
3438
|
-
/**
|
|
3439
|
-
* Define a route that handles all common HTTP methods
|
|
3440
|
-
* @param pattern - The route pattern
|
|
3441
|
-
* @param handler - Route handler (function, string, or controller tuple)
|
|
3442
|
-
* @returns The created route instance
|
|
3443
|
-
*/
|
|
3444
|
-
any(pattern, handler) {
|
|
3445
|
-
return this.route(
|
|
3446
|
-
pattern,
|
|
3447
|
-
["HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
3448
|
-
handler
|
|
3449
|
-
);
|
|
3450
|
-
}
|
|
3451
|
-
/**
|
|
3452
|
-
* Define `GET` route
|
|
3453
|
-
* @param pattern - The route pattern
|
|
3454
|
-
* @param handler - Route handler (function, string, or controller tuple)
|
|
3455
|
-
* @returns The created route instance
|
|
3456
|
-
*/
|
|
3457
|
-
get(pattern, handler) {
|
|
3458
|
-
return this.route(pattern, ["GET", "HEAD"], handler);
|
|
3459
|
-
}
|
|
3460
|
-
/**
|
|
3461
|
-
* Define `POST` route
|
|
3462
|
-
* @param pattern - The route pattern
|
|
3463
|
-
* @param handler - Route handler (function, string, or controller tuple)
|
|
3464
|
-
* @returns The created route instance
|
|
3465
|
-
*/
|
|
3466
|
-
post(pattern, handler) {
|
|
3467
|
-
return this.route(pattern, ["POST"], handler);
|
|
3468
|
-
}
|
|
3469
|
-
/**
|
|
3470
|
-
* Define `PUT` route
|
|
3471
|
-
* @param pattern - The route pattern
|
|
3472
|
-
* @param handler - Route handler (function, string, or controller tuple)
|
|
3473
|
-
* @returns The created route instance
|
|
3474
|
-
*/
|
|
3475
|
-
put(pattern, handler) {
|
|
3476
|
-
return this.route(pattern, ["PUT"], handler);
|
|
3477
|
-
}
|
|
3478
|
-
/**
|
|
3479
|
-
* Define `PATCH` route
|
|
3480
|
-
* @param pattern - The route pattern
|
|
3481
|
-
* @param handler - Route handler (function, string, or controller tuple)
|
|
3482
|
-
* @returns The created route instance
|
|
3483
|
-
*/
|
|
3484
|
-
patch(pattern, handler) {
|
|
3485
|
-
return this.route(pattern, ["PATCH"], handler);
|
|
3486
|
-
}
|
|
3487
|
-
/**
|
|
3488
|
-
* Define `DELETE` route
|
|
3489
|
-
* @param pattern - The route pattern
|
|
3490
|
-
* @param handler - Route handler (function, string, or controller tuple)
|
|
3491
|
-
* @returns The created route instance
|
|
3492
|
-
*/
|
|
3493
|
-
delete(pattern, handler) {
|
|
3494
|
-
return this.route(pattern, ["DELETE"], handler);
|
|
3495
|
-
}
|
|
3496
|
-
/**
|
|
3497
|
-
* Creates a group of routes. A route group can apply transforms
|
|
3498
|
-
* to routes in bulk
|
|
3499
|
-
* @param callback - Function that defines routes within the group
|
|
3500
|
-
* @returns The created route group instance
|
|
3501
|
-
*/
|
|
3502
|
-
group(callback) {
|
|
3503
|
-
const group = new RouteGroup([]);
|
|
3504
|
-
this.#pushToRoutes(group);
|
|
3505
|
-
this.#openedGroups.push(group);
|
|
3506
|
-
callback();
|
|
3507
|
-
this.#openedGroups.pop();
|
|
3508
|
-
return group;
|
|
3509
|
-
}
|
|
3510
|
-
/**
|
|
3511
|
-
* Registers a route resource with conventional set of routes
|
|
3512
|
-
* @param resource - The resource name
|
|
3513
|
-
* @param controller - Controller to handle the resource
|
|
3514
|
-
* @returns The created route resource instance
|
|
3515
|
-
*/
|
|
3516
|
-
resource(resource, controller) {
|
|
3517
|
-
const resourceInstance = new RouteResource(this.#app, this.#middleware, {
|
|
3518
|
-
resource,
|
|
3519
|
-
controller,
|
|
3520
|
-
shallow: false,
|
|
3521
|
-
globalMatchers: this.#globalMatchers
|
|
3522
|
-
});
|
|
3523
|
-
this.#pushToRoutes(resourceInstance);
|
|
3524
|
-
return resourceInstance;
|
|
3525
|
-
}
|
|
3526
|
-
/**
|
|
3527
|
-
* Register a route resource with shallow nested routes.
|
|
3528
|
-
* @param resource - The resource name
|
|
3529
|
-
* @param controller - Controller to handle the resource
|
|
3530
|
-
* @returns The created route resource instance
|
|
3531
|
-
*/
|
|
3532
|
-
shallowResource(resource, controller) {
|
|
3533
|
-
const resourceInstance = new RouteResource(this.#app, this.#middleware, {
|
|
3534
|
-
resource,
|
|
3535
|
-
controller,
|
|
3536
|
-
shallow: true,
|
|
3537
|
-
globalMatchers: this.#globalMatchers
|
|
3538
|
-
});
|
|
3539
|
-
this.#pushToRoutes(resourceInstance);
|
|
3540
|
-
return resourceInstance;
|
|
3541
|
-
}
|
|
3542
|
-
/**
|
|
3543
|
-
* Returns a brisk route instance for a given URL pattern
|
|
3544
|
-
* @param pattern - The route pattern
|
|
3545
|
-
* @returns The created brisk route instance
|
|
3546
|
-
*/
|
|
3547
|
-
on(pattern) {
|
|
3548
|
-
const briskRoute = new BriskRoute(this.#app, this.#middleware, {
|
|
3549
|
-
pattern,
|
|
3550
|
-
globalMatchers: this.#globalMatchers
|
|
3551
|
-
});
|
|
3552
|
-
this.#pushToRoutes(briskRoute);
|
|
3553
|
-
return briskRoute;
|
|
3554
|
-
}
|
|
3555
|
-
/**
|
|
3556
|
-
* Define matcher for a given param. The global params are applied
|
|
3557
|
-
* on all the routes (unless overridden at the route level).
|
|
3558
|
-
* @param param - The parameter name to match
|
|
3559
|
-
* @param matcher - The matcher pattern (RegExp, string, or RouteMatcher)
|
|
3560
|
-
* @returns Current Router instance for method chaining
|
|
3561
|
-
*/
|
|
3562
|
-
where(param, matcher) {
|
|
3563
|
-
if (typeof matcher === "string") {
|
|
3564
|
-
this.#globalMatchers[param] = { match: new RegExp(matcher) };
|
|
3565
|
-
} else if (is2.regExp(matcher)) {
|
|
3566
|
-
this.#globalMatchers[param] = { match: matcher };
|
|
3567
|
-
} else {
|
|
3568
|
-
this.#globalMatchers[param] = matcher;
|
|
3569
|
-
}
|
|
3570
|
-
return this;
|
|
3571
|
-
}
|
|
3572
|
-
/**
|
|
3573
|
-
* Commit routes to the store. The router is freezed after the
|
|
3574
|
-
* commit method is called.
|
|
3575
|
-
*/
|
|
3576
|
-
commit() {
|
|
3577
|
-
if (this.#commited) {
|
|
3578
|
-
return;
|
|
3579
|
-
}
|
|
3580
|
-
debug_default("Committing routes to the routes store");
|
|
3581
|
-
const routeNamesByDomain = /* @__PURE__ */ new Map();
|
|
3582
|
-
toRoutesJSON(this.#routesToBeCommitted).forEach((route) => {
|
|
3583
|
-
if (!routeNamesByDomain.has(route.domain)) {
|
|
3584
|
-
routeNamesByDomain.set(route.domain, /* @__PURE__ */ new Set());
|
|
3585
|
-
}
|
|
3586
|
-
const routeNames = routeNamesByDomain.get(route.domain);
|
|
3587
|
-
if (route.name && routeNames.has(route.name)) {
|
|
3588
|
-
throw new RuntimeException3(
|
|
3589
|
-
`A route with name "${route.name}" already exists. It may happen when two routes use the same controller, so make sure to give explicit names to these routes`
|
|
3590
|
-
);
|
|
3591
|
-
}
|
|
3592
|
-
if (route.name) {
|
|
3593
|
-
routeNames.add(route.name);
|
|
3594
|
-
}
|
|
3595
|
-
this.register(route);
|
|
3596
|
-
this.#store.add(route);
|
|
3597
|
-
});
|
|
3598
|
-
routeNamesByDomain.clear();
|
|
3599
|
-
this.usingDomains = this.#store.usingDomains;
|
|
3600
|
-
this.#routesToBeCommitted = [];
|
|
3601
|
-
this.#globalMatchers = {};
|
|
3602
|
-
this.#middleware = [];
|
|
3603
|
-
this.#openedGroups = [];
|
|
3604
|
-
this.#commited = true;
|
|
3605
|
-
}
|
|
3606
|
-
/**
|
|
3607
|
-
* Finds a route by its identifier. The identifier can be the
|
|
3608
|
-
* route name, controller.method name or the route pattern
|
|
3609
|
-
* itself.
|
|
3610
|
-
*
|
|
3611
|
-
* When "disableLegacyLookup" is set, the lookup will be performed
|
|
3612
|
-
* only using the route name
|
|
3613
|
-
* @param routeIdentifier - Route name, pattern, or controller reference
|
|
3614
|
-
* @param domain - Optional domain to search within
|
|
3615
|
-
* @param method - Optional HTTP method to filter by
|
|
3616
|
-
* @param disableLegacyLookup - Whether to disable legacy lookup strategies
|
|
3617
|
-
* @returns Found route or null if not found
|
|
3618
|
-
*/
|
|
3619
|
-
find(routeIdentifier, domain, method, disableLegacyLookup) {
|
|
3620
|
-
return findRoute(this.routes, routeIdentifier, domain, method, disableLegacyLookup);
|
|
3621
|
-
}
|
|
3622
|
-
/**
|
|
3623
|
-
* Finds a route by its identifier. The identifier can be the
|
|
3624
|
-
* route name, controller.method name or the route pattern
|
|
3625
|
-
* itself.
|
|
3626
|
-
*
|
|
3627
|
-
* An error is raised when unable to find the route.
|
|
3628
|
-
*
|
|
3629
|
-
* When "disableLegacyLookup" is set, the lookup will be performed
|
|
3630
|
-
* only using the route name
|
|
3631
|
-
* @param routeIdentifier - Route name, pattern, or controller reference
|
|
3632
|
-
* @param domain - Optional domain to search within
|
|
3633
|
-
* @param method - Optional HTTP method to filter by
|
|
3634
|
-
* @param disableLegacyLookup - Whether to disable legacy lookup strategies
|
|
3635
|
-
* @returns Found route
|
|
3636
|
-
* @throws Error when route is not found
|
|
3637
|
-
*/
|
|
3638
|
-
findOrFail(routeIdentifier, domain, method, disableLegacyLookup) {
|
|
3639
|
-
const route = this.find(routeIdentifier, domain, method, disableLegacyLookup);
|
|
3640
|
-
if (!route) {
|
|
3641
|
-
if (method) {
|
|
3642
|
-
throw new Error(`Cannot lookup route "${routeIdentifier}" for method "${method}"`);
|
|
3643
|
-
}
|
|
3644
|
-
throw new Error(`Cannot lookup route "${routeIdentifier}"`);
|
|
3645
|
-
}
|
|
3646
|
-
return route;
|
|
3647
|
-
}
|
|
3648
|
-
/**
|
|
3649
|
-
* Check if a route exists. The identifier can be the
|
|
3650
|
-
* route name, controller.method name or the route pattern
|
|
3651
|
-
* itself.
|
|
3652
|
-
*
|
|
3653
|
-
* When "followLookupStrategy" is enabled, the lookup will be performed
|
|
3654
|
-
* on the basis of the lookup strategy enabled via the "lookupStrategies"
|
|
3655
|
-
* method. The default lookupStrategy is "name" and "pattern".
|
|
3656
|
-
* @param routeIdentifier - Route name, pattern, or controller reference
|
|
3657
|
-
* @param domain - Optional domain to search within
|
|
3658
|
-
* @param method - Optional HTTP method to filter by
|
|
3659
|
-
* @param followLookupStrategy - Whether to follow the configured lookup strategy
|
|
3660
|
-
* @returns True if route exists, false otherwise
|
|
3661
|
-
*/
|
|
3662
|
-
has(routeIdentifier, domain, method, followLookupStrategy) {
|
|
3663
|
-
return !!this.find(routeIdentifier, domain, method, followLookupStrategy);
|
|
3664
|
-
}
|
|
3665
|
-
/**
|
|
3666
|
-
* Returns a list of routes grouped by their domain names
|
|
3667
|
-
* @returns Object mapping domain names to route arrays
|
|
3668
|
-
*/
|
|
3669
|
-
toJSON() {
|
|
3670
|
-
return this.routes;
|
|
3671
|
-
}
|
|
3672
|
-
/**
|
|
3673
|
-
* Generates types for the URL builder. These types must
|
|
3674
|
-
* be written inside a file for the URL builder to
|
|
3675
|
-
* pick them up.
|
|
3676
|
-
* @param indentation - Indentation level for generated types
|
|
3677
|
-
* @returns Generated TypeScript types as string
|
|
3678
|
-
*/
|
|
3679
|
-
generateTypes(indentation = 0) {
|
|
3680
|
-
const routesList = {};
|
|
3681
|
-
function trackRoute(route, domain) {
|
|
3682
|
-
let params = [];
|
|
3683
|
-
let paramsTuple = [];
|
|
3684
|
-
let hasRequiredParams = false;
|
|
3685
|
-
for (let token of route.tokens) {
|
|
3686
|
-
if (token.type === 1) {
|
|
3687
|
-
hasRequiredParams = true;
|
|
3688
|
-
params.push(`'${token.val}': ParamValue`);
|
|
3689
|
-
paramsTuple.push("ParamValue");
|
|
3690
|
-
} else if (token.type === 3) {
|
|
3691
|
-
params.push(`'${token.val}'?: ParamValue`);
|
|
3692
|
-
paramsTuple.push("ParamValue?");
|
|
3693
|
-
} else if (token.type === 2) {
|
|
3694
|
-
hasRequiredParams = true;
|
|
3695
|
-
params.push(`'*': ParamValue[]`);
|
|
3696
|
-
paramsTuple.push("...ParamValue[]");
|
|
3697
|
-
break;
|
|
3698
|
-
}
|
|
3699
|
-
}
|
|
3700
|
-
route.methods.forEach((method) => {
|
|
3701
|
-
routesList["ALL"] = routesList["ALL"] ?? {};
|
|
3702
|
-
routesList[method] = routesList[method] ?? {};
|
|
3703
|
-
const identifiers = [];
|
|
3704
|
-
if (route.name) {
|
|
3705
|
-
identifiers.push(
|
|
3706
|
-
domain && routesList[method][route.name] ? `${domain}@${route.name}` : route.name
|
|
3707
|
-
);
|
|
3708
|
-
}
|
|
3709
|
-
identifiers.forEach((identifier) => {
|
|
3710
|
-
routesList["ALL"][identifier] = {
|
|
3711
|
-
params,
|
|
3712
|
-
paramsTuple,
|
|
3713
|
-
hasRequiredParams
|
|
3714
|
-
};
|
|
3715
|
-
routesList[method][identifier] = {
|
|
3716
|
-
params,
|
|
3717
|
-
paramsTuple,
|
|
3718
|
-
hasRequiredParams
|
|
3719
|
-
};
|
|
3720
|
-
});
|
|
3721
|
-
});
|
|
3722
|
-
}
|
|
3723
|
-
const domains = Object.keys(this.routes).filter((domain) => domain !== "root");
|
|
3724
|
-
this.routes["root"]?.forEach((route) => trackRoute.bind(this)(route));
|
|
3725
|
-
domains.forEach(
|
|
3726
|
-
(domain) => this.routes[domain].forEach((route) => trackRoute.bind(this)(route, domain))
|
|
3727
|
-
);
|
|
3728
|
-
return {
|
|
3729
|
-
imports: [],
|
|
3730
|
-
types: ["type ParamValue = string | number | bigint | boolean"],
|
|
3731
|
-
routes: Object.keys(routesList).reduce((result, method) => {
|
|
3732
|
-
result.push(`${" ".repeat(indentation)}${method}: {`);
|
|
3733
|
-
Object.keys(routesList[method]).forEach((identifier) => {
|
|
3734
|
-
const key = `'${identifier}'`;
|
|
3735
|
-
const { paramsTuple, hasRequiredParams, params } = routesList[method][identifier];
|
|
3736
|
-
const dictName = hasRequiredParams ? "params" : "params?";
|
|
3737
|
-
const tupleName = hasRequiredParams ? "paramsTuple" : "paramsTuple?";
|
|
3738
|
-
const dictValue = `{${params.join(",")}}`;
|
|
3739
|
-
const tupleValue = `[${paramsTuple?.join(",")}]`;
|
|
3740
|
-
const value = `{ ${tupleName}: ${tupleValue}; ${dictName}: ${dictValue} }`;
|
|
3741
|
-
result.push(`${" ".repeat(indentation + 2)}${key}: ${value}`);
|
|
3742
|
-
});
|
|
3743
|
-
result.push(`${" ".repeat(indentation)}}`);
|
|
3744
|
-
return result;
|
|
3745
|
-
}, []).join("\n")
|
|
3746
|
-
};
|
|
3747
|
-
}
|
|
3748
|
-
/**
|
|
3749
|
-
* Find route for a given URL, method and optionally domain
|
|
3750
|
-
* @param uri - The URI to match
|
|
3751
|
-
* @param method - HTTP method
|
|
3752
|
-
* @param shouldDecodeParam - Whether to decode parameters
|
|
3753
|
-
* @param hostname - Optional hostname for domain matching
|
|
3754
|
-
* @returns Matched route or null if no match found
|
|
3755
|
-
*/
|
|
3756
|
-
match(uri, method, shouldDecodeParam, hostname) {
|
|
3757
|
-
const matchingDomain = this.#store.matchDomain(hostname);
|
|
3758
|
-
return matchingDomain.length ? this.#store.match(uri, method, shouldDecodeParam, {
|
|
3759
|
-
tokens: matchingDomain,
|
|
3760
|
-
hostname
|
|
3761
|
-
}) : this.#store.match(uri, method, shouldDecodeParam);
|
|
3762
|
-
}
|
|
3763
|
-
/**
|
|
3764
|
-
* Create URL builder instance.
|
|
3765
|
-
* @deprecated Instead use "@adonisjs/core/services/url_builder" instead
|
|
3766
|
-
*/
|
|
3767
|
-
builder() {
|
|
3768
|
-
return new UrlBuilder(this);
|
|
3769
|
-
}
|
|
3770
|
-
/**
|
|
3771
|
-
* Create URL builder instance for a given domain.
|
|
3772
|
-
* @deprecated
|
|
3773
|
-
*
|
|
3774
|
-
* Instead use "@adonisjs/core/services/url_builder"
|
|
3775
|
-
*/
|
|
3776
|
-
builderForDomain(domain) {
|
|
3777
|
-
return new UrlBuilder(this, domain);
|
|
3778
|
-
}
|
|
3779
|
-
/**
|
|
3780
|
-
* Make URL to a pre-registered route
|
|
3781
|
-
*
|
|
3782
|
-
* @deprecated
|
|
3783
|
-
* Instead use "@adonisjs/core/services/url_builder"
|
|
3784
|
-
*/
|
|
3785
|
-
makeUrl(routeIdentifier, params, options) {
|
|
3786
|
-
const normalizedOptions = Object.assign({}, options);
|
|
3787
|
-
if (options?.disableRouteLookup) {
|
|
3788
|
-
return createURL(
|
|
3789
|
-
routeIdentifier,
|
|
3790
|
-
parseRoute(routeIdentifier),
|
|
3791
|
-
this.qs.stringify,
|
|
3792
|
-
params,
|
|
3793
|
-
options
|
|
3794
|
-
);
|
|
3795
|
-
}
|
|
3796
|
-
const route = this.findOrFail(routeIdentifier, normalizedOptions.domain);
|
|
3797
|
-
return createURL(route.name ?? route.pattern, route.tokens, this.qs.stringify, params, options);
|
|
3798
|
-
}
|
|
3799
|
-
/**
|
|
3800
|
-
* Makes a signed URL to a pre-registered route.
|
|
3801
|
-
*
|
|
3802
|
-
* @deprecated
|
|
3803
|
-
* Instead use "@adonisjs/core/services/url_builder"
|
|
3804
|
-
*/
|
|
3805
|
-
makeSignedUrl(routeIdentifier, params, options) {
|
|
3806
|
-
const normalizedOptions = Object.assign({}, options);
|
|
3807
|
-
if (options?.disableRouteLookup) {
|
|
3808
|
-
return createSignedURL(
|
|
3809
|
-
routeIdentifier,
|
|
3810
|
-
parseRoute(routeIdentifier),
|
|
3811
|
-
this.qs.stringify,
|
|
3812
|
-
this.#encryption,
|
|
3813
|
-
params,
|
|
3814
|
-
options
|
|
3815
|
-
);
|
|
3816
|
-
}
|
|
3817
|
-
const route = this.findOrFail(routeIdentifier, normalizedOptions.domain);
|
|
3818
|
-
return createSignedURL(
|
|
3819
|
-
route.name ?? route.pattern,
|
|
3820
|
-
route.tokens,
|
|
3821
|
-
this.qs.stringify,
|
|
3822
|
-
this.#encryption,
|
|
3823
|
-
params,
|
|
3824
|
-
options
|
|
3825
|
-
);
|
|
3826
|
-
}
|
|
3827
|
-
};
|
|
3828
|
-
|
|
3829
|
-
// src/http_context/main.ts
|
|
3830
|
-
import { inspect } from "util";
|
|
3831
|
-
import Macroable4 from "@poppinss/macroable";
|
|
3832
|
-
import { RuntimeException as RuntimeException4 } from "@poppinss/utils/exception";
|
|
3833
|
-
|
|
3834
|
-
// src/http_context/local_storage.ts
|
|
3835
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
3836
|
-
var asyncLocalStorage = {
|
|
3837
|
-
isEnabled: false,
|
|
3838
|
-
storage: null,
|
|
3839
|
-
create() {
|
|
3840
|
-
this.isEnabled = true;
|
|
3841
|
-
this.storage = new AsyncLocalStorage();
|
|
3842
|
-
return this.storage;
|
|
3843
|
-
},
|
|
3844
|
-
destroy() {
|
|
3845
|
-
this.isEnabled = false;
|
|
3846
|
-
this.storage = null;
|
|
3847
|
-
}
|
|
3848
|
-
};
|
|
3849
|
-
|
|
3850
|
-
// src/http_context/main.ts
|
|
3851
|
-
var HttpContext = class extends Macroable4 {
|
|
3852
|
-
/**
|
|
3853
|
-
* Creates a new HttpContext instance
|
|
3854
|
-
*
|
|
3855
|
-
* @param {Request} request - The HTTP request instance
|
|
3856
|
-
* @param {Response} response - The HTTP response instance
|
|
3857
|
-
* @param {Logger} logger - The logger instance
|
|
3858
|
-
* @param {ContainerResolver<any>} containerResolver - The IoC container resolver
|
|
3859
|
-
*/
|
|
3860
|
-
constructor(request, response, logger, containerResolver) {
|
|
3861
|
-
super();
|
|
3862
|
-
this.request = request;
|
|
3863
|
-
this.response = response;
|
|
3864
|
-
this.logger = logger;
|
|
3865
|
-
this.containerResolver = containerResolver;
|
|
3866
|
-
this.request.ctx = this;
|
|
3867
|
-
this.response.ctx = this;
|
|
3868
|
-
}
|
|
3869
|
-
/**
|
|
3870
|
-
* Indicates whether async local storage is enabled for HTTP requests.
|
|
3871
|
-
*
|
|
3872
|
-
* When enabled, the HTTP context is automatically available within the
|
|
3873
|
-
* scope of request processing through static methods like get() and getOrFail().
|
|
3874
|
-
*/
|
|
3875
|
-
static get usingAsyncLocalStorage() {
|
|
3876
|
-
return asyncLocalStorage.isEnabled;
|
|
3877
|
-
}
|
|
3878
|
-
/**
|
|
3879
|
-
* Get access to the current HTTP context from async local storage.
|
|
3880
|
-
*
|
|
3881
|
-
* This method is only available when async local storage is enabled.
|
|
3882
|
-
* Returns null if called outside of an HTTP request context.
|
|
3883
|
-
*
|
|
3884
|
-
* @example
|
|
3885
|
-
* ```ts
|
|
3886
|
-
* const ctx = HttpContext.get()
|
|
3887
|
-
* if (ctx) {
|
|
3888
|
-
* console.log(ctx.request.url())
|
|
3889
|
-
* }
|
|
3890
|
-
* ```
|
|
3891
|
-
*/
|
|
3892
|
-
static get() {
|
|
3893
|
-
if (!this.usingAsyncLocalStorage || !asyncLocalStorage.storage) {
|
|
3894
|
-
return null;
|
|
3895
|
-
}
|
|
3896
|
-
return asyncLocalStorage.storage.getStore() || null;
|
|
3897
|
-
}
|
|
3898
|
-
/**
|
|
3899
|
-
* Get the HttpContext instance or raise an exception if not available.
|
|
3900
|
-
*
|
|
3901
|
-
* This method is useful when you need guaranteed access to the HTTP context
|
|
3902
|
-
* and want to fail fast if it's not available.
|
|
3903
|
-
*
|
|
3904
|
-
* @throws RuntimeException when async local storage is disabled or context is unavailable
|
|
3905
|
-
*
|
|
3906
|
-
* @example
|
|
3907
|
-
* ```ts
|
|
3908
|
-
* const ctx = HttpContext.getOrFail()
|
|
3909
|
-
* const userId = ctx.request.input('user_id')
|
|
3910
|
-
* ```
|
|
3911
|
-
*/
|
|
3912
|
-
static getOrFail() {
|
|
3913
|
-
if (!this.usingAsyncLocalStorage || !asyncLocalStorage.storage) {
|
|
3914
|
-
throw new RuntimeException4(
|
|
3915
|
-
'HTTP context is not available. Enable "useAsyncLocalStorage" inside "config/app.ts" file'
|
|
3916
|
-
);
|
|
3917
|
-
}
|
|
3918
|
-
const store = this.get();
|
|
3919
|
-
if (!store) {
|
|
3920
|
-
throw new RuntimeException4("Http context is not available outside of an HTTP request");
|
|
3921
|
-
}
|
|
3922
|
-
return store;
|
|
3923
|
-
}
|
|
3924
|
-
/**
|
|
3925
|
-
* Run a method outside of the HTTP context scope.
|
|
3926
|
-
*
|
|
3927
|
-
* This method allows you to execute code that should not have access to
|
|
3928
|
-
* the current HTTP context from async local storage. Useful for background
|
|
3929
|
-
* tasks or operations that should be context-independent.
|
|
3930
|
-
*
|
|
3931
|
-
* @param callback - Function to execute outside the context
|
|
3932
|
-
* @param args - Arguments to pass to the callback
|
|
3933
|
-
*
|
|
3934
|
-
* @example
|
|
3935
|
-
* ```ts
|
|
3936
|
-
* HttpContext.runOutsideContext(() => {
|
|
3937
|
-
* // This code cannot access HttpContext.get()
|
|
3938
|
-
* performBackgroundTask()
|
|
3939
|
-
* })
|
|
3940
|
-
* ```
|
|
3941
|
-
*/
|
|
3942
|
-
static runOutsideContext(callback, ...args) {
|
|
3943
|
-
if (!asyncLocalStorage.storage) {
|
|
3944
|
-
return callback(...args);
|
|
3945
|
-
}
|
|
3946
|
-
return asyncLocalStorage.storage.exit(callback, ...args);
|
|
3947
|
-
}
|
|
3948
|
-
/**
|
|
3949
|
-
* Reference to the current route. Not available inside
|
|
3950
|
-
* server middleware
|
|
3951
|
-
*/
|
|
3952
|
-
route;
|
|
3953
|
-
/**
|
|
3954
|
-
* A unique key for the current route
|
|
3955
|
-
*/
|
|
3956
|
-
routeKey;
|
|
3957
|
-
/**
|
|
3958
|
-
* Route params
|
|
3959
|
-
*/
|
|
3960
|
-
params = {};
|
|
3961
|
-
/**
|
|
3962
|
-
* Route subdomains
|
|
3963
|
-
*/
|
|
3964
|
-
subdomains = {};
|
|
3965
|
-
/**
|
|
3966
|
-
* A helper to see top level properties on the context object
|
|
3967
|
-
*/
|
|
3968
|
-
/* c8 ignore next 3 */
|
|
3969
|
-
inspect() {
|
|
3970
|
-
return inspect(this, false, 1, true);
|
|
3971
|
-
}
|
|
3972
|
-
};
|
|
3973
|
-
|
|
3974
|
-
// src/server/main.ts
|
|
3975
|
-
import onFinished2 from "on-finished";
|
|
3976
|
-
import Middleware from "@poppinss/middleware";
|
|
3977
|
-
import { moduleCaller, moduleImporter as moduleImporter3 } from "@adonisjs/fold";
|
|
3978
|
-
|
|
3979
|
-
// src/server/factories/route_finder.ts
|
|
3980
|
-
function routeFinder(router, resolver, ctx, errorResponder) {
|
|
3981
|
-
return function() {
|
|
3982
|
-
const url = ctx.request.url();
|
|
3983
|
-
const method = ctx.request.method();
|
|
3984
|
-
const hostname = router.usingDomains ? ctx.request.hostname() : void 0;
|
|
3985
|
-
const route = router.match(url, method, ctx.request.parsedUrl.shouldDecodeParam, hostname);
|
|
3986
|
-
if (route) {
|
|
3987
|
-
ctx.params = route.params;
|
|
3988
|
-
ctx.subdomains = route.subdomains;
|
|
3989
|
-
ctx.route = route.route;
|
|
3990
|
-
ctx.routeKey = route.routeKey;
|
|
3991
|
-
return route.route.execute(route.route, resolver, ctx, errorResponder);
|
|
3992
|
-
}
|
|
3993
|
-
return Promise.reject(new E_ROUTE_NOT_FOUND([method, url]));
|
|
3994
|
-
};
|
|
3995
|
-
}
|
|
3996
|
-
|
|
3997
|
-
// src/server/factories/write_response.ts
|
|
3998
|
-
function writeResponse(ctx) {
|
|
3999
|
-
return function() {
|
|
4000
|
-
try {
|
|
4001
|
-
ctx.response.finish();
|
|
4002
|
-
} catch (error) {
|
|
4003
|
-
ctx.logger.fatal({ err: error }, "Response serialization failed");
|
|
4004
|
-
ctx.response.internalServerError(error.message);
|
|
4005
|
-
ctx.response.finish();
|
|
4006
|
-
}
|
|
4007
|
-
};
|
|
4008
|
-
}
|
|
4009
|
-
|
|
4010
|
-
// src/server/factories/middleware_handler.ts
|
|
4011
|
-
function middlewareHandler(resolver, ctx) {
|
|
4012
|
-
return function(fn, next) {
|
|
4013
|
-
debug_default("executing middleware %s", fn.name);
|
|
4014
|
-
return httpMiddleware.tracePromise(
|
|
4015
|
-
fn.handle,
|
|
4016
|
-
httpMiddleware.hasSubscribers ? { middleware: fn } : void 0,
|
|
4017
|
-
void 0,
|
|
4018
|
-
resolver,
|
|
4019
|
-
ctx,
|
|
4020
|
-
next
|
|
4021
|
-
);
|
|
4022
|
-
};
|
|
4023
|
-
}
|
|
4024
|
-
|
|
4025
|
-
// src/server/main.ts
|
|
4026
|
-
var Server = class {
|
|
4027
|
-
/**
|
|
4028
|
-
* Flag indicating whether the server has been booted and initialized
|
|
4029
|
-
*/
|
|
4030
|
-
#booted = false;
|
|
4031
|
-
/**
|
|
4032
|
-
* Built-in fallback error handler used when no custom handler is registered
|
|
4033
|
-
*/
|
|
4034
|
-
#defaultErrorHandler = {
|
|
4035
|
-
report() {
|
|
4036
|
-
},
|
|
4037
|
-
handle(error, ctx) {
|
|
4038
|
-
ctx.response.status(error.status || 500).send(error.message || "Internal server error");
|
|
4039
|
-
}
|
|
4040
|
-
};
|
|
4041
|
-
/**
|
|
4042
|
-
* Logger instance for server-level logging (child loggers are created per request)
|
|
4043
|
-
*/
|
|
4044
|
-
#logger;
|
|
4045
|
-
/**
|
|
4046
|
-
* Lazy import reference to the custom error handler class
|
|
4047
|
-
*/
|
|
4048
|
-
#errorHandler;
|
|
4049
|
-
/**
|
|
4050
|
-
* Active error handler instance (either custom or default)
|
|
4051
|
-
*/
|
|
4052
|
-
#resolvedErrorHandler = this.#defaultErrorHandler;
|
|
4053
|
-
/**
|
|
4054
|
-
* Event emitter for HTTP server lifecycle events
|
|
4055
|
-
*/
|
|
4056
|
-
#emitter;
|
|
4057
|
-
/**
|
|
4058
|
-
* AdonisJS application instance providing IoC container and configuration
|
|
4059
|
-
*/
|
|
4060
|
-
#app;
|
|
4061
|
-
/**
|
|
4062
|
-
* Encryption service for secure cookie handling and data encryption
|
|
4063
|
-
*/
|
|
4064
|
-
#encryption;
|
|
4065
|
-
/**
|
|
4066
|
-
* Server configuration settings including timeouts, middleware options, etc.
|
|
4067
|
-
*/
|
|
4068
|
-
#config;
|
|
4069
|
-
/**
|
|
4070
|
-
* Query string parser instance for URL parameter processing
|
|
4071
|
-
*/
|
|
4072
|
-
#qsParser;
|
|
4073
|
-
/**
|
|
4074
|
-
* Compiled middleware stack that executes on every incoming HTTP request
|
|
4075
|
-
*/
|
|
4076
|
-
#serverMiddlewareStack;
|
|
4077
|
-
/**
|
|
4078
|
-
* Router instance responsible for route registration and matching
|
|
4079
|
-
*/
|
|
4080
|
-
#router;
|
|
4081
|
-
/**
|
|
4082
|
-
* Reference to the underlying Node.js HTTP or HTTPS server instance
|
|
4083
|
-
*/
|
|
4084
|
-
#nodeHttpServer;
|
|
4085
|
-
/**
|
|
4086
|
-
* Collection of registered global middleware before compilation
|
|
4087
|
-
*/
|
|
4088
|
-
#middleware = [];
|
|
4089
|
-
/**
|
|
4090
|
-
* Error responder function that handles exceptions in middleware and routes.
|
|
4091
|
-
* Reports errors and delegates handling to the configured error handler.
|
|
4092
|
-
*/
|
|
4093
|
-
#requestErrorResponder = (error, ctx) => {
|
|
4094
|
-
this.#resolvedErrorHandler.report(error, ctx);
|
|
4095
|
-
return httpExceptionHandler.tracePromise(
|
|
4096
|
-
this.#resolvedErrorHandler.handle,
|
|
4097
|
-
void 0,
|
|
4098
|
-
this.#resolvedErrorHandler,
|
|
4099
|
-
error,
|
|
4100
|
-
ctx
|
|
4101
|
-
);
|
|
4102
|
-
};
|
|
4103
|
-
/**
|
|
4104
|
-
* Indicates whether the server has completed its boot process
|
|
4105
|
-
*/
|
|
4106
|
-
get booted() {
|
|
4107
|
-
return this.#booted;
|
|
4108
|
-
}
|
|
4109
|
-
/**
|
|
4110
|
-
* Indicates whether async local storage is enabled for request context
|
|
4111
|
-
*/
|
|
4112
|
-
get usingAsyncLocalStorage() {
|
|
4113
|
-
return asyncLocalStorage.isEnabled;
|
|
4114
|
-
}
|
|
4115
|
-
/**
|
|
4116
|
-
* Creates a new Server instance
|
|
4117
|
-
*
|
|
4118
|
-
* @param app - AdonisJS application instance
|
|
4119
|
-
* @param encryption - Encryption service for secure operations
|
|
4120
|
-
* @param emitter - Event emitter for server lifecycle events
|
|
4121
|
-
* @param logger - Logger instance for server operations
|
|
4122
|
-
* @param config - Server configuration settings
|
|
4123
|
-
*/
|
|
4124
|
-
constructor(app, encryption, emitter, logger, config) {
|
|
4125
|
-
this.#app = app;
|
|
4126
|
-
this.#emitter = emitter;
|
|
4127
|
-
this.#config = config;
|
|
4128
|
-
this.#logger = logger;
|
|
4129
|
-
this.#encryption = encryption;
|
|
4130
|
-
this.#qsParser = new Qs(this.#config.qs);
|
|
4131
|
-
this.#router = new Router(this.#app, this.#encryption, this.#qsParser);
|
|
4132
|
-
this.#createAsyncLocalStore();
|
|
4133
|
-
debug_default("server config: %O", this.#config);
|
|
4134
|
-
}
|
|
4135
|
-
/**
|
|
4136
|
-
* Initializes or destroys async local storage based on configuration
|
|
4137
|
-
*/
|
|
4138
|
-
#createAsyncLocalStore() {
|
|
4139
|
-
if (this.#config.useAsyncLocalStorage) {
|
|
4140
|
-
debug_default("creating ALS store for HTTP context");
|
|
4141
|
-
asyncLocalStorage.create();
|
|
4142
|
-
} else {
|
|
4143
|
-
asyncLocalStorage.destroy();
|
|
4144
|
-
}
|
|
4145
|
-
}
|
|
4146
|
-
/**
|
|
4147
|
-
* Compiles registered middleware into a frozen middleware stack for execution
|
|
4148
|
-
*/
|
|
4149
|
-
#createServerMiddlewareStack() {
|
|
4150
|
-
this.#serverMiddlewareStack = new Middleware();
|
|
4151
|
-
this.#middleware.forEach((middleware) => this.#serverMiddlewareStack.add(middleware));
|
|
4152
|
-
this.#serverMiddlewareStack.freeze();
|
|
4153
|
-
this.#middleware = [];
|
|
4154
|
-
}
|
|
4155
|
-
/**
|
|
4156
|
-
* Processes an HTTP request through the middleware pipeline and routing
|
|
4157
|
-
*
|
|
4158
|
-
* @param ctx - HTTP context containing request/response objects
|
|
4159
|
-
* @param resolver - Container resolver for dependency injection
|
|
4160
|
-
* @returns Promise that resolves when request processing is complete
|
|
4161
|
-
*/
|
|
4162
|
-
#handleRequest(ctx, resolver) {
|
|
4163
|
-
return this.#serverMiddlewareStack.runner().errorHandler((error) => this.#requestErrorResponder(error, ctx)).finalHandler(routeFinder(this.#router, resolver, ctx, this.#requestErrorResponder)).run(middlewareHandler(resolver, ctx)).catch((error) => {
|
|
4164
|
-
ctx.logger.fatal({ err: error }, "Exception raised by error handler");
|
|
4165
|
-
return this.#defaultErrorHandler.handle(error, ctx);
|
|
4166
|
-
}).finally(writeResponse(ctx));
|
|
4167
|
-
}
|
|
4168
|
-
/**
|
|
4169
|
-
* Creates a testing middleware pipeline for unit/integration testing
|
|
4170
|
-
*
|
|
4171
|
-
* @param middleware - Array of middleware classes to include in pipeline
|
|
4172
|
-
* @returns TestingMiddlewarePipeline instance for test execution
|
|
4173
|
-
*/
|
|
4174
|
-
pipeline(middleware) {
|
|
4175
|
-
const middlewareStack = new Middleware();
|
|
4176
|
-
middleware.forEach((one) => {
|
|
4177
|
-
middlewareStack.add({
|
|
4178
|
-
reference: one,
|
|
4179
|
-
...moduleCaller(one, "handle").toHandleMethod()
|
|
4180
|
-
});
|
|
4181
|
-
});
|
|
4182
|
-
middlewareStack.freeze();
|
|
4183
|
-
const stackRunner = middlewareStack.runner();
|
|
4184
|
-
return {
|
|
4185
|
-
finalHandler(handler) {
|
|
4186
|
-
stackRunner.finalHandler(handler);
|
|
4187
|
-
return this;
|
|
4188
|
-
},
|
|
4189
|
-
errorHandler(handler) {
|
|
4190
|
-
stackRunner.errorHandler(handler);
|
|
4191
|
-
return this;
|
|
4192
|
-
},
|
|
4193
|
-
run(ctx) {
|
|
4194
|
-
return stackRunner.run((handler, next) => {
|
|
4195
|
-
return handler.handle(ctx.containerResolver, ctx, next);
|
|
4196
|
-
});
|
|
4197
|
-
}
|
|
4198
|
-
};
|
|
4199
|
-
}
|
|
4200
|
-
/**
|
|
4201
|
-
* Registers global middleware to run on all incoming HTTP requests
|
|
4202
|
-
*
|
|
4203
|
-
* @param middleware - Array of lazy-imported middleware classes
|
|
4204
|
-
* @returns The Server instance for method chaining
|
|
4205
|
-
*/
|
|
4206
|
-
use(middleware) {
|
|
4207
|
-
middleware.forEach(
|
|
4208
|
-
(one) => this.#middleware.push({
|
|
4209
|
-
reference: one,
|
|
4210
|
-
...moduleImporter3(one, "handle").toHandleMethod()
|
|
4211
|
-
})
|
|
4212
|
-
);
|
|
4213
|
-
return this;
|
|
4214
|
-
}
|
|
4215
|
-
/**
|
|
4216
|
-
* Registers a custom error handler for HTTP request processing
|
|
4217
|
-
*
|
|
4218
|
-
* @param handler - Lazy import of the error handler class
|
|
4219
|
-
* @returns The Server instance for method chaining
|
|
4220
|
-
*/
|
|
4221
|
-
errorHandler(handler) {
|
|
4222
|
-
this.#errorHandler = handler;
|
|
4223
|
-
return this;
|
|
4224
|
-
}
|
|
4225
|
-
/**
|
|
4226
|
-
* Initializes the server by compiling middleware, committing routes, and resolving handlers
|
|
4227
|
-
*
|
|
4228
|
-
* Performs the following operations:
|
|
4229
|
-
* - Compiles the middleware stack
|
|
4230
|
-
* - Commits registered routes to the router
|
|
4231
|
-
* - Resolves and instantiates the custom error handler
|
|
4232
|
-
*/
|
|
4233
|
-
async boot() {
|
|
4234
|
-
if (this.#booted) {
|
|
4235
|
-
return;
|
|
4236
|
-
}
|
|
4237
|
-
debug_default("booting HTTP server");
|
|
4238
|
-
this.#createServerMiddlewareStack();
|
|
4239
|
-
this.#router.commit();
|
|
4240
|
-
if (this.#errorHandler) {
|
|
4241
|
-
if (debug_default.enabled) {
|
|
4242
|
-
debug_default('using custom error handler "%s"', this.#errorHandler);
|
|
4243
|
-
}
|
|
4244
|
-
const moduleExports = await this.#errorHandler();
|
|
4245
|
-
this.#resolvedErrorHandler = await this.#app.container.make(moduleExports.default);
|
|
4246
|
-
}
|
|
4247
|
-
this.#booted = true;
|
|
4248
|
-
}
|
|
4249
|
-
/**
|
|
4250
|
-
* Configures the underlying Node.js HTTP/HTTPS server with timeout settings
|
|
4251
|
-
*
|
|
4252
|
-
* @param server - Node.js HTTP or HTTPS server instance
|
|
4253
|
-
*/
|
|
4254
|
-
setNodeServer(server) {
|
|
4255
|
-
server.timeout = this.#config.timeout ?? server.timeout;
|
|
4256
|
-
server.keepAliveTimeout = this.#config.keepAliveTimeout ?? server.keepAliveTimeout;
|
|
4257
|
-
server.headersTimeout = this.#config.headersTimeout ?? server.headersTimeout;
|
|
4258
|
-
server.requestTimeout = this.#config.requestTimeout ?? server.requestTimeout;
|
|
4259
|
-
this.#nodeHttpServer = server;
|
|
4260
|
-
}
|
|
4261
|
-
/**
|
|
4262
|
-
* Gets the underlying Node.js HTTP/HTTPS server instance
|
|
4263
|
-
*
|
|
4264
|
-
* @returns The configured server instance or undefined if not set
|
|
4265
|
-
*/
|
|
4266
|
-
getNodeServer() {
|
|
4267
|
-
return this.#nodeHttpServer;
|
|
4268
|
-
}
|
|
4269
|
-
/**
|
|
4270
|
-
* Gets the router instance used for route registration and matching
|
|
4271
|
-
*
|
|
4272
|
-
* @returns The Router instance
|
|
4273
|
-
*/
|
|
4274
|
-
getRouter() {
|
|
4275
|
-
return this.#router;
|
|
4276
|
-
}
|
|
4277
|
-
/**
|
|
4278
|
-
* Creates a Request instance from Node.js request/response objects
|
|
4279
|
-
*
|
|
4280
|
-
* @param req - Node.js IncomingMessage
|
|
4281
|
-
* @param res - Node.js ServerResponse
|
|
4282
|
-
* @returns New Request instance
|
|
4283
|
-
*/
|
|
4284
|
-
createRequest(req, res) {
|
|
4285
|
-
return new Request(req, res, this.#encryption, this.#config, this.#qsParser);
|
|
4286
|
-
}
|
|
4287
|
-
/**
|
|
4288
|
-
* Creates a Response instance from Node.js request/response objects
|
|
4289
|
-
*
|
|
4290
|
-
* @param req - Node.js IncomingMessage
|
|
4291
|
-
* @param res - Node.js ServerResponse
|
|
4292
|
-
* @returns New Response instance
|
|
4293
|
-
*/
|
|
4294
|
-
createResponse(req, res) {
|
|
4295
|
-
return new Response(req, res, this.#encryption, this.#config, this.#router, this.#qsParser);
|
|
4296
|
-
}
|
|
4297
|
-
/**
|
|
4298
|
-
* Creates an HttpContext instance with request-specific logger
|
|
4299
|
-
*
|
|
4300
|
-
* @param request - Request instance
|
|
4301
|
-
* @param response - Response instance
|
|
4302
|
-
* @param resolver - Container resolver for dependency injection
|
|
4303
|
-
* @returns New HttpContext instance
|
|
4304
|
-
*/
|
|
4305
|
-
createHttpContext(request, response, resolver) {
|
|
4306
|
-
return new HttpContext(
|
|
4307
|
-
request,
|
|
4308
|
-
response,
|
|
4309
|
-
this.#logger.child({ request_id: request.id() }),
|
|
4310
|
-
resolver
|
|
4311
|
-
);
|
|
4312
|
-
}
|
|
4313
|
-
/**
|
|
4314
|
-
* Gets the list of registered global middleware
|
|
4315
|
-
*
|
|
4316
|
-
* @returns Array of parsed global middleware
|
|
4317
|
-
*/
|
|
4318
|
-
getMiddlewareList() {
|
|
4319
|
-
return this.#serverMiddlewareStack ? Array.from(this.#serverMiddlewareStack.all()) : [...this.#middleware];
|
|
4320
|
-
}
|
|
4321
|
-
/**
|
|
4322
|
-
* Handles an incoming HTTP request by creating context and processing through pipeline
|
|
4323
|
-
*
|
|
4324
|
-
* @param req - Node.js IncomingMessage
|
|
4325
|
-
* @param res - Node.js ServerResponse
|
|
4326
|
-
* @returns Promise that resolves when request processing is complete
|
|
4327
|
-
*/
|
|
4328
|
-
handle(req, res) {
|
|
4329
|
-
const hasRequestListener = this.#emitter.hasListeners("http:request_completed");
|
|
4330
|
-
const startTime = hasRequestListener ? process.hrtime() : null;
|
|
4331
|
-
const resolver = this.#app.container.createResolver();
|
|
4332
|
-
const ctx = this.createHttpContext(
|
|
4333
|
-
this.createRequest(req, res),
|
|
4334
|
-
this.createResponse(req, res),
|
|
4335
|
-
resolver
|
|
4336
|
-
);
|
|
4337
|
-
if (startTime) {
|
|
4338
|
-
onFinished2(res, () => {
|
|
4339
|
-
this.#emitter.emit("http:request_completed", {
|
|
4340
|
-
ctx,
|
|
4341
|
-
duration: process.hrtime(startTime)
|
|
4342
|
-
});
|
|
4343
|
-
});
|
|
4344
|
-
}
|
|
4345
|
-
if (this.usingAsyncLocalStorage) {
|
|
4346
|
-
return asyncLocalStorage.storage.run(
|
|
4347
|
-
ctx,
|
|
4348
|
-
() => httpRequest.tracePromise(
|
|
4349
|
-
this.#handleRequest,
|
|
4350
|
-
httpRequest.hasSubscribers ? { ctx } : void 0,
|
|
4351
|
-
this,
|
|
4352
|
-
ctx,
|
|
4353
|
-
resolver
|
|
4354
|
-
)
|
|
4355
|
-
);
|
|
4356
|
-
}
|
|
4357
|
-
return httpRequest.tracePromise(
|
|
4358
|
-
this.#handleRequest,
|
|
4359
|
-
httpRequest.hasSubscribers ? { ctx } : void 0,
|
|
4360
|
-
this,
|
|
4361
|
-
ctx,
|
|
4362
|
-
resolver
|
|
4363
|
-
);
|
|
4364
|
-
}
|
|
4365
|
-
};
|
|
4366
|
-
|
|
4367
|
-
// src/define_config.ts
|
|
4368
|
-
import proxyAddr from "proxy-addr";
|
|
4369
|
-
import string from "@poppinss/utils/string";
|
|
4370
|
-
import lodash2 from "@poppinss/utils/lodash";
|
|
4371
|
-
function defineConfig(config) {
|
|
4372
|
-
const { trustProxy: trustProxy2, ...rest } = config;
|
|
4373
|
-
const defaults = {
|
|
4374
|
-
allowMethodSpoofing: false,
|
|
4375
|
-
trustProxy: proxyAddr.compile("loopback"),
|
|
4376
|
-
subdomainOffset: 2,
|
|
4377
|
-
generateRequestId: !!config.createRequestId,
|
|
4378
|
-
createRequestId() {
|
|
4379
|
-
return crypto.randomUUID();
|
|
4380
|
-
},
|
|
4381
|
-
useAsyncLocalStorage: false,
|
|
4382
|
-
etag: false,
|
|
4383
|
-
jsonpCallbackName: "callback",
|
|
4384
|
-
cookie: {
|
|
4385
|
-
maxAge: "2h",
|
|
4386
|
-
path: "/",
|
|
4387
|
-
httpOnly: true,
|
|
4388
|
-
secure: true,
|
|
4389
|
-
sameSite: "lax"
|
|
4390
|
-
},
|
|
4391
|
-
qs: {
|
|
4392
|
-
parse: {
|
|
4393
|
-
depth: 5,
|
|
4394
|
-
parameterLimit: 1e3,
|
|
4395
|
-
allowSparse: false,
|
|
4396
|
-
arrayLimit: 20,
|
|
4397
|
-
comma: true
|
|
4398
|
-
},
|
|
4399
|
-
stringify: {
|
|
4400
|
-
encode: true,
|
|
4401
|
-
encodeValuesOnly: false,
|
|
4402
|
-
arrayFormat: "indices",
|
|
4403
|
-
skipNulls: false
|
|
4404
|
-
}
|
|
4405
|
-
}
|
|
4406
|
-
};
|
|
4407
|
-
const normalizedConfig = lodash2.merge({}, defaults, rest);
|
|
4408
|
-
if (normalizedConfig.cookie.maxAge) {
|
|
4409
|
-
normalizedConfig.cookie.maxAge = string.seconds.parse(normalizedConfig.cookie.maxAge);
|
|
4410
|
-
}
|
|
4411
|
-
if (typeof trustProxy2 === "boolean") {
|
|
4412
|
-
const tpValue = trustProxy2;
|
|
4413
|
-
normalizedConfig.trustProxy = (_, __) => tpValue;
|
|
4414
|
-
} else if (typeof trustProxy2 === "string") {
|
|
4415
|
-
const tpValue = trustProxy2;
|
|
4416
|
-
normalizedConfig.trustProxy = proxyAddr.compile(tpValue);
|
|
4417
|
-
} else if (trustProxy2) {
|
|
4418
|
-
normalizedConfig.trustProxy = trustProxy2;
|
|
4419
|
-
}
|
|
4420
|
-
return normalizedConfig;
|
|
4421
|
-
}
|
|
4422
|
-
|
|
4423
|
-
export {
|
|
4424
|
-
Qs,
|
|
4425
|
-
E_ROUTE_NOT_FOUND,
|
|
4426
|
-
E_CANNOT_LOOKUP_ROUTE,
|
|
4427
|
-
E_HTTP_EXCEPTION,
|
|
4428
|
-
E_HTTP_REQUEST_ABORTED,
|
|
4429
|
-
errors_exports,
|
|
4430
|
-
CookieClient,
|
|
4431
|
-
CookieParser,
|
|
4432
|
-
Request,
|
|
4433
|
-
Redirect,
|
|
4434
|
-
ResponseStatus,
|
|
4435
|
-
CookieSerializer,
|
|
4436
|
-
Response,
|
|
4437
|
-
Router,
|
|
4438
|
-
HttpContext,
|
|
4439
|
-
Server,
|
|
4440
|
-
defineConfig
|
|
4441
|
-
};
|