@whatwg-node/server
Version:
Fetch API compliant HTTP Server adapter
136 lines (135 loc) • 7.12 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useContentEncoding = useContentEncoding;
const utils_js_1 = require("../utils.js");
function useContentEncoding() {
const encodingMap = new WeakMap();
return {
onRequest({ request, setRequest, fetchAPI, endResponse }) {
if (request.body) {
const contentEncodingHeader = request.headers.get('content-encoding');
if (contentEncodingHeader && contentEncodingHeader !== 'none') {
const contentEncodings = contentEncodingHeader?.split(',');
if (!contentEncodings.every(encoding => (0, utils_js_1.getSupportedEncodings)(fetchAPI).includes(encoding))) {
endResponse(new fetchAPI.Response(`Unsupported 'Content-Encoding': ${contentEncodingHeader}`, {
status: 415,
statusText: 'Unsupported Media Type',
}));
return;
}
let newBody = request.body;
for (const contentEncoding of contentEncodings) {
newBody = newBody.pipeThrough(new fetchAPI.DecompressionStream(contentEncoding));
}
request = new fetchAPI.Request(request.url, {
body: newBody,
cache: request.cache,
credentials: request.credentials,
headers: request.headers,
integrity: request.integrity,
keepalive: request.keepalive,
method: request.method,
mode: request.mode,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
signal: request.signal,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - not in the TS types yet
duplex: 'half',
});
setRequest(request);
}
}
const acceptEncoding = request.headers.get('accept-encoding');
if (acceptEncoding) {
encodingMap.set(request, acceptEncoding.split(','));
}
},
onResponse({ request, response, setResponse, fetchAPI, serverContext }) {
// Hack for avoiding to create whatwg-node to create a readable stream until it's needed
if (response['bodyInit'] || response.body) {
const encodings = encodingMap.get(request);
if (encodings) {
const supportedEncoding = encodings.find(encoding => (0, utils_js_1.getSupportedEncodings)(fetchAPI).includes(encoding));
if (supportedEncoding) {
const compressionStream = new fetchAPI.CompressionStream(supportedEncoding);
// To calculate final content-length
const contentLength = response.headers.get('content-length');
if (contentLength) {
const bufOfRes = response._buffer;
if (bufOfRes) {
const writer = compressionStream.writable.getWriter();
const write$ = writer.write(bufOfRes);
serverContext.waitUntil?.(write$);
const close$ = writer.close();
serverContext.waitUntil?.(close$);
const uint8Arrays$ = (0, utils_js_1.isReadable)(compressionStream.readable['readable'])
? collectReadableValues(compressionStream.readable['readable'])
: (0, utils_js_1.isAsyncIterable)(compressionStream.readable)
? collectAsyncIterableValues(compressionStream.readable)
: collectReadableStreamValues(compressionStream.readable);
return uint8Arrays$.then(uint8Arrays => {
const chunks = uint8Arrays.flatMap(uint8Array => [...uint8Array]);
const uint8Array = new Uint8Array(chunks);
const newHeaders = new fetchAPI.Headers(response.headers);
newHeaders.set('content-encoding', supportedEncoding);
newHeaders.set('content-length', uint8Array.byteLength.toString());
const compressedResponse = new fetchAPI.Response(uint8Array, {
...response,
headers: newHeaders,
});
utils_js_1.decompressedResponseMap.set(compressedResponse, response);
setResponse(compressedResponse);
const close$ = compressionStream.writable.close();
serverContext.waitUntil?.(close$);
});
}
}
const newHeaders = new fetchAPI.Headers(response.headers);
newHeaders.set('content-encoding', supportedEncoding);
newHeaders.delete('content-length');
const compressedBody = response.body.pipeThrough(compressionStream);
const compressedResponse = new fetchAPI.Response(compressedBody, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
utils_js_1.decompressedResponseMap.set(compressedResponse, response);
setResponse(compressedResponse);
}
}
}
},
};
}
function collectReadableValues(readable) {
const values = [];
readable.on('data', value => values.push(value));
return new Promise((resolve, reject) => {
readable.once('end', () => resolve(values));
readable.once('error', reject);
});
}
async function collectAsyncIterableValues(asyncIterable) {
const values = [];
for await (const value of asyncIterable) {
values.push(value);
}
return values;
}
async function collectReadableStreamValues(readableStream) {
const reader = readableStream.getReader();
const values = [];
while (true) {
const { done, value } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
else if (value) {
values.push(value);
}
}
return values;
}