@whatwg-node/server 0.10.0-alpha-20240717150008-1474b9d9b679a0e8f6225f44f11b95a6f4bf24ea → 0.10.0-alpha-20241123133536-975c9068dde45574fcfa26567e4bab96f45d1f85
Sign up to get free protection for your applications and to get access to all the features.
- package/cjs/createServerAdapter.js +62 -24
- package/cjs/index.js +1 -0
- package/cjs/plugins/useContentEncoding.js +133 -0
- package/cjs/plugins/useErrorHandling.js +5 -1
- package/cjs/utils.js +174 -96
- package/cjs/uwebsockets.js +192 -27
- package/esm/createServerAdapter.js +63 -25
- package/esm/index.js +1 -0
- package/esm/plugins/useContentEncoding.js +130 -0
- package/esm/plugins/useErrorHandling.js +5 -1
- package/esm/utils.js +170 -95
- package/esm/uwebsockets.js +190 -27
- package/package.json +4 -3
- package/typings/index.d.cts +1 -0
- package/typings/index.d.ts +1 -0
- package/typings/plugins/types.d.cts +3 -0
- package/typings/plugins/types.d.ts +3 -0
- package/typings/plugins/useContentEncoding.d.cts +2 -0
- package/typings/plugins/useContentEncoding.d.ts +2 -0
- package/typings/plugins/useCors.d.cts +1 -1
- package/typings/plugins/useCors.d.ts +1 -1
- package/typings/plugins/useErrorHandling.d.cts +3 -3
- package/typings/plugins/useErrorHandling.d.ts +3 -3
- package/typings/types.d.cts +10 -2
- package/typings/types.d.ts +10 -2
- package/typings/utils.d.cts +8 -3
- package/typings/utils.d.ts +8 -3
- package/typings/uwebsockets.d.cts +3 -1
- package/typings/uwebsockets.d.ts +3 -1
package/cjs/utils.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.ServerAdapterRequestAbortSignal = void 0;
|
3
|
+
exports.decompressedResponseMap = exports.nodeRequestResponseMap = exports.ServerAdapterRequestAbortSignal = void 0;
|
4
4
|
exports.isAsyncIterable = isAsyncIterable;
|
5
5
|
exports.normalizeNodeRequest = normalizeNodeRequest;
|
6
6
|
exports.isReadable = isReadable;
|
@@ -17,7 +17,9 @@ exports.handleErrorFromRequestHandler = handleErrorFromRequestHandler;
|
|
17
17
|
exports.isolateObject = isolateObject;
|
18
18
|
exports.createDeferredPromise = createDeferredPromise;
|
19
19
|
exports.handleAbortSignalAndPromiseResponse = handleAbortSignalAndPromiseResponse;
|
20
|
-
|
20
|
+
exports.getSupportedEncodings = getSupportedEncodings;
|
21
|
+
exports.handleResponseDecompression = handleResponseDecompression;
|
22
|
+
exports.ensureDisposableStackRegisteredForTerminateEvents = ensureDisposableStackRegisteredForTerminateEvents;
|
21
23
|
function isAsyncIterable(body) {
|
22
24
|
return (body != null && typeof body === 'object' && typeof body[Symbol.asyncIterator] === 'function');
|
23
25
|
}
|
@@ -68,11 +70,9 @@ function isRequestBody(body) {
|
|
68
70
|
return false;
|
69
71
|
}
|
70
72
|
class ServerAdapterRequestAbortSignal extends EventTarget {
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
this._onabort = null;
|
75
|
-
}
|
73
|
+
aborted = false;
|
74
|
+
_onabort = null;
|
75
|
+
reason;
|
76
76
|
throwIfAborted() {
|
77
77
|
if (this.aborted) {
|
78
78
|
throw this.reason;
|
@@ -101,22 +101,36 @@ class ServerAdapterRequestAbortSignal extends EventTarget {
|
|
101
101
|
}
|
102
102
|
exports.ServerAdapterRequestAbortSignal = ServerAdapterRequestAbortSignal;
|
103
103
|
let bunNodeCompatModeWarned = false;
|
104
|
-
|
104
|
+
exports.nodeRequestResponseMap = new WeakMap();
|
105
|
+
function normalizeNodeRequest(nodeRequest, fetchAPI, registerSignal) {
|
105
106
|
const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
|
106
107
|
let fullUrl = buildFullUrl(rawRequest);
|
107
108
|
if (nodeRequest.query) {
|
108
|
-
const url = new
|
109
|
+
const url = new fetchAPI.URL(fullUrl);
|
109
110
|
for (const key in nodeRequest.query) {
|
110
111
|
url.searchParams.set(key, nodeRequest.query[key]);
|
111
112
|
}
|
112
113
|
fullUrl = url.toString();
|
113
114
|
}
|
114
115
|
let signal;
|
115
|
-
|
116
|
+
const nodeResponse = exports.nodeRequestResponseMap.get(nodeRequest);
|
117
|
+
exports.nodeRequestResponseMap.delete(nodeRequest);
|
118
|
+
let normalizedHeaders = nodeRequest.headers;
|
119
|
+
if (nodeRequest.headers?.[':method']) {
|
120
|
+
normalizedHeaders = {};
|
121
|
+
for (const key in nodeRequest.headers) {
|
122
|
+
if (!key.startsWith(':')) {
|
123
|
+
normalizedHeaders[key] = nodeRequest.headers[key];
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
if (nodeResponse?.once) {
|
116
128
|
let sendAbortSignal;
|
117
129
|
// If ponyfilled
|
118
|
-
if (
|
119
|
-
|
130
|
+
if (fetchAPI.Request !== globalThis.Request) {
|
131
|
+
const newSignal = new ServerAdapterRequestAbortSignal();
|
132
|
+
registerSignal?.(newSignal);
|
133
|
+
signal = newSignal;
|
120
134
|
sendAbortSignal = () => signal.sendAbort();
|
121
135
|
}
|
122
136
|
else {
|
@@ -137,9 +151,9 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
137
151
|
});
|
138
152
|
}
|
139
153
|
if (nodeRequest.method === 'GET' || nodeRequest.method === 'HEAD') {
|
140
|
-
return new
|
154
|
+
return new fetchAPI.Request(fullUrl, {
|
141
155
|
method: nodeRequest.method,
|
142
|
-
headers:
|
156
|
+
headers: normalizedHeaders,
|
143
157
|
signal,
|
144
158
|
});
|
145
159
|
}
|
@@ -152,16 +166,16 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
152
166
|
const maybeParsedBody = nodeRequest.body;
|
153
167
|
if (maybeParsedBody != null && Object.keys(maybeParsedBody).length > 0) {
|
154
168
|
if (isRequestBody(maybeParsedBody)) {
|
155
|
-
return new
|
169
|
+
return new fetchAPI.Request(fullUrl, {
|
156
170
|
method: nodeRequest.method,
|
157
|
-
headers:
|
171
|
+
headers: normalizedHeaders,
|
158
172
|
body: maybeParsedBody,
|
159
173
|
signal,
|
160
174
|
});
|
161
175
|
}
|
162
|
-
const request = new
|
176
|
+
const request = new fetchAPI.Request(fullUrl, {
|
163
177
|
method: nodeRequest.method,
|
164
|
-
headers:
|
178
|
+
headers: normalizedHeaders,
|
165
179
|
signal,
|
166
180
|
});
|
167
181
|
if (!request.headers.get('content-type')?.includes('json')) {
|
@@ -187,9 +201,9 @@ function normalizeNodeRequest(nodeRequest, nodeResponse, RequestCtor) {
|
|
187
201
|
console.warn(`You use Bun Node compatibility mode, which is not recommended!
|
188
202
|
It will affect your performance. Please check our Bun integration recipe, and avoid using 'http' for your server implementation.`);
|
189
203
|
}
|
190
|
-
return new
|
204
|
+
return new fetchAPI.Request(fullUrl, {
|
191
205
|
method: nodeRequest.method,
|
192
|
-
headers:
|
206
|
+
headers: normalizedHeaders,
|
193
207
|
duplex: 'half',
|
194
208
|
body: new ReadableStream({
|
195
209
|
start(controller) {
|
@@ -211,9 +225,9 @@ It will affect your performance. Please check our Bun integration recipe, and av
|
|
211
225
|
});
|
212
226
|
}
|
213
227
|
// perf: instead of spreading the object, we can just pass it as is and it performs better
|
214
|
-
return new
|
228
|
+
return new fetchAPI.Request(fullUrl, {
|
215
229
|
method: nodeRequest.method,
|
216
|
-
headers:
|
230
|
+
headers: normalizedHeaders,
|
217
231
|
body: rawRequest,
|
218
232
|
duplex: 'half',
|
219
233
|
signal,
|
@@ -249,11 +263,26 @@ function endResponse(serverResponse) {
|
|
249
263
|
serverResponse.end(null, null, null);
|
250
264
|
}
|
251
265
|
async function sendAsyncIterable(serverResponse, asyncIterable) {
|
266
|
+
let closed = false;
|
267
|
+
const closeEventListener = () => {
|
268
|
+
closed = true;
|
269
|
+
};
|
270
|
+
serverResponse.once('error', closeEventListener);
|
271
|
+
serverResponse.once('close', closeEventListener);
|
272
|
+
serverResponse.once('finish', () => {
|
273
|
+
serverResponse.removeListener('close', closeEventListener);
|
274
|
+
});
|
252
275
|
for await (const chunk of asyncIterable) {
|
276
|
+
if (closed) {
|
277
|
+
break;
|
278
|
+
}
|
253
279
|
if (!serverResponse
|
254
280
|
// @ts-expect-error http and http2 writes are actually compatible
|
255
281
|
.write(chunk)) {
|
256
|
-
|
282
|
+
if (closed) {
|
283
|
+
break;
|
284
|
+
}
|
285
|
+
await new Promise(resolve => serverResponse.once('drain', resolve));
|
257
286
|
}
|
258
287
|
}
|
259
288
|
endResponse(serverResponse);
|
@@ -264,7 +293,7 @@ function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
|
|
264
293
|
}
|
265
294
|
if (!fetchResponse) {
|
266
295
|
serverResponse.statusCode = 404;
|
267
|
-
serverResponse
|
296
|
+
endResponse(serverResponse);
|
268
297
|
return;
|
269
298
|
}
|
270
299
|
serverResponse.statusCode = fetchResponse.status;
|
@@ -313,10 +342,31 @@ function sendNodeResponse(fetchResponse, serverResponse, nodeRequest) {
|
|
313
342
|
fetchBody.pipe(serverResponse);
|
314
343
|
return;
|
315
344
|
}
|
345
|
+
if (isReadableStream(fetchBody)) {
|
346
|
+
return sendReadableStream(serverResponse, fetchBody);
|
347
|
+
}
|
316
348
|
if (isAsyncIterable(fetchBody)) {
|
317
349
|
return sendAsyncIterable(serverResponse, fetchBody);
|
318
350
|
}
|
319
351
|
}
|
352
|
+
async function sendReadableStream(serverResponse, readableStream) {
|
353
|
+
const reader = readableStream.getReader();
|
354
|
+
serverResponse.req.once('error', err => {
|
355
|
+
reader.cancel(err);
|
356
|
+
});
|
357
|
+
while (true) {
|
358
|
+
const { done, value } = await reader.read();
|
359
|
+
if (done) {
|
360
|
+
break;
|
361
|
+
}
|
362
|
+
if (!serverResponse
|
363
|
+
// @ts-expect-error http and http2 writes are actually compatible
|
364
|
+
.write(value)) {
|
365
|
+
await new Promise(resolve => serverResponse.once('drain', resolve));
|
366
|
+
}
|
367
|
+
}
|
368
|
+
endResponse(serverResponse);
|
369
|
+
}
|
320
370
|
function isRequestInit(val) {
|
321
371
|
return (val != null &&
|
322
372
|
typeof val === 'object' &&
|
@@ -341,13 +391,16 @@ function completeAssign(...args) {
|
|
341
391
|
// modified Object.keys to Object.getOwnPropertyNames
|
342
392
|
// because Object.keys only returns enumerable properties
|
343
393
|
const descriptors = Object.getOwnPropertyNames(source).reduce((descriptors, key) => {
|
344
|
-
|
394
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
395
|
+
if (descriptor) {
|
396
|
+
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
|
397
|
+
}
|
345
398
|
return descriptors;
|
346
399
|
}, {});
|
347
400
|
// By default, Object.assign copies enumerable Symbols, too
|
348
401
|
Object.getOwnPropertySymbols(source).forEach(sym => {
|
349
402
|
const descriptor = Object.getOwnPropertyDescriptor(source, sym);
|
350
|
-
if (descriptor
|
403
|
+
if (descriptor?.enumerable) {
|
351
404
|
descriptors[sym] = descriptor;
|
352
405
|
}
|
353
406
|
});
|
@@ -390,77 +443,18 @@ function handleErrorFromRequestHandler(error, ResponseCtor) {
|
|
390
443
|
status: error.status || 500,
|
391
444
|
});
|
392
445
|
}
|
393
|
-
function isolateObject(originalCtx,
|
446
|
+
function isolateObject(originalCtx, waitUntilFn) {
|
394
447
|
if (originalCtx == null) {
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
}
|
406
|
-
const extraPropVal = extraProps[prop];
|
407
|
-
if (extraPropVal != null) {
|
408
|
-
if (typeof extraPropVal === 'function') {
|
409
|
-
return extraPropVal.bind(extraProps);
|
410
|
-
}
|
411
|
-
return extraPropVal;
|
412
|
-
}
|
413
|
-
if (deletedProps.has(prop)) {
|
414
|
-
return undefined;
|
415
|
-
}
|
416
|
-
return originalCtx[prop];
|
417
|
-
},
|
418
|
-
set(_originalCtx, prop, value) {
|
419
|
-
extraProps[prop] = value;
|
420
|
-
return true;
|
421
|
-
},
|
422
|
-
has(originalCtx, prop) {
|
423
|
-
if (waitUntilPromises != null && prop === 'waitUntil') {
|
424
|
-
return true;
|
425
|
-
}
|
426
|
-
if (deletedProps.has(prop)) {
|
427
|
-
return false;
|
428
|
-
}
|
429
|
-
if (prop in extraProps) {
|
430
|
-
return true;
|
431
|
-
}
|
432
|
-
return prop in originalCtx;
|
433
|
-
},
|
434
|
-
defineProperty(_originalCtx, prop, descriptor) {
|
435
|
-
return Reflect.defineProperty(extraProps, prop, descriptor);
|
436
|
-
},
|
437
|
-
deleteProperty(_originalCtx, prop) {
|
438
|
-
if (prop in extraProps) {
|
439
|
-
return Reflect.deleteProperty(extraProps, prop);
|
440
|
-
}
|
441
|
-
deletedProps.add(prop);
|
442
|
-
return true;
|
443
|
-
},
|
444
|
-
ownKeys(originalCtx) {
|
445
|
-
const extraKeys = Reflect.ownKeys(extraProps);
|
446
|
-
const originalKeys = Reflect.ownKeys(originalCtx);
|
447
|
-
const deletedKeys = Array.from(deletedProps);
|
448
|
-
const allKeys = new Set(extraKeys.concat(originalKeys.filter(keys => !deletedKeys.includes(keys))));
|
449
|
-
if (waitUntilPromises != null) {
|
450
|
-
allKeys.add('waitUntil');
|
451
|
-
}
|
452
|
-
return Array.from(allKeys);
|
453
|
-
},
|
454
|
-
getOwnPropertyDescriptor(originalCtx, prop) {
|
455
|
-
if (prop in extraProps) {
|
456
|
-
return Reflect.getOwnPropertyDescriptor(extraProps, prop);
|
457
|
-
}
|
458
|
-
if (deletedProps.has(prop)) {
|
459
|
-
return undefined;
|
460
|
-
}
|
461
|
-
return Reflect.getOwnPropertyDescriptor(originalCtx, prop);
|
462
|
-
},
|
463
|
-
});
|
448
|
+
if (waitUntilFn == null) {
|
449
|
+
return {};
|
450
|
+
}
|
451
|
+
return {
|
452
|
+
waitUntil: waitUntilFn,
|
453
|
+
};
|
454
|
+
}
|
455
|
+
return completeAssign(Object.create(originalCtx), {
|
456
|
+
waitUntil: waitUntilFn,
|
457
|
+
}, originalCtx);
|
464
458
|
}
|
465
459
|
function createDeferredPromise() {
|
466
460
|
let resolveFn;
|
@@ -496,3 +490,87 @@ function handleAbortSignalAndPromiseResponse(response$, abortSignal) {
|
|
496
490
|
}
|
497
491
|
return response$;
|
498
492
|
}
|
493
|
+
exports.decompressedResponseMap = new WeakMap();
|
494
|
+
const supportedEncodingsByFetchAPI = new WeakMap();
|
495
|
+
function getSupportedEncodings(fetchAPI) {
|
496
|
+
let supportedEncodings = supportedEncodingsByFetchAPI.get(fetchAPI);
|
497
|
+
if (!supportedEncodings) {
|
498
|
+
const possibleEncodings = ['deflate', 'gzip', 'deflate-raw', 'br'];
|
499
|
+
if (fetchAPI.DecompressionStream?.['supportedFormats']) {
|
500
|
+
supportedEncodings = fetchAPI.DecompressionStream['supportedFormats'];
|
501
|
+
}
|
502
|
+
else {
|
503
|
+
supportedEncodings = possibleEncodings.filter(encoding => {
|
504
|
+
// deflate-raw is not supported in Node.js >v20
|
505
|
+
if (globalThis.process?.version?.startsWith('v2') &&
|
506
|
+
fetchAPI.DecompressionStream === globalThis.DecompressionStream &&
|
507
|
+
encoding === 'deflate-raw') {
|
508
|
+
return false;
|
509
|
+
}
|
510
|
+
try {
|
511
|
+
// eslint-disable-next-line no-new
|
512
|
+
new fetchAPI.DecompressionStream(encoding);
|
513
|
+
return true;
|
514
|
+
}
|
515
|
+
catch {
|
516
|
+
return false;
|
517
|
+
}
|
518
|
+
});
|
519
|
+
}
|
520
|
+
supportedEncodingsByFetchAPI.set(fetchAPI, supportedEncodings);
|
521
|
+
}
|
522
|
+
return supportedEncodings;
|
523
|
+
}
|
524
|
+
function handleResponseDecompression(response, fetchAPI) {
|
525
|
+
const contentEncodingHeader = response?.headers.get('content-encoding');
|
526
|
+
if (!contentEncodingHeader || contentEncodingHeader === 'none') {
|
527
|
+
return response;
|
528
|
+
}
|
529
|
+
if (!response?.body) {
|
530
|
+
return response;
|
531
|
+
}
|
532
|
+
let decompressedResponse = exports.decompressedResponseMap.get(response);
|
533
|
+
if (!decompressedResponse || decompressedResponse.bodyUsed) {
|
534
|
+
let decompressedBody = response.body;
|
535
|
+
const contentEncodings = contentEncodingHeader.split(',');
|
536
|
+
if (!contentEncodings.every(encoding => getSupportedEncodings(fetchAPI).includes(encoding))) {
|
537
|
+
return new fetchAPI.Response(`Unsupported 'Content-Encoding': ${contentEncodingHeader}`, {
|
538
|
+
status: 415,
|
539
|
+
statusText: 'Unsupported Media Type',
|
540
|
+
});
|
541
|
+
}
|
542
|
+
for (const contentEncoding of contentEncodings) {
|
543
|
+
decompressedBody = decompressedBody.pipeThrough(new fetchAPI.DecompressionStream(contentEncoding));
|
544
|
+
}
|
545
|
+
decompressedResponse = new fetchAPI.Response(decompressedBody, response);
|
546
|
+
exports.decompressedResponseMap.set(response, decompressedResponse);
|
547
|
+
}
|
548
|
+
return decompressedResponse;
|
549
|
+
}
|
550
|
+
const terminateEvents = ['SIGINT', 'SIGTERM', 'exit'];
|
551
|
+
const disposableStacks = new Set();
|
552
|
+
let eventListenerRegistered = false;
|
553
|
+
function ensureEventListenerForDisposableStacks() {
|
554
|
+
if (eventListenerRegistered) {
|
555
|
+
return;
|
556
|
+
}
|
557
|
+
eventListenerRegistered = true;
|
558
|
+
for (const event of terminateEvents) {
|
559
|
+
globalThis.process.once(event, function terminateHandler() {
|
560
|
+
return Promise.allSettled([...disposableStacks].map(stack => stack.disposeAsync().catch(e => {
|
561
|
+
console.error('Error while disposing:', e);
|
562
|
+
})));
|
563
|
+
});
|
564
|
+
}
|
565
|
+
}
|
566
|
+
function ensureDisposableStackRegisteredForTerminateEvents(disposableStack) {
|
567
|
+
if (globalThis.process) {
|
568
|
+
ensureEventListenerForDisposableStacks();
|
569
|
+
if (!disposableStacks.has(disposableStack)) {
|
570
|
+
disposableStacks.add(disposableStack);
|
571
|
+
disposableStack.defer(() => {
|
572
|
+
disposableStacks.delete(disposableStack);
|
573
|
+
});
|
574
|
+
}
|
575
|
+
}
|
576
|
+
}
|
package/cjs/uwebsockets.js
CHANGED
@@ -2,26 +2,75 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.isUWSResponse = isUWSResponse;
|
4
4
|
exports.getRequestFromUWSRequest = getRequestFromUWSRequest;
|
5
|
+
exports.createWritableFromUWS = createWritableFromUWS;
|
5
6
|
exports.sendResponseToUwsOpts = sendResponseToUwsOpts;
|
7
|
+
exports.fakePromise = fakePromise;
|
8
|
+
const utils_js_1 = require("./utils.js");
|
6
9
|
function isUWSResponse(res) {
|
7
10
|
return !!res.onData;
|
8
11
|
}
|
9
12
|
function getRequestFromUWSRequest({ req, res, fetchAPI, signal }) {
|
10
|
-
let body;
|
11
13
|
const method = req.getMethod();
|
14
|
+
let duplex;
|
15
|
+
const chunks = [];
|
16
|
+
const pushFns = [
|
17
|
+
(chunk) => {
|
18
|
+
chunks.push(chunk);
|
19
|
+
},
|
20
|
+
];
|
21
|
+
const push = (chunk) => {
|
22
|
+
for (const pushFn of pushFns) {
|
23
|
+
pushFn(chunk);
|
24
|
+
}
|
25
|
+
};
|
26
|
+
let stopped = false;
|
27
|
+
const stopFns = [
|
28
|
+
() => {
|
29
|
+
stopped = true;
|
30
|
+
},
|
31
|
+
];
|
32
|
+
const stop = () => {
|
33
|
+
for (const stopFn of stopFns) {
|
34
|
+
stopFn();
|
35
|
+
}
|
36
|
+
};
|
37
|
+
res.onData(function (ab, isLast) {
|
38
|
+
push(Buffer.from(Buffer.from(ab, 0, ab.byteLength)));
|
39
|
+
if (isLast) {
|
40
|
+
stop();
|
41
|
+
}
|
42
|
+
});
|
43
|
+
let getReadableStream;
|
12
44
|
if (method !== 'get' && method !== 'head') {
|
13
|
-
|
14
|
-
const readable = body.readable;
|
45
|
+
duplex = 'half';
|
15
46
|
signal.addEventListener('abort', () => {
|
16
|
-
|
47
|
+
stop();
|
17
48
|
});
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
49
|
+
let readableStream;
|
50
|
+
getReadableStream = () => {
|
51
|
+
if (!readableStream) {
|
52
|
+
readableStream = new fetchAPI.ReadableStream({
|
53
|
+
start(controller) {
|
54
|
+
for (const chunk of chunks) {
|
55
|
+
controller.enqueue(chunk);
|
56
|
+
}
|
57
|
+
if (stopped) {
|
58
|
+
controller.close();
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
pushFns.push((chunk) => {
|
62
|
+
controller.enqueue(chunk);
|
63
|
+
});
|
64
|
+
stopFns.push(() => {
|
65
|
+
if (controller.desiredSize) {
|
66
|
+
controller.close();
|
67
|
+
}
|
68
|
+
});
|
69
|
+
},
|
70
|
+
});
|
23
71
|
}
|
24
|
-
|
72
|
+
return readableStream;
|
73
|
+
};
|
25
74
|
}
|
26
75
|
const headers = new fetchAPI.Headers();
|
27
76
|
req.forEach((key, value) => {
|
@@ -32,27 +81,97 @@ function getRequestFromUWSRequest({ req, res, fetchAPI, signal }) {
|
|
32
81
|
if (query) {
|
33
82
|
url += `?${query}`;
|
34
83
|
}
|
35
|
-
|
84
|
+
let buffer;
|
85
|
+
function getBody() {
|
86
|
+
if (!getReadableStream) {
|
87
|
+
return null;
|
88
|
+
}
|
89
|
+
if (stopped) {
|
90
|
+
return getBufferFromChunks();
|
91
|
+
}
|
92
|
+
return getReadableStream();
|
93
|
+
}
|
94
|
+
const request = new fetchAPI.Request(url, {
|
36
95
|
method,
|
37
96
|
headers,
|
38
|
-
body
|
97
|
+
get body() {
|
98
|
+
return getBody();
|
99
|
+
},
|
39
100
|
signal,
|
101
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
102
|
+
// @ts-ignore - not in the TS types yet
|
103
|
+
duplex,
|
40
104
|
});
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
if (signal.aborted) {
|
45
|
-
return;
|
105
|
+
function getBufferFromChunks() {
|
106
|
+
if (!buffer) {
|
107
|
+
buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
|
46
108
|
}
|
47
|
-
|
48
|
-
|
109
|
+
return buffer;
|
110
|
+
}
|
111
|
+
function collectBuffer() {
|
112
|
+
if (stopped) {
|
113
|
+
return fakePromise(getBufferFromChunks());
|
114
|
+
}
|
115
|
+
return new Promise((resolve, reject) => {
|
116
|
+
try {
|
117
|
+
stopFns.push(() => {
|
118
|
+
resolve(getBufferFromChunks());
|
119
|
+
});
|
120
|
+
}
|
121
|
+
catch (e) {
|
122
|
+
reject(e);
|
123
|
+
}
|
49
124
|
});
|
50
125
|
}
|
51
|
-
|
52
|
-
|
126
|
+
Object.defineProperties(request, {
|
127
|
+
body: {
|
128
|
+
get() {
|
129
|
+
return getBody();
|
130
|
+
},
|
131
|
+
configurable: true,
|
132
|
+
enumerable: true,
|
133
|
+
},
|
134
|
+
json: {
|
135
|
+
value() {
|
136
|
+
return collectBuffer()
|
137
|
+
.then(b => b.toString('utf8'))
|
138
|
+
.then(t => JSON.parse(t));
|
139
|
+
},
|
140
|
+
configurable: true,
|
141
|
+
enumerable: true,
|
142
|
+
},
|
143
|
+
text: {
|
144
|
+
value() {
|
145
|
+
return collectBuffer().then(b => b.toString('utf8'));
|
146
|
+
},
|
147
|
+
configurable: true,
|
148
|
+
enumerable: true,
|
149
|
+
},
|
150
|
+
arrayBuffer: {
|
151
|
+
value() {
|
152
|
+
return collectBuffer();
|
153
|
+
},
|
154
|
+
configurable: true,
|
155
|
+
enumerable: true,
|
156
|
+
},
|
157
|
+
});
|
158
|
+
return request;
|
159
|
+
}
|
160
|
+
function createWritableFromUWS(uwsResponse, fetchAPI) {
|
161
|
+
return new fetchAPI.WritableStream({
|
162
|
+
write(chunk) {
|
163
|
+
uwsResponse.cork(() => {
|
164
|
+
uwsResponse.write(chunk);
|
165
|
+
});
|
166
|
+
},
|
167
|
+
close() {
|
168
|
+
uwsResponse.cork(() => {
|
169
|
+
uwsResponse.end();
|
170
|
+
});
|
171
|
+
},
|
53
172
|
});
|
54
173
|
}
|
55
|
-
function sendResponseToUwsOpts(uwsResponse, fetchResponse, signal) {
|
174
|
+
function sendResponseToUwsOpts(uwsResponse, fetchResponse, signal, fetchAPI) {
|
56
175
|
if (!fetchResponse) {
|
57
176
|
uwsResponse.writeStatus('404 Not Found');
|
58
177
|
uwsResponse.end();
|
@@ -82,13 +201,59 @@ function sendResponseToUwsOpts(uwsResponse, fetchResponse, signal) {
|
|
82
201
|
if (bufferOfRes) {
|
83
202
|
uwsResponse.end(bufferOfRes);
|
84
203
|
}
|
204
|
+
else if (!fetchResponse.body) {
|
205
|
+
uwsResponse.end();
|
206
|
+
}
|
85
207
|
});
|
86
|
-
if (bufferOfRes) {
|
208
|
+
if (bufferOfRes || !fetchResponse.body) {
|
87
209
|
return;
|
88
210
|
}
|
89
|
-
|
90
|
-
|
91
|
-
|
211
|
+
signal.addEventListener('abort', () => {
|
212
|
+
if (!fetchResponse.body?.locked) {
|
213
|
+
fetchResponse.body?.cancel(signal.reason);
|
214
|
+
}
|
215
|
+
});
|
216
|
+
return fetchResponse.body
|
217
|
+
.pipeTo(createWritableFromUWS(uwsResponse, fetchAPI), {
|
218
|
+
signal,
|
219
|
+
})
|
220
|
+
.catch(err => {
|
221
|
+
if (signal.aborted) {
|
222
|
+
return;
|
223
|
+
}
|
224
|
+
throw err;
|
225
|
+
});
|
226
|
+
}
|
227
|
+
function fakePromise(value) {
|
228
|
+
if ((0, utils_js_1.isPromise)(value)) {
|
229
|
+
return value;
|
92
230
|
}
|
93
|
-
|
231
|
+
// Write a fake promise to avoid the promise constructor
|
232
|
+
// being called with `new Promise` in the browser.
|
233
|
+
return {
|
234
|
+
then(resolve) {
|
235
|
+
if (resolve) {
|
236
|
+
const callbackResult = resolve(value);
|
237
|
+
if ((0, utils_js_1.isPromise)(callbackResult)) {
|
238
|
+
return callbackResult;
|
239
|
+
}
|
240
|
+
return fakePromise(callbackResult);
|
241
|
+
}
|
242
|
+
return this;
|
243
|
+
},
|
244
|
+
catch() {
|
245
|
+
return this;
|
246
|
+
},
|
247
|
+
finally(cb) {
|
248
|
+
if (cb) {
|
249
|
+
const callbackResult = cb();
|
250
|
+
if ((0, utils_js_1.isPromise)(callbackResult)) {
|
251
|
+
return callbackResult.then(() => value);
|
252
|
+
}
|
253
|
+
return fakePromise(value);
|
254
|
+
}
|
255
|
+
return this;
|
256
|
+
},
|
257
|
+
[Symbol.toStringTag]: 'Promise',
|
258
|
+
};
|
94
259
|
}
|