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.
357 lines • 13.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.uWSPipeStream = exports.uWSfiles = exports.uWSBody = exports.uWSAdaptRequestResponse = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const mime_types_1 = __importDefault(require("mime-types"));
const crypto_1 = __importDefault(require("crypto"));
const const_1 = require("../../const");
const utils_1 = require("../../utils");
const uWSAdaptRequestResponse = (uwsReq, uwsRes) => {
const headers = {};
uwsReq.forEach((key, value) => {
headers[key.toLowerCase()] = value;
});
const req = {
uWS: uwsReq,
method: String(uwsReq.getMethod()).toUpperCase(),
url: uwsReq.getUrl() + (uwsReq.getQuery() ? `?${uwsReq.getQuery()}` : ""),
headers: headers,
};
const _writeHead = (status, context) => {
const statusMessage = const_1.HTTP_STATUS_MESSAGES[status] ||
const_1.HTTP_STATUS_MESSAGES[500];
res.uWS.writeStatus(`${status} ${statusMessage}`);
res.uWS.writeHeader(Object.keys(context)[0], Object.values(context)[0]);
return res;
};
const res = {
writeHeader: (key, value) => {
if (!res.aborted) {
uwsRes.writeHeader(key, value);
}
return res;
},
setHeader: (key, value) => {
if (!res.aborted) {
uwsRes.writeHeader(key, value);
}
return res;
},
writeHead(status, context) {
res.writeHeaders = {
...res.writeHeaders,
[status]: context,
};
res.headersSent = true;
res.statusCode = status;
return res;
},
writeStatus: (status) => {
if (!res.aborted) {
res.uWS.writeStatus(status);
}
return res;
},
end: (str) => {
if (res.aborted) {
return;
}
if (str === undefined) {
return;
}
uwsRes.cork(() => {
if (!res.aborted) {
res.aborted = true;
res.writableEnded = true;
for (const h in res.writeHeaders) {
_writeHead(+h, res.writeHeaders[h]);
}
uwsRes.end(str);
return;
}
});
},
writableEnded: false,
aborted: false,
writeHeaders: Object.create(null),
headersSent: false,
statusCode: 200,
uWS: uwsRes,
};
uwsRes.onAborted(() => {
res.aborted = true;
});
return { req, res };
};
exports.uWSAdaptRequestResponse = uWSAdaptRequestResponse;
const uWSBody = (req, res) => {
return new Promise((resolve, reject) => {
let buffer = [];
res.uWS.onAborted(() => {
reject(new Error("Request aborted"));
});
res.uWS.onData(async (chunk, isLast) => {
buffer.push(Buffer.from(chunk));
if (!isLast)
return;
const payload = Buffer.concat(buffer).toString("utf-8");
const contentType = req.headers["content-type"]?.toLowerCase() ?? null;
try {
const body = await (0, utils_1.normalizeRequestBody)({ contentType, payload });
return resolve(body);
}
catch (err) {
return reject(err);
}
});
});
};
exports.uWSBody = uWSBody;
const uWSfiles = async ({ req, res, options, }) => {
const temp = options.tempFileDir;
if (!fs_1.default.existsSync(temp)) {
try {
fs_1.default.mkdirSync(temp, { recursive: true });
}
catch { }
}
const removeTemp = (fileTemp, ms) => {
const remove = () => {
try {
fs_1.default.unlinkSync(fileTemp);
}
catch (err) { }
};
setTimeout(remove, ms);
};
const contentType = req.headers["content-type"] ?? "";
const boundary = contentType.split("boundary=")[1];
if (!boundary) {
throw new Error("Invalid multipart/form-data (no boundary)");
}
const boundaryBuf = Buffer.from(`\r\n--${boundary}`);
return new Promise((resolve, reject) => {
let body = {};
let files = {};
let buffer = Buffer.alloc(0);
let currentFileStream = null;
let file = null;
let headerParsed = false;
let aborted = false;
const fail = (err) => {
if (aborted)
return;
aborted = true;
try {
currentFileStream?.destroy();
}
catch { }
try {
file?.tempFilePath && fs_1.default.unlinkSync(file.tempFilePath);
}
catch { }
try {
res.uWS.close();
}
catch { }
return reject(err);
};
res.uWS.onData((chunk, isLast) => {
if (aborted)
return;
const data = Buffer.from(new Uint8Array(chunk));
buffer = buffer.length === 0 ? data : Buffer.concat([buffer, data]);
try {
while (true) {
if (!headerParsed) {
const headerEnd = buffer.indexOf("\r\n\r\n");
if (headerEnd === -1)
break;
const header = buffer.slice(0, headerEnd).toString();
buffer = buffer.slice(headerEnd + 4);
const disposition = header.match(/name="([^"]+)"(?:; filename="([^"]+)")?/);
if (!disposition)
continue;
const fieldName = disposition[1];
const fileName = disposition[2];
if (!fileName) {
const nextBoundary = buffer.indexOf(boundaryBuf);
if (nextBoundary === -1)
break;
const value = buffer.slice(0, nextBoundary).toString().trim();
body[fieldName] = value;
buffer = buffer.slice(nextBoundary + boundaryBuf.length);
continue;
}
const contentTypeMatch = header.match(/Content-Type: ([^\r\n]+)/);
const mimetype = contentTypeMatch
? contentTypeMatch[1]
: "application/octet-stream";
const extension = mime_types_1.default.extension(mimetype) ||
path_1.default.extname(fileName).replace(".", "") ||
"bin";
const tempFilename = crypto_1.default.randomBytes(16).toString("hex");
const filePath = path_1.default.join(path_1.default.resolve(), `${temp}/${tempFilename}`);
currentFileStream = fs_1.default.createWriteStream(filePath);
file = {
name: fileName,
tempFilePath: filePath,
tempFileName: tempFilename,
mimetype: mimetype,
extension: extension,
size: 0,
sizes: {
bytes: 0,
kb: 0,
mb: 0,
gb: 0,
},
write: (to) => {
return new Promise((resolve, reject) => {
fs_1.default
.createReadStream(filePath)
.pipe(fs_1.default.createWriteStream(to))
.on("finish", () => {
return resolve(null);
})
.on("error", (err) => {
return reject(err);
});
});
},
remove: () => {
return new Promise((resolve) => setTimeout(() => {
fs_1.default.unlinkSync(filePath);
return resolve(null);
}, 100));
},
};
if (!files[fieldName])
files[fieldName] = [];
files[fieldName].push(file);
if (options.removeTempFile.remove) {
removeTemp(filePath, options.removeTempFile.ms);
}
headerParsed = true;
}
const boundaryIndex = buffer.indexOf(boundaryBuf);
if (boundaryIndex === -1) {
const safeLength = buffer.length - boundaryBuf.length;
if (safeLength > 0) {
const writeChunk = buffer.slice(0, safeLength);
currentFileStream.write(writeChunk);
file.size += writeChunk.length;
file.sizes = {
bytes: file.size,
kb: file.size / 1024,
mb: file.size / 1024 / 1024,
gb: file.size / 1024 / 1024 / 1024,
};
if (file.size > options.limit) {
return fail(new Error(`File too large (limit ${options.limit} bytes)`));
}
buffer = buffer.slice(safeLength);
}
break;
}
const filePart = buffer.slice(0, boundaryIndex);
currentFileStream.write(filePart);
file.size += filePart.length;
file.sizes = {
bytes: file.size,
kb: file.size / 1024,
mb: file.size / 1024 / 1024,
gb: file.size / 1024 / 1024 / 1024,
};
if (file.size > options.limit) {
return fail(new Error(`File too large (limit ${options.limit} bytes)`));
}
currentFileStream.end();
currentFileStream = null;
file = null;
buffer = buffer.slice(boundaryIndex + boundaryBuf.length);
headerParsed = false;
}
if (isLast && !aborted) {
if (currentFileStream)
currentFileStream.end();
return resolve({ body, files });
}
}
catch (err) {
return fail(err);
}
});
});
};
exports.uWSfiles = uWSfiles;
const uWSPipeStream = async ({ req, res, filePath }) => {
//@ts-ignore
const uwsRes = res.uWS;
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/");
//@ts-ignore
let aborted = res.aborted || false;
let stream;
let start = 0;
let end = fileSize - 1;
uwsRes.onAborted(() => {
aborted = true;
if (stream)
stream.destroy();
});
if (range && isVideo) {
const parts = range.replace(/bytes=/, "").split("-");
start = parseInt(parts[0], 10);
end = parts[1] ? parseInt(parts[1], 10) : end;
uwsRes.writeStatus("206 Partial Content");
uwsRes.writeHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`);
}
else {
uwsRes.writeStatus("200 OK");
}
const chunkSize = end - start + 1;
uwsRes.writeHeader("Content-Type", contentType);
uwsRes.writeHeader("Accept-Ranges", "bytes");
uwsRes.writeHeader("Content-Length", chunkSize.toString());
stream = fs_1.default.createReadStream(filePath, { start, end });
stream.pause();
stream.on("data", (chunk) => {
if (aborted)
return;
const ok = uwsRes.cork(() => uwsRes.write(chunk));
if (!ok)
stream.pause();
});
uwsRes.onWritable(() => {
if (aborted)
return false;
stream.resume();
return true;
});
stream.on("end", () => {
if (!aborted) {
uwsRes.cork(() => {
uwsRes.end();
});
}
});
stream.on("error", () => {
if (!aborted) {
uwsRes.cork(() => {
uwsRes.writeStatus("500 Internal Server Error").end();
});
}
});
stream.resume();
return stream;
};
exports.uWSPipeStream = uWSPipeStream;
//# sourceMappingURL=index.js.map