UNPKG

tspace-spear

Version:

tspace-spear is a lightweight, high-performance API framework for Node.js that leverages the native HTTP server and supports uWebSockets.js (C++) for maximum speed and efficiency.

137 lines 5.28 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.pipeStream = exports.normalizeRequestBody = void 0; const const_1 = require("../const"); const uWS_1 = require("../server/uWS"); const querystring_1 = __importDefault(require("querystring")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const mime_types_1 = __importDefault(require("mime-types")); const xml2js_1 = __importDefault(require("xml2js")); const crypto_1 = __importDefault(require("crypto")); const normalizeRequestBody = async ({ contentType, payload, }) => { if (contentType == null || payload == null || payload === "") { return {}; } if (contentType.includes("x-www-form-urlencoded")) { return querystring_1.default.parse(payload); } if (contentType.includes("application/json")) { try { return JSON.parse(payload); } catch (err) { throw new Error("Invalid JSON format in request body."); } } if (contentType.includes("application/xml") || contentType.includes("text/xml")) { try { const result = await xml2js_1.default.parseStringPromise(payload, { explicitArray: false, }); return result; } catch (err) { throw new Error("Invalid XML format in request body."); } } if (contentType.includes("text/plain") || contentType.includes("text/javascript") || contentType.includes("application/javascript") || contentType.includes("application/x-javascript")) { return { contentType, text: payload }; } return {}; }; exports.normalizeRequestBody = normalizeRequestBody; const pipeStream = async ({ req, res, filePath, isUwebSocket, }) => { if (!fs_1.default.existsSync(filePath)) { return res .writeHead(404, const_1.HEADER_CONTENT_TYPES["text"]) .end(`File not found: ${path_1.default.basename(filePath)}`); } if (isUwebSocket) { return (0, uWS_1.uWSPipeStream)({ req, res, filePath }); } const stat = fs_1.default.statSync(filePath); const fileSize = stat.size; const range = req.headers["range"] ?? null; const contentType = mime_types_1.default.lookup(filePath) || "application/octet-stream"; const isVideo = contentType.startsWith("video/"); const writeHead = (header, code = 200) => { const extension = filePath.split(".").pop(); const previews = Object.values({ video: [ "mp4", "webm", "ogg", "ogv", "avi", "mov", "mkv", "flv", "f4v", "wmv", "ts", "mpeg", ], audio: ["wav", "mp3"], document: ["pdf"], image: ["png", "jpeg", "jpg", "gif", "webp", "svg", "ico"], }).flat(); if (previews.some((p) => extension?.toLocaleLowerCase().includes(p))) { res.writeHead(code, header); return; } res.setHeader("Content-Disposition", `attachment; filename=${+new Date()}.${extension}`); res.setHeader("Content-Type", "application/octet-stream"); }; const maxAge = 1000 * 60 * 60 * 24 * 7; const etag = crypto_1.default.createHash("md5").update(`${stat.size}-${stat.mtimeMs}`).digest("hex"); const baseHeader = { "Connection": "keep-alive", "Keep-Alive": "timeout=60, max=1000", "Cache-Control": `public, max-age=${maxAge}, immutable`, "Strict-transport-security": `max-age=${maxAge}; includeSubDomains`, "ETag": `"${etag}"`, "Date": new Date(stat.birthtimeMs).toUTCString(), "Last-modified": new Date(stat.birthtimeMs).toUTCString(), "Vary": "Origin, Accept-Encoding", "Accept-Ranges": "bytes", "Content-Length": fileSize, "Content-Type": contentType, "X-Content-type-options": "nosniff", "X-Xss-protection": "1; mode=block", }; if (!isVideo || range == null) { const header = { ...baseHeader, "Content-Length": fileSize, "Content-Type": contentType, }; const stream = fs_1.default.createReadStream(filePath); writeHead(header); stream.on("error", () => res.end()); return stream.pipe(res); } const parts = range.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1; const chunksize = end - start + 1; const stream = fs_1.default.createReadStream(filePath, { start, end }); const header = { ...baseHeader, "Content-Range": `bytes ${start}-${end}/${fileSize}`, "Content-Length": chunksize, "Accept-Ranges": "bytes" }; writeHead(header, 206); stream.on("error", () => res.end()); return stream.pipe(res); }; exports.pipeStream = pipeStream; //# sourceMappingURL=index.js.map