UNPKG

@modern-js/server-core

Version:

A Progressive React Framework for modern web development.

170 lines (169 loc) 4.73 kB
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 === "ABORT_ERR" || 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 };