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
JavaScript
;
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