@fluojs/runtime 1.0.0-beta.6 → 1.0.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node/internal-node-request.d.ts +107 -0
- package/dist/node/internal-node-request.d.ts.map +1 -1
- package/dist/node/internal-node-request.js +225 -24
- package/dist/node/internal-node.d.ts +2 -1
- package/dist/node/internal-node.d.ts.map +1 -1
- package/dist/node/internal-node.js +2 -2
- package/dist/web.d.ts +2 -0
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +91 -34
- package/package.json +2 -2
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import type { IncomingHttpHeaders, IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import { PayloadTooLargeException, type FrameworkRequest } from '@fluojs/http';
|
|
3
3
|
import { type MultipartOptions } from '../multipart.js';
|
|
4
|
+
type MemoizedValue<T> = () => T;
|
|
5
|
+
type QueryRecord = Record<string, string | string[] | undefined>;
|
|
6
|
+
/**
|
|
7
|
+
* Options for creating a deferred framework request shell from a Node-backed adapter.
|
|
8
|
+
*/
|
|
9
|
+
export interface DeferredFrameworkRequestShellOptions<RawRequest> {
|
|
10
|
+
cookieHeader?: string | string[] | undefined;
|
|
11
|
+
headers?: FrameworkRequest['headers'];
|
|
12
|
+
headersFactory?: () => FrameworkRequest['headers'];
|
|
13
|
+
materializeBody?: () => Promise<void>;
|
|
14
|
+
method?: string;
|
|
15
|
+
path: string;
|
|
16
|
+
query?: QueryRecord;
|
|
17
|
+
queryFactory?: () => QueryRecord;
|
|
18
|
+
raw: RawRequest;
|
|
19
|
+
requestId?: string;
|
|
20
|
+
signal: AbortSignal | (() => AbortSignal);
|
|
21
|
+
url: string;
|
|
22
|
+
}
|
|
4
23
|
/**
|
|
5
24
|
* HTTP payload-size error that closes the underlying Node request stream after the response commits.
|
|
6
25
|
*/
|
|
@@ -31,6 +50,13 @@ export declare function createFrameworkRequest(request: IncomingMessage, signal:
|
|
|
31
50
|
* @returns The framework request shell with metadata snapshotted and body materialization deferred.
|
|
32
51
|
*/
|
|
33
52
|
export declare function createDeferredFrameworkRequest(request: IncomingMessage, signal: AbortSignal, multipartOptions?: MultipartOptions, maxBodySize?: number, preserveRawBody?: boolean): FrameworkRequest;
|
|
53
|
+
/**
|
|
54
|
+
* Creates a framework request shell from already-snapshotted Node adapter metadata.
|
|
55
|
+
*
|
|
56
|
+
* @param options - Raw request, metadata factories, and deferred body materialization hooks.
|
|
57
|
+
* @returns A framework request with lazy headers, cookies, query values, and optional body materialization.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createDeferredFrameworkRequestShell<RawRequest>({ cookieHeader, headers, headersFactory, materializeBody, method, path, query, queryFactory, raw, requestId, signal, url, }: DeferredFrameworkRequestShellOptions<RawRequest>): FrameworkRequest;
|
|
34
60
|
/**
|
|
35
61
|
* Materializes a deferred Node framework request body exactly once.
|
|
36
62
|
*
|
|
@@ -38,6 +64,20 @@ export declare function createDeferredFrameworkRequest(request: IncomingMessage,
|
|
|
38
64
|
* @returns A promise that settles after body, rawBody, and files fields are populated when applicable.
|
|
39
65
|
*/
|
|
40
66
|
export declare function materializeFrameworkRequestBody(request: FrameworkRequest): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Creates a synchronous memoized value resolver.
|
|
69
|
+
*
|
|
70
|
+
* @param factory - Function that computes the value on first access.
|
|
71
|
+
* @returns A stable resolver that returns the cached value after the first call.
|
|
72
|
+
*/
|
|
73
|
+
export declare function createMemoizedValue<T>(factory: () => T): MemoizedValue<T>;
|
|
74
|
+
/**
|
|
75
|
+
* Creates an async memoized side-effect resolver.
|
|
76
|
+
*
|
|
77
|
+
* @param factory - Async function to run at most once.
|
|
78
|
+
* @returns A resolver that returns the same in-flight or completed promise for every call.
|
|
79
|
+
*/
|
|
80
|
+
export declare function createMemoizedAsyncValue(factory: () => Promise<void>): () => Promise<void>;
|
|
41
81
|
/**
|
|
42
82
|
* Creates an abort signal that fires when the Node response closes unexpectedly.
|
|
43
83
|
*
|
|
@@ -52,4 +92,71 @@ export declare function createRequestSignal(response: ServerResponse): AbortSign
|
|
|
52
92
|
* @returns The request identifier when present.
|
|
53
93
|
*/
|
|
54
94
|
export declare function resolveRequestIdFromHeaders(headers: IncomingHttpHeaders): string | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* Parses a raw URL search string into the framework query shape.
|
|
97
|
+
*
|
|
98
|
+
* @param search - Raw search string, with or without a leading question mark.
|
|
99
|
+
* @returns Query values where repeated keys become string arrays.
|
|
100
|
+
*/
|
|
101
|
+
export declare function parseQueryParamsFromSearch(search: string): Record<string, string | string[]>;
|
|
102
|
+
/**
|
|
103
|
+
* Snapshots host-parsed query values when they already match framework semantics.
|
|
104
|
+
*
|
|
105
|
+
* @param query - Host query object exposed by a Node-backed adapter.
|
|
106
|
+
* @returns A cloned query record when all values are strings or string arrays; otherwise `undefined` for raw URL fallback.
|
|
107
|
+
*/
|
|
108
|
+
export declare function snapshotSimpleQueryRecord(query: unknown): QueryRecord | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* Clones Node request headers into the framework header record shape.
|
|
111
|
+
*
|
|
112
|
+
* @param headers - Raw Node incoming headers.
|
|
113
|
+
* @returns A shallow header snapshot with array values cloned.
|
|
114
|
+
*/
|
|
115
|
+
export declare function cloneRequestHeaders(headers: IncomingHttpHeaders): FrameworkRequest['headers'];
|
|
116
|
+
/**
|
|
117
|
+
* Clones a single Node header value when it is array-backed.
|
|
118
|
+
*
|
|
119
|
+
* @param value - Header value to snapshot.
|
|
120
|
+
* @returns The original scalar value or a cloned array value.
|
|
121
|
+
*/
|
|
122
|
+
export declare function cloneHeaderValue<T extends string | string[] | undefined>(value: T): T;
|
|
123
|
+
/**
|
|
124
|
+
* Reads the primary value from a Node header value.
|
|
125
|
+
*
|
|
126
|
+
* @param headerValue - Header value that may contain multiple entries.
|
|
127
|
+
* @returns The first header value when present.
|
|
128
|
+
*/
|
|
129
|
+
export declare function readPrimaryHeaderValue(headerValue: string | string[] | undefined): string | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* Normalizes a Node content-type header to its primary media type.
|
|
132
|
+
*
|
|
133
|
+
* @param headerValue - Raw content-type header value.
|
|
134
|
+
* @returns Lowercase primary media type without parameters, or `undefined` when absent.
|
|
135
|
+
*/
|
|
136
|
+
export declare function normalizePrimaryContentType(headerValue: string | string[] | undefined): string | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* Parses a Node cookie header into framework cookie values.
|
|
139
|
+
*
|
|
140
|
+
* @param cookieHeader - Raw cookie header value or values.
|
|
141
|
+
* @returns Cookie name/value pairs with percent-decoded values when possible.
|
|
142
|
+
*/
|
|
143
|
+
export declare function parseCookieHeader(cookieHeader: string | string[] | undefined): Record<string, string>;
|
|
144
|
+
/**
|
|
145
|
+
* Splits a raw Node request URL into path and search components.
|
|
146
|
+
*
|
|
147
|
+
* @param rawUrl - Raw request URL, absolute URL, or undefined value from Node.
|
|
148
|
+
* @returns The pathname and search string used by framework request matching and query parsing.
|
|
149
|
+
*/
|
|
150
|
+
export declare function splitRawRequestUrl(rawUrl: string | undefined): {
|
|
151
|
+
path: string;
|
|
152
|
+
search: string;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Resolves a raw Node request URL into an absolute URL string.
|
|
156
|
+
*
|
|
157
|
+
* @param rawUrl - Raw request URL, absolute URL, or undefined value from Node.
|
|
158
|
+
* @returns An absolute URL suitable for Web-standard parsers.
|
|
159
|
+
*/
|
|
160
|
+
export declare function resolveAbsoluteRequestUrl(rawUrl: string | undefined): string;
|
|
161
|
+
export {};
|
|
55
162
|
//# sourceMappingURL=internal-node-request.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-node-request.d.ts","sourceRoot":"","sources":["../../src/node/internal-node-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EACf,cAAc,EACf,MAAM,WAAW,CAAC;AAInB,OAAO,EAEL,wBAAwB,EACxB,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"internal-node-request.d.ts","sourceRoot":"","sources":["../../src/node/internal-node-request.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,11 @@ import { Readable } from 'node:stream';
|
|
|
2
2
|
import { URL } from 'node:url';
|
|
3
3
|
import { BadRequestException, PayloadTooLargeException } from '@fluojs/http';
|
|
4
4
|
import { parseMultipart } from '../multipart.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for creating a deferred framework request shell from a Node-backed adapter.
|
|
8
|
+
*/
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* HTTP payload-size error that closes the underlying Node request stream after the response commits.
|
|
7
12
|
*/
|
|
@@ -50,21 +55,19 @@ export async function createFrameworkRequest(request, signal, multipartOptions,
|
|
|
50
55
|
* @returns The framework request shell with metadata snapshotted and body materialization deferred.
|
|
51
56
|
*/
|
|
52
57
|
export function createDeferredFrameworkRequest(request, signal, multipartOptions, maxBodySize = 1 * 1024 * 1024, preserveRawBody = false) {
|
|
53
|
-
const
|
|
58
|
+
const rawUrl = request.url ?? '/';
|
|
59
|
+
const urlParts = splitRawRequestUrl(rawUrl);
|
|
54
60
|
const headers = cloneRequestHeaders(request.headers);
|
|
55
|
-
const cookieHeader = cloneHeaderValue(headers.cookie);
|
|
56
|
-
const searchParams = new URLSearchParams(url.searchParams);
|
|
57
|
-
const cookies = createMemoizedValue(() => parseCookieHeader(cookieHeader));
|
|
58
|
-
const query = createMemoizedValue(() => parseQueryParams(searchParams));
|
|
59
61
|
const contentType = normalizePrimaryContentType(headers['content-type']);
|
|
60
62
|
const isMultipart = contentType === 'multipart/form-data';
|
|
63
|
+
let frameworkRequest;
|
|
61
64
|
const materializeBody = createMemoizedAsyncValue(async () => {
|
|
62
65
|
if (isMultipart) {
|
|
63
66
|
const result = await parseMultipart({
|
|
64
67
|
body: Readable.toWeb(request),
|
|
65
68
|
headers,
|
|
66
69
|
method: request.method,
|
|
67
|
-
url:
|
|
70
|
+
url: resolveAbsoluteRequestUrl(rawUrl)
|
|
68
71
|
}, {
|
|
69
72
|
...multipartOptions,
|
|
70
73
|
maxTotalSize: multipartOptions?.maxTotalSize ?? maxBodySize
|
|
@@ -83,22 +86,85 @@ export function createDeferredFrameworkRequest(request, signal, multipartOptions
|
|
|
83
86
|
frameworkRequest.rawBody = bodyResult.rawBody;
|
|
84
87
|
}
|
|
85
88
|
});
|
|
89
|
+
frameworkRequest = createDeferredFrameworkRequestShell({
|
|
90
|
+
cookieHeader: cloneHeaderValue(headers.cookie),
|
|
91
|
+
headers,
|
|
92
|
+
materializeBody,
|
|
93
|
+
method: request.method,
|
|
94
|
+
path: urlParts.path,
|
|
95
|
+
queryFactory: () => parseQueryParamsFromSearch(urlParts.search),
|
|
96
|
+
raw: request,
|
|
97
|
+
requestId: resolvePrimaryRequestIdFromHeaders(headers),
|
|
98
|
+
signal,
|
|
99
|
+
url: urlParts.path + urlParts.search
|
|
100
|
+
});
|
|
101
|
+
return frameworkRequest;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Creates a framework request shell from already-snapshotted Node adapter metadata.
|
|
106
|
+
*
|
|
107
|
+
* @param options - Raw request, metadata factories, and deferred body materialization hooks.
|
|
108
|
+
* @returns A framework request with lazy headers, cookies, query values, and optional body materialization.
|
|
109
|
+
*/
|
|
110
|
+
export function createDeferredFrameworkRequestShell({
|
|
111
|
+
cookieHeader,
|
|
112
|
+
headers,
|
|
113
|
+
headersFactory,
|
|
114
|
+
materializeBody,
|
|
115
|
+
method,
|
|
116
|
+
path,
|
|
117
|
+
query,
|
|
118
|
+
queryFactory,
|
|
119
|
+
raw,
|
|
120
|
+
requestId,
|
|
121
|
+
signal,
|
|
122
|
+
url
|
|
123
|
+
}) {
|
|
124
|
+
const hasQuerySnapshot = query !== undefined;
|
|
125
|
+
const hasLazySignal = typeof signal === 'function';
|
|
126
|
+
const resolveHeaders = headersFactory ? createMemoizedValue(headersFactory) : () => headers ?? {};
|
|
127
|
+
const resolveCookies = createMemoizedValue(() => parseCookieHeader(cookieHeader ?? resolveHeaders().cookie));
|
|
128
|
+
const resolveQuery = hasQuerySnapshot ? undefined : createMemoizedValue(() => queryFactory?.() ?? {});
|
|
86
129
|
const frameworkRequest = {
|
|
87
130
|
get cookies() {
|
|
88
|
-
return
|
|
131
|
+
return resolveCookies();
|
|
89
132
|
},
|
|
90
|
-
headers
|
|
91
|
-
|
|
92
|
-
params: {},
|
|
93
|
-
path: url.pathname,
|
|
94
|
-
get query() {
|
|
95
|
-
return query();
|
|
133
|
+
get headers() {
|
|
134
|
+
return resolveHeaders();
|
|
96
135
|
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
136
|
+
method: method ?? 'GET',
|
|
137
|
+
params: {},
|
|
138
|
+
path,
|
|
139
|
+
requestId,
|
|
140
|
+
raw,
|
|
141
|
+
url
|
|
101
142
|
};
|
|
143
|
+
if (hasLazySignal) {
|
|
144
|
+
Object.defineProperty(frameworkRequest, 'signal', {
|
|
145
|
+
configurable: true,
|
|
146
|
+
enumerable: true,
|
|
147
|
+
get() {
|
|
148
|
+
return signal();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
} else {
|
|
152
|
+
frameworkRequest.signal = signal;
|
|
153
|
+
}
|
|
154
|
+
if (hasQuerySnapshot) {
|
|
155
|
+
frameworkRequest.query = query;
|
|
156
|
+
} else {
|
|
157
|
+
Object.defineProperty(frameworkRequest, 'query', {
|
|
158
|
+
configurable: true,
|
|
159
|
+
enumerable: true,
|
|
160
|
+
get() {
|
|
161
|
+
return resolveQuery();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (materializeBody) {
|
|
166
|
+
frameworkRequest.materializeBody = materializeBody;
|
|
167
|
+
}
|
|
102
168
|
return frameworkRequest;
|
|
103
169
|
}
|
|
104
170
|
function hasNodeRequestBody(request) {
|
|
@@ -125,7 +191,14 @@ export async function materializeFrameworkRequestBody(request) {
|
|
|
125
191
|
await request.materializeBody?.();
|
|
126
192
|
delete request.materializeBody;
|
|
127
193
|
}
|
|
128
|
-
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Creates a synchronous memoized value resolver.
|
|
197
|
+
*
|
|
198
|
+
* @param factory - Function that computes the value on first access.
|
|
199
|
+
* @returns A stable resolver that returns the cached value after the first call.
|
|
200
|
+
*/
|
|
201
|
+
export function createMemoizedValue(factory) {
|
|
129
202
|
let initialized = false;
|
|
130
203
|
let value;
|
|
131
204
|
return () => {
|
|
@@ -136,7 +209,14 @@ function createMemoizedValue(factory) {
|
|
|
136
209
|
return value;
|
|
137
210
|
};
|
|
138
211
|
}
|
|
139
|
-
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Creates an async memoized side-effect resolver.
|
|
215
|
+
*
|
|
216
|
+
* @param factory - Async function to run at most once.
|
|
217
|
+
* @returns A resolver that returns the same in-flight or completed promise for every call.
|
|
218
|
+
*/
|
|
219
|
+
export function createMemoizedAsyncValue(factory) {
|
|
140
220
|
let promise;
|
|
141
221
|
return () => {
|
|
142
222
|
promise ??= factory();
|
|
@@ -175,6 +255,20 @@ export function resolveRequestIdFromHeaders(headers) {
|
|
|
175
255
|
const requestId = headers['x-request-id'] ?? headers['x-correlation-id'];
|
|
176
256
|
return Array.isArray(requestId) ? requestId[0] : requestId;
|
|
177
257
|
}
|
|
258
|
+
function resolvePrimaryRequestIdFromHeaders(headers) {
|
|
259
|
+
const requestId = headers['x-request-id'];
|
|
260
|
+
return Array.isArray(requestId) ? requestId[0] : requestId;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Parses a raw URL search string into the framework query shape.
|
|
265
|
+
*
|
|
266
|
+
* @param search - Raw search string, with or without a leading question mark.
|
|
267
|
+
* @returns Query values where repeated keys become string arrays.
|
|
268
|
+
*/
|
|
269
|
+
export function parseQueryParamsFromSearch(search) {
|
|
270
|
+
return parseQueryParams(new URLSearchParams(search));
|
|
271
|
+
}
|
|
178
272
|
function parseQueryParams(searchParams) {
|
|
179
273
|
const query = {};
|
|
180
274
|
for (const [key, value] of searchParams.entries()) {
|
|
@@ -191,20 +285,73 @@ function parseQueryParams(searchParams) {
|
|
|
191
285
|
}
|
|
192
286
|
return query;
|
|
193
287
|
}
|
|
194
|
-
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Snapshots host-parsed query values when they already match framework semantics.
|
|
291
|
+
*
|
|
292
|
+
* @param query - Host query object exposed by a Node-backed adapter.
|
|
293
|
+
* @returns A cloned query record when all values are strings or string arrays; otherwise `undefined` for raw URL fallback.
|
|
294
|
+
*/
|
|
295
|
+
export function snapshotSimpleQueryRecord(query) {
|
|
296
|
+
if (typeof query !== 'object' || query === null) {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
const snapshot = {};
|
|
300
|
+
for (const [key, value] of Object.entries(query)) {
|
|
301
|
+
if (typeof value === 'string') {
|
|
302
|
+
snapshot[key] = value;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (Array.isArray(value) && value.every(item => typeof item === 'string')) {
|
|
306
|
+
snapshot[key] = [...value];
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
return snapshot;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Clones Node request headers into the framework header record shape.
|
|
316
|
+
*
|
|
317
|
+
* @param headers - Raw Node incoming headers.
|
|
318
|
+
* @returns A shallow header snapshot with array values cloned.
|
|
319
|
+
*/
|
|
320
|
+
export function cloneRequestHeaders(headers) {
|
|
195
321
|
const clonedEntries = Object.entries(headers).map(([name, value]) => [name, cloneHeaderValue(value)]);
|
|
196
322
|
return Object.fromEntries(clonedEntries);
|
|
197
323
|
}
|
|
198
|
-
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Clones a single Node header value when it is array-backed.
|
|
327
|
+
*
|
|
328
|
+
* @param value - Header value to snapshot.
|
|
329
|
+
* @returns The original scalar value or a cloned array value.
|
|
330
|
+
*/
|
|
331
|
+
export function cloneHeaderValue(value) {
|
|
199
332
|
return Array.isArray(value) ? [...value] : value;
|
|
200
333
|
}
|
|
201
|
-
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Reads the primary value from a Node header value.
|
|
337
|
+
*
|
|
338
|
+
* @param headerValue - Header value that may contain multiple entries.
|
|
339
|
+
* @returns The first header value when present.
|
|
340
|
+
*/
|
|
341
|
+
export function readPrimaryHeaderValue(headerValue) {
|
|
202
342
|
if (Array.isArray(headerValue)) {
|
|
203
343
|
return headerValue[0];
|
|
204
344
|
}
|
|
205
345
|
return headerValue;
|
|
206
346
|
}
|
|
207
|
-
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Normalizes a Node content-type header to its primary media type.
|
|
350
|
+
*
|
|
351
|
+
* @param headerValue - Raw content-type header value.
|
|
352
|
+
* @returns Lowercase primary media type without parameters, or `undefined` when absent.
|
|
353
|
+
*/
|
|
354
|
+
export function normalizePrimaryContentType(headerValue) {
|
|
208
355
|
const primaryHeaderValue = readPrimaryHeaderValue(headerValue);
|
|
209
356
|
if (typeof primaryHeaderValue !== 'string') {
|
|
210
357
|
return undefined;
|
|
@@ -220,7 +367,14 @@ function decodeCookieValue(raw) {
|
|
|
220
367
|
return raw;
|
|
221
368
|
}
|
|
222
369
|
}
|
|
223
|
-
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Parses a Node cookie header into framework cookie values.
|
|
373
|
+
*
|
|
374
|
+
* @param cookieHeader - Raw cookie header value or values.
|
|
375
|
+
* @returns Cookie name/value pairs with percent-decoded values when possible.
|
|
376
|
+
*/
|
|
377
|
+
export function parseCookieHeader(cookieHeader) {
|
|
224
378
|
const normalizedCookieHeader = Array.isArray(cookieHeader) ? cookieHeader.join('; ') : cookieHeader;
|
|
225
379
|
if (!normalizedCookieHeader) {
|
|
226
380
|
return {};
|
|
@@ -272,4 +426,51 @@ async function readRequestBody(request, contentType, maxBodySize = 1 * 1024 * 10
|
|
|
272
426
|
body: bodyText,
|
|
273
427
|
rawBody: preserveRawBody ? rawBody : undefined
|
|
274
428
|
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Splits a raw Node request URL into path and search components.
|
|
433
|
+
*
|
|
434
|
+
* @param rawUrl - Raw request URL, absolute URL, or undefined value from Node.
|
|
435
|
+
* @returns The pathname and search string used by framework request matching and query parsing.
|
|
436
|
+
*/
|
|
437
|
+
export function splitRawRequestUrl(rawUrl) {
|
|
438
|
+
const resolvedRawUrl = rawUrl ?? '/';
|
|
439
|
+
if (resolvedRawUrl.startsWith('http://') || resolvedRawUrl.startsWith('https://')) {
|
|
440
|
+
const url = new URL(resolvedRawUrl);
|
|
441
|
+
return {
|
|
442
|
+
path: url.pathname,
|
|
443
|
+
search: url.search
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const queryStart = resolvedRawUrl.indexOf('?');
|
|
447
|
+
const hashStart = resolvedRawUrl.indexOf('#');
|
|
448
|
+
const pathEndCandidates = [queryStart, hashStart].filter(index => index >= 0);
|
|
449
|
+
const pathEnd = pathEndCandidates.length > 0 ? Math.min(...pathEndCandidates) : resolvedRawUrl.length;
|
|
450
|
+
const path = resolvedRawUrl.slice(0, pathEnd) || '/';
|
|
451
|
+
if (queryStart === -1) {
|
|
452
|
+
return {
|
|
453
|
+
path,
|
|
454
|
+
search: ''
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const searchEnd = hashStart >= 0 && hashStart > queryStart ? hashStart : resolvedRawUrl.length;
|
|
458
|
+
return {
|
|
459
|
+
path,
|
|
460
|
+
search: resolvedRawUrl.slice(queryStart, searchEnd)
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Resolves a raw Node request URL into an absolute URL string.
|
|
466
|
+
*
|
|
467
|
+
* @param rawUrl - Raw request URL, absolute URL, or undefined value from Node.
|
|
468
|
+
* @returns An absolute URL suitable for Web-standard parsers.
|
|
469
|
+
*/
|
|
470
|
+
export function resolveAbsoluteRequestUrl(rawUrl) {
|
|
471
|
+
const resolvedRawUrl = rawUrl ?? '/';
|
|
472
|
+
if (resolvedRawUrl.startsWith('http://') || resolvedRawUrl.startsWith('https://')) {
|
|
473
|
+
return resolvedRawUrl;
|
|
474
|
+
}
|
|
475
|
+
return new URL(resolvedRawUrl, 'http://localhost').toString();
|
|
275
476
|
}
|
|
@@ -2,6 +2,7 @@ import { createServer as createHttpServer } from 'node:http';
|
|
|
2
2
|
import { createServer as createHttpsServer, type ServerOptions as HttpsServerOptions } from 'node:https';
|
|
3
3
|
import { type CorsOptions, type Dispatcher, type HttpApplicationAdapter, type MiddlewareLike, type SecurityHeadersOptions } from '@fluojs/http';
|
|
4
4
|
import { createNodeResponseCompression, compressNodeResponse } from './internal-node-compression.js';
|
|
5
|
+
import { cloneHeaderValue, cloneRequestHeaders, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, createRequestSignal, readPrimaryHeaderValue, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl } from './internal-node-request.js';
|
|
5
6
|
import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals } from './internal-node-shutdown.js';
|
|
6
7
|
import type { MultipartOptions, UploadedFile } from '../multipart.js';
|
|
7
8
|
import type { Application, ApplicationLogger, CreateApplicationOptions, ModuleType } from '../types.js';
|
|
@@ -112,5 +113,5 @@ export declare function bootstrapNodeApplication(rootModule: ModuleType, options
|
|
|
112
113
|
* @returns The run node application result.
|
|
113
114
|
*/
|
|
114
115
|
export declare function runNodeApplication(rootModule: ModuleType, options: RunNodeApplicationOptions): Promise<Application>;
|
|
115
|
-
export { compressNodeResponse, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals, };
|
|
116
|
+
export { cloneHeaderValue, cloneRequestHeaders, compressNodeResponse, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, createRequestSignal, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, readPrimaryHeaderValue, registerShutdownSignals, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl, };
|
|
116
117
|
//# sourceMappingURL=internal-node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-node.d.ts","sourceRoot":"","sources":["../../src/node/internal-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,gBAAgB,EAA6C,MAAM,WAAW,CAAC;AACxG,OAAO,EAAE,YAAY,IAAI,iBAAiB,EAAE,KAAK,aAAa,IAAI,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGzG,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC5B,MAAM,cAAc,CAAC;AAMtB,OAAO,EACL,6BAA6B,EAC7B,oBAAoB,EACrB,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"internal-node.d.ts","sourceRoot":"","sources":["../../src/node/internal-node.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { createServer as createHttpsServer } from 'node:https';
|
|
|
3
3
|
import { createServerBackedHttpAdapterRealtimeCapability } from '@fluojs/http';
|
|
4
4
|
import { bootstrapHttpAdapterApplication, runHttpAdapterApplication } from '../http-adapter-shared.js';
|
|
5
5
|
import { createNodeResponseCompression, compressNodeResponse } from './internal-node-compression.js';
|
|
6
|
-
import { createDeferredFrameworkRequest, NodeRequestPayloadTooLargeException, createRequestSignal, materializeFrameworkRequestBody, resolveRequestIdFromHeaders } from './internal-node-request.js';
|
|
6
|
+
import { cloneHeaderValue, cloneRequestHeaders, createDeferredFrameworkRequestShell, createDeferredFrameworkRequest, createMemoizedAsyncValue, createMemoizedValue, NodeRequestPayloadTooLargeException, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, createRequestSignal, materializeFrameworkRequestBody, readPrimaryHeaderValue, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl } from './internal-node-request.js';
|
|
7
7
|
import { createFrameworkResponse, writeNodeAdapterErrorResponse } from './internal-node-response.js';
|
|
8
8
|
import { createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals } from './internal-node-shutdown.js';
|
|
9
9
|
import { dispatchWithRequestResponseFactory } from '../adapters/request-response-factory.js';
|
|
@@ -156,7 +156,7 @@ export async function runNodeApplication(rootModule, options) {
|
|
|
156
156
|
shutdownRegistration: createNodeShutdownSignalRegistration(options.shutdownSignals ?? defaultNodeShutdownSignals())
|
|
157
157
|
}, adapter);
|
|
158
158
|
}
|
|
159
|
-
export { compressNodeResponse, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, registerShutdownSignals };
|
|
159
|
+
export { cloneHeaderValue, cloneRequestHeaders, compressNodeResponse, createDeferredFrameworkRequestShell, createMemoizedAsyncValue, createMemoizedValue, createRequestSignal, createNodeResponseCompression, createNodeShutdownSignalRegistration, defaultNodeShutdownSignals, normalizePrimaryContentType, parseCookieHeader, parseQueryParamsFromSearch, readPrimaryHeaderValue, registerShutdownSignals, resolveAbsoluteRequestUrl, resolveRequestIdFromHeaders, snapshotSimpleQueryRecord, splitRawRequestUrl };
|
|
160
160
|
function createNodeServer(httpsOptions, handler) {
|
|
161
161
|
return httpsOptions ? createHttpsServer(httpsOptions, handler) : createHttpServer(handler);
|
|
162
162
|
}
|
package/dist/web.d.ts
CHANGED
|
@@ -11,8 +11,10 @@ declare module '@fluojs/http' {
|
|
|
11
11
|
* Configures Web request parsing, multipart handling, and raw body preservation.
|
|
12
12
|
*/
|
|
13
13
|
export interface CreateWebRequestResponseFactoryOptions {
|
|
14
|
+
consumeOriginalBody?: boolean;
|
|
14
15
|
maxBodySize?: number;
|
|
15
16
|
multipart?: MultipartOptions;
|
|
17
|
+
preferNativeJsonBodyReader?: boolean;
|
|
16
18
|
rawBody?: boolean;
|
|
17
19
|
}
|
|
18
20
|
/**
|
package/dist/web.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAMtB,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAOD;;GAEG;AACH,MAAM,WAAW,sCAAsC;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,yBAA0B,SAAQ,sCAAsC;IACvF,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,oBAAoB,CAAC,CAAC;IACzF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,UAAU,IAAI,QAAQ,CAAC;CACxB;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAMtB,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAOD;;GAEG;AACH,MAAM,WAAW,sCAAsC;IACrD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,yBAA0B,SAAQ,sCAAsC;IACvF,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,oBAAoB,CAAC,CAAC;IACzF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,UAAU,IAAI,QAAQ,CAAC;CACxB;AAED,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAgNhD;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,sCAA2C,GACnD,sBAAsB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,oBAAoB,CAAC,CA+BhF;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,UAAU,EACV,yBAAiG,EACjG,OAAO,EACP,OAAO,EACP,GAAG,OAAO,EACX,EAAE,yBAAyB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAU/C;AAED;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,WAAW,EACnB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,WAAW,SAAwB,EACnC,eAAe,UAAQ,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAW3B"}
|
package/dist/web.js
CHANGED
|
@@ -146,8 +146,7 @@ class MutableWebFrameworkResponse {
|
|
|
146
146
|
headers: toResponseHeaders(this.headers),
|
|
147
147
|
status: this.statusCode ?? 200
|
|
148
148
|
};
|
|
149
|
-
const
|
|
150
|
-
const nativeResponseBody = isResponseBodyForbidden(init.status) ? undefined : responseBody ?? '';
|
|
149
|
+
const nativeResponseBody = isResponseBodyForbidden(init.status) ? undefined : this.responseBody === undefined ? '' : this.responseBody;
|
|
151
150
|
this.finalizedResponse = this.streamActive ? new Response(this.getOrCreateResponseStream().readable, init) : new Response(nativeResponseBody, init);
|
|
152
151
|
this.raw = this.finalizedResponse;
|
|
153
152
|
this.committed = true;
|
|
@@ -171,7 +170,7 @@ class MutableWebFrameworkResponse {
|
|
|
171
170
|
export function createWebRequestResponseFactory(options = {}) {
|
|
172
171
|
return {
|
|
173
172
|
async createRequest(request, signal) {
|
|
174
|
-
return createDeferredWebFrameworkRequest(request, signal, options.multipart, options.maxBodySize ?? DEFAULT_MAX_BODY_SIZE, options.rawBody ?? false);
|
|
173
|
+
return createDeferredWebFrameworkRequest(request, signal, options.multipart, options.maxBodySize ?? DEFAULT_MAX_BODY_SIZE, options.rawBody ?? false, options.preferNativeJsonBodyReader ?? false, options.consumeOriginalBody ?? false);
|
|
175
174
|
},
|
|
176
175
|
materializeRequest(request) {
|
|
177
176
|
return materializeWebFrameworkRequestBody(request);
|
|
@@ -242,26 +241,20 @@ export async function createWebFrameworkRequest(request, signal, multipartOption
|
|
|
242
241
|
* @param preserveRawBody - Whether materialization should retain raw request body bytes.
|
|
243
242
|
* @returns The framework request shell with metadata snapshotted and body materialization deferred.
|
|
244
243
|
*/
|
|
245
|
-
function createDeferredWebFrameworkRequest(request, signal, multipartOptions, maxBodySize = DEFAULT_MAX_BODY_SIZE, preserveRawBody = false) {
|
|
244
|
+
function createDeferredWebFrameworkRequest(request, signal, multipartOptions, maxBodySize = DEFAULT_MAX_BODY_SIZE, preserveRawBody = false, preferNativeJsonBodyReader = false, consumeOriginalBody = false) {
|
|
246
245
|
const url = new URL(request.url);
|
|
247
|
-
const
|
|
246
|
+
const requestHeaders = new Headers(request.headers);
|
|
248
247
|
const method = request.method;
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const query = createMemoizedValue(() => parseQueryParams(searchParams));
|
|
254
|
-
const contentType = request.headers.get('content-type') ?? undefined;
|
|
248
|
+
const headers = createMemoizedValue(() => cloneWebHeaders(requestHeaders));
|
|
249
|
+
const cookies = createMemoizedValue(() => parseCookieHeader(requestHeaders.get('cookie') ?? undefined));
|
|
250
|
+
const query = createMemoizedValue(() => parseQueryString(url.search));
|
|
251
|
+
const contentType = requestHeaders.get('content-type') ?? undefined;
|
|
255
252
|
const isMultipart = typeof contentType === 'string' && contentType.includes('multipart/form-data');
|
|
256
|
-
const
|
|
253
|
+
const hasRequestBody = request.body !== null;
|
|
254
|
+
const materializeBody = hasRequestBody ? createMemoizedAsyncValue(async () => {
|
|
257
255
|
if (isMultipart) {
|
|
258
256
|
const materializedRequest = request.clone();
|
|
259
|
-
const result = await parseMultipart({
|
|
260
|
-
body: materializedRequest.body,
|
|
261
|
-
headers: new Headers(headerEntries),
|
|
262
|
-
method,
|
|
263
|
-
url: url.toString()
|
|
264
|
-
}, {
|
|
257
|
+
const result = await parseMultipart(createRequestWithSnapshotMetadata(materializedRequest, request.url, method, requestHeaders), {
|
|
265
258
|
...multipartOptions,
|
|
266
259
|
maxTotalSize: multipartOptions?.maxTotalSize ?? maxBodySize
|
|
267
260
|
});
|
|
@@ -274,12 +267,13 @@ function createDeferredWebFrameworkRequest(request, signal, multipartOptions, ma
|
|
|
274
267
|
frameworkRequest.body = undefined;
|
|
275
268
|
return;
|
|
276
269
|
}
|
|
277
|
-
const
|
|
270
|
+
const requestToRead = consumeOriginalBody ? request : request.clone();
|
|
271
|
+
const bodyResult = await readWebRequestBody(requestToRead, contentType, maxBodySize, preserveRawBody, preferNativeJsonBodyReader);
|
|
278
272
|
frameworkRequest.body = bodyResult.body;
|
|
279
273
|
if (bodyResult.rawBody) {
|
|
280
274
|
frameworkRequest.rawBody = bodyResult.rawBody;
|
|
281
275
|
}
|
|
282
|
-
});
|
|
276
|
+
}) : undefined;
|
|
283
277
|
const frameworkRequest = {
|
|
284
278
|
get cookies() {
|
|
285
279
|
return cookies();
|
|
@@ -294,13 +288,28 @@ function createDeferredWebFrameworkRequest(request, signal, multipartOptions, ma
|
|
|
294
288
|
return query();
|
|
295
289
|
},
|
|
296
290
|
raw: request,
|
|
291
|
+
requestId: requestHeaders.get('x-request-id') ?? undefined,
|
|
297
292
|
signal,
|
|
298
293
|
url: url.pathname + url.search,
|
|
299
294
|
materializeBody
|
|
300
295
|
};
|
|
296
|
+
if (!hasRequestBody) {
|
|
297
|
+
frameworkRequest.body = undefined;
|
|
298
|
+
}
|
|
301
299
|
const nativeRouteHandoff = consumeRawRequestNativeRouteHandoff(request);
|
|
302
300
|
return nativeRouteHandoff ? attachFrameworkRequestNativeRouteHandoff(frameworkRequest, nativeRouteHandoff) : frameworkRequest;
|
|
303
301
|
}
|
|
302
|
+
function createRequestWithSnapshotMetadata(request, url, method, headers) {
|
|
303
|
+
const init = {
|
|
304
|
+
headers: new Headers(headers),
|
|
305
|
+
method
|
|
306
|
+
};
|
|
307
|
+
if (request.body) {
|
|
308
|
+
init.body = request.body;
|
|
309
|
+
init.duplex = 'half';
|
|
310
|
+
}
|
|
311
|
+
return new Request(url, init);
|
|
312
|
+
}
|
|
304
313
|
function validateWebRequestContentLength(request, maxBodySize) {
|
|
305
314
|
const contentLength = request.headers.get('content-length');
|
|
306
315
|
if (contentLength === null) {
|
|
@@ -340,25 +349,56 @@ function createMemoizedAsyncValue(factory) {
|
|
|
340
349
|
return promise;
|
|
341
350
|
};
|
|
342
351
|
}
|
|
343
|
-
function
|
|
352
|
+
function parseQueryString(search) {
|
|
344
353
|
const query = {};
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
354
|
+
if (search.length <= 1) {
|
|
355
|
+
return query;
|
|
356
|
+
}
|
|
357
|
+
let index = search.charCodeAt(0) === 63 ? 1 : 0;
|
|
358
|
+
while (index <= search.length) {
|
|
359
|
+
let nextDelimiter = search.indexOf('&', index);
|
|
360
|
+
if (nextDelimiter === -1) {
|
|
361
|
+
nextDelimiter = search.length;
|
|
350
362
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
363
|
+
const entry = search.slice(index, nextDelimiter);
|
|
364
|
+
if (entry.length > 0) {
|
|
365
|
+
const separatorIndex = entry.indexOf('=');
|
|
366
|
+
const rawKey = separatorIndex === -1 ? entry : entry.slice(0, separatorIndex);
|
|
367
|
+
const rawValue = separatorIndex === -1 ? '' : entry.slice(separatorIndex + 1);
|
|
368
|
+
const key = decodeQueryComponent(rawKey, 'key');
|
|
369
|
+
const value = decodeQueryComponent(rawValue, 'value');
|
|
370
|
+
const current = query[key];
|
|
371
|
+
if (current === undefined) {
|
|
372
|
+
query[key] = value;
|
|
373
|
+
} else if (Array.isArray(current)) {
|
|
374
|
+
current.push(value);
|
|
375
|
+
} else {
|
|
376
|
+
query[key] = [current, value];
|
|
377
|
+
}
|
|
354
378
|
}
|
|
355
|
-
|
|
379
|
+
index = nextDelimiter + 1;
|
|
356
380
|
}
|
|
357
381
|
return query;
|
|
358
382
|
}
|
|
383
|
+
function decodeQueryComponent(value, kind) {
|
|
384
|
+
const normalizedValue = value.includes('+') ? value.replaceAll('+', ' ') : value;
|
|
385
|
+
try {
|
|
386
|
+
return decodeURIComponent(normalizedValue);
|
|
387
|
+
} catch {
|
|
388
|
+
return decodeQueryComponentLikeUrlSearchParams(value, kind);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function decodeQueryComponentLikeUrlSearchParams(value, kind) {
|
|
392
|
+
if (kind === 'key') {
|
|
393
|
+
const params = new URLSearchParams(`${value}=`);
|
|
394
|
+
return params.keys().next().value ?? '';
|
|
395
|
+
}
|
|
396
|
+
const params = new URLSearchParams(`x=${value}`);
|
|
397
|
+
return params.get('x') ?? '';
|
|
398
|
+
}
|
|
359
399
|
function cloneWebHeaders(headers) {
|
|
360
400
|
const clonedHeaders = {};
|
|
361
|
-
for (const [name, value] of headers) {
|
|
401
|
+
for (const [name, value] of headers.entries()) {
|
|
362
402
|
clonedHeaders[name] = value;
|
|
363
403
|
}
|
|
364
404
|
return clonedHeaders;
|
|
@@ -382,14 +422,23 @@ function parseCookieHeader(cookieHeader) {
|
|
|
382
422
|
return [pair.slice(0, index).trim(), decodeCookieValue(pair.slice(index + 1).trim())];
|
|
383
423
|
}));
|
|
384
424
|
}
|
|
385
|
-
async function readWebRequestBody(request, contentType, maxBodySize = DEFAULT_MAX_BODY_SIZE, preserveRawBody = false) {
|
|
425
|
+
async function readWebRequestBody(request, contentType, maxBodySize = DEFAULT_MAX_BODY_SIZE, preserveRawBody = false, preferNativeJsonBodyReader = false) {
|
|
386
426
|
validateWebRequestContentLength(request, maxBodySize);
|
|
387
427
|
if (!request.body) {
|
|
388
428
|
return {
|
|
389
429
|
body: undefined
|
|
390
430
|
};
|
|
391
431
|
}
|
|
392
|
-
|
|
432
|
+
if (!preserveRawBody && isJsonContentType(contentType) && (preferNativeJsonBodyReader || isContentLengthWithinLimit(request, maxBodySize))) {
|
|
433
|
+
const rawBody = new Uint8Array(await request.arrayBuffer());
|
|
434
|
+
if (rawBody.byteLength > maxBodySize) {
|
|
435
|
+
throw new PayloadTooLargeException(REQUEST_BODY_LIMIT_MESSAGE);
|
|
436
|
+
}
|
|
437
|
+
return parseWebRequestRawBody(rawBody, contentType, preserveRawBody);
|
|
438
|
+
}
|
|
439
|
+
return parseWebRequestRawBody(await readByteLimitedStream(request.body, maxBodySize), contentType, preserveRawBody);
|
|
440
|
+
}
|
|
441
|
+
function parseWebRequestRawBody(rawBody, contentType, preserveRawBody) {
|
|
393
442
|
if (rawBody.byteLength === 0) {
|
|
394
443
|
return {
|
|
395
444
|
body: undefined
|
|
@@ -402,7 +451,7 @@ async function readWebRequestBody(request, contentType, maxBodySize = DEFAULT_MA
|
|
|
402
451
|
rawBody: preserveRawBody ? rawBody : undefined
|
|
403
452
|
};
|
|
404
453
|
}
|
|
405
|
-
if (
|
|
454
|
+
if (isJsonContentType(contentType)) {
|
|
406
455
|
try {
|
|
407
456
|
return {
|
|
408
457
|
body: JSON.parse(bodyText),
|
|
@@ -417,6 +466,14 @@ async function readWebRequestBody(request, contentType, maxBodySize = DEFAULT_MA
|
|
|
417
466
|
rawBody: preserveRawBody ? rawBody : undefined
|
|
418
467
|
};
|
|
419
468
|
}
|
|
469
|
+
function isContentLengthWithinLimit(request, maxBodySize) {
|
|
470
|
+
const contentLength = request.headers.get('content-length');
|
|
471
|
+
if (contentLength === null) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
const parsedContentLength = Number(contentLength);
|
|
475
|
+
return Number.isFinite(parsedContentLength) && parsedContentLength > 0 && parsedContentLength <= maxBodySize;
|
|
476
|
+
}
|
|
420
477
|
async function readByteLimitedStream(stream, maxBodySize) {
|
|
421
478
|
const reader = stream.getReader();
|
|
422
479
|
const chunks = [];
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"module-graph",
|
|
10
10
|
"orchestration"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.0-beta.
|
|
12
|
+
"version": "1.0.0-beta.8",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@fluojs/config": "^1.0.0-beta.3",
|
|
71
71
|
"@fluojs/core": "^1.0.0-beta.2",
|
|
72
72
|
"@fluojs/di": "^1.0.0-beta.5",
|
|
73
|
-
"@fluojs/http": "^1.0.0-beta.
|
|
73
|
+
"@fluojs/http": "^1.0.0-beta.6"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"vitest": "^3.2.4",
|