rasengan
Version:
The modern React Framework
92 lines (91 loc) • 3.53 kB
JavaScript
import { createReadableStreamFromReadable, writeReadableStreamToWritable, } from './stream.js';
/**
* This function is used to create a Rasengan request from an Express request.
* Reference: https://github.com/remix-run/react-router/blob/main/packages/react-router-express/server.ts#L86
*/
export default function createRasenganRequest(req, res) {
// req.hostname doesn't include port information so grab that from
// `X-Forwarded-Host` or `Host`
let [, hostnamePort] = req.get('X-Forwarded-Host')?.split(':') ?? [];
let [, hostPort] = req.get('host')?.split(':') ?? [];
let port = hostnamePort || hostPort;
// Use req.hostname here as it respects the "trust proxy" setting
let resolvedHost = `${req.hostname}${port ? `:${port}` : ''}`;
// Use `req.originalUrl` so Remix is aware of the full path
let url = new URL(`${req.protocol}://${resolvedHost}${req.originalUrl}`);
// Abort action/loaders once we can no longer write a response
let controller = new AbortController();
// Abort action/loaders once we can no longer write a response iff we have
// not yet sent a response (i.e., `close` without `finish`)
// `finish` -> done rendering the response
// `close` -> response can no longer be written to
res.on('finish', () => (controller = null));
res.on('close', () => controller?.abort());
let init = {
method: req.method,
headers: createRasenganHeaders(req.headers),
signal: controller.signal,
};
if (req.method !== 'GET' && req.method !== 'HEAD') {
init.body = createReadableStreamFromReadable(req);
init.duplex = 'half';
}
return new Request(url.href, init);
}
/**
* This function is used to send a Rasengan response to an Express response.
* @param res
* @param nodeResponse
*/
export async function sendRasenganResponse(res, nodeResponse) {
try {
// Set status and status message
res.statusMessage = nodeResponse.statusText;
res.status(nodeResponse.status);
// Set headers
for (let [key, value] of nodeResponse.headers.entries()) {
res.append(key, value);
}
// Handle Server-Sent Events (SSE)
if (nodeResponse.headers.get('Content-Type')?.match(/text\/event-stream/i)) {
res.flushHeaders();
}
// Write the response body if available
if (nodeResponse.body) {
await writeReadableStreamToWritable(nodeResponse.body, res);
}
else {
res.end();
}
}
catch (error) {
// Log the error (optional)
console.error('Error while sending response:', error);
// Send a 500 Internal Server Error response with error details
res.status(500).send({
message: 'An error occurred while processing the response.',
error: error instanceof Error ? error.message : String(error),
});
}
}
/**
* This function is used to create a Rasengan headers from Express request headers.
* @param requestHeaders
* @returns
*/
export function createRasenganHeaders(requestHeaders) {
let headers = new Headers();
for (let [key, values] of Object.entries(requestHeaders)) {
if (values) {
if (Array.isArray(values)) {
for (const value of values) {
headers.append(key, value);
}
}
else {
headers.set(key, values);
}
}
}
return headers;
}