@modern-js/server-core
Version:
A Progressive React Framework for modern web development.
170 lines (169 loc) • 4.71 kB
JavaScript
import { ServerResponse } from "node:http";
import { Readable, Writable } from "node:stream";
import cloneable from "cloneable-readable";
import { isResFinalized } from "./helper";
const createWebRequest = (req, res, body) => {
const headerRecord = [];
for (const [key, value] of Object.entries(req.headers)) {
if (key.startsWith(":")) {
continue;
}
if (Array.isArray(value)) {
for (const item of value) {
if (item !== void 0) {
headerRecord.push([
key,
item
]);
}
}
} else if (value !== void 0) {
if (typeof value === "string") {
headerRecord.push([
key,
value
]);
}
}
}
const { method } = req;
const controller = new AbortController();
const init = {
method,
headers: headerRecord,
signal: controller.signal
};
res.on("close", () => controller.abort("res closed"));
const url = `http://${req.headers.host}${req.url}`;
const needsRequestBody = body || !(method === "GET" || method === "HEAD");
const cloneableReq = needsRequestBody ? cloneable(req) : null;
if (needsRequestBody) {
if (body) {
init.body = body;
} else {
const stream = cloneableReq.clone();
init.body = Readable.toWeb(stream);
}
init.duplex = "half";
}
const originalRequest = new Request(url, init);
if (needsRequestBody) {
const interceptedMethods = [
"json",
"text",
"blob",
"arrayBuffer",
"formData"
];
return new Proxy(originalRequest, {
get(target, prop) {
if (interceptedMethods.includes(prop)) {
return (...args) => {
cloneableReq.resume();
return target[prop].call(target, ...args);
};
}
const value = target[prop];
if (prop === "body") {
cloneableReq.resume();
return value;
}
if (typeof value === "function") {
return (...args) => value.apply(target, args);
}
return value;
}
});
}
return originalRequest;
};
const sendResponse = async (response, res) => {
var _response_headers_get;
res.statusMessage = response.statusText;
res.statusCode = response.status;
const cookies = [];
for (const [key, value] of response.headers.entries()) {
if (key === "set-cookie") {
cookies.push(value);
} else {
res.setHeader(key, value);
}
}
if (cookies.length > 0) {
res.setHeader("set-cookie", cookies);
}
if (((_response_headers_get = response.headers.get("Content-Type")) === null || _response_headers_get === void 0 ? void 0 : _response_headers_get.match(/text\/event-stream/i)) && res instanceof ServerResponse) {
res.flushHeaders();
}
if (response.body) {
const writable = Writable.toWeb(res);
await response.body.pipeTo(writable);
} else {
res.end();
}
};
const handleResponseError = (e, res) => {
const err = e instanceof Error ? e : new Error("unknown error", {
cause: e
});
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
console.info("The user aborted a request.");
} else {
console.error(e);
if (!res.headersSent) {
res.writeHead(500, {
"Content-Type": "text/plain"
});
}
res.end(`Error: ${err.message}`);
res.destroy(err);
}
};
const getRequestListener = (handler) => {
return async (req, res) => {
try {
const request = createWebRequest(req, res);
const response = await handler(request, {
node: {
req,
res
}
});
if (!response.res && !isResFinalized(res)) {
await sendResponse(response, res);
}
} catch (error) {
return handleResponseError(error, res);
}
};
};
const createNodeServer = async (requestHandler, httpsOptions, http2) => {
const requestListener = getRequestListener(requestHandler);
let nodeServer;
if (httpsOptions) {
if (http2) {
const { createSecureServer } = await import("node:http2");
nodeServer = createSecureServer({
allowHTTP1: true,
maxSessionMemory: 1024,
...httpsOptions
}, (req, res) => {
return requestListener(req, res);
});
} else {
const { createServer } = await import("node:https");
nodeServer = createServer(httpsOptions, requestListener);
}
} else {
const { createServer } = await import("node:http");
nodeServer = createServer(requestListener);
}
nodeServer.getRequestListener = () => requestListener;
nodeServer.getRequestHandler = () => requestHandler;
return nodeServer;
};
export {
createNodeServer,
createWebRequest,
sendResponse
};