UNPKG

f2e-server3

Version:

f2e-server 3.0

269 lines (268 loc) 10.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createResponseHelper = exports.commonWriteHeaders = exports.etag = exports.getHttpHeaders = exports.getIpAddress = void 0; const node_crypto_1 = require("node:crypto"); const _ = __importStar(require("./misc")); const zlib = __importStar(require("node:zlib")); const logger_1 = __importDefault(require("./logger")); const server_engine_1 = require("../server-engine"); const engine_1 = require("./engine"); const node_http_1 = require("node:http"); const getIpAddress = (resp) => { const bf1 = resp.getProxiedRemoteAddressAsText(); if (bf1.byteLength > 0) { return Buffer.from(bf1).toString('utf-8'); } const bf2 = resp.getRemoteAddressAsText(); if (bf2.byteLength > 0) { return Buffer.from(bf2).toString('utf-8'); } return ''; }; exports.getIpAddress = getIpAddress; const getHttpHeaders = (req) => { let headers = {}; if (req instanceof node_http_1.IncomingMessage) { if (req.headers) { headers = req.headers; } else { for (let i = 0; i < req.rawHeaders.length; i += 2) { headers[req.rawHeaders[i].trim().toLowerCase()] = req.rawHeaders[i + 1]; } } } else if (req.forEach) { req.forEach((key, value) => { headers[key.trim().toLowerCase()] = (value || '').toString().trim(); }); } return headers; }; exports.getHttpHeaders = getHttpHeaders; const gzipSync = server_engine_1.ENGINE_TYPE === 'bun' ? Bun.gzipSync : zlib.gzipSync; const etag = (entity) => { if (entity.length === 0) { return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'; } const hash = (0, node_crypto_1.createHash)('sha1') .update(entity) .digest('base64') .substring(0, 27); const len = Buffer.byteLength(entity); return '"' + len.toString(16) + '-' + hash + '"'; }; exports.etag = etag; const gzipStore = new Map(); const commonWriteHeaders = (resp, headers = {}) => { Object.assign(headers, { 'X-Powered-By': engine_1.VERSION, }); for (const key in headers) { if (Object.prototype.hasOwnProperty.call(headers, key)) { const value = headers[key]; if (typeof value != 'undefined') { resp.writeHeader(key, value.toString()); } } } }; exports.commonWriteHeaders = commonWriteHeaders; const createResponseHelper = (conf) => { const { gzip, gzip_filter, range_size, page_404, page_50x, page_dir, cache_filter, } = conf; const handleNotFound = (resp, pathname) => { const body = _.renderHTML(page_404, { title: 'Page Not Found!', pathname }); resp.cork(() => { resp.writeStatus('404 Not Found'); (0, exports.commonWriteHeaders)(resp, { 'Content-Type': 'text/html; charset=utf-8' }); resp.end(body); }); }; const handleError = (resp, error) => { const error_body = _.renderHTML(page_50x, { error, title: 'Server Error!' }); logger_1.default.error(error); resp.cork(() => { resp.writeStatus('500 Internal Server Error'); (0, exports.commonWriteHeaders)(resp, { 'Content-Type': 'text/html; charset=utf-8' }); resp.end(error_body); }); }; const handleRedirect = (resp, location) => { resp.cork(() => { resp.writeStatus('302 Found'); (0, exports.commonWriteHeaders)(resp, { location, }); resp.end(); }); }; const handleSuccess = (ctx, pathname, data) => { const { resp, headers = {}, responseHeaders = {} } = ctx; const tag = headers['if-none-match']; const newTag = data && (0, exports.etag)(data); const txt = _.isText(pathname); const gz = txt && gzip && gzip_filter(pathname, data?.length); const type = _.getMimeType(pathname) + (txt ? '; charset=utf-8' : ''); const range = headers['range']?.toString(); if (tag && data && tag === newTag) { resp.cork(() => { resp.writeStatus("304 Not Modified"); (0, exports.commonWriteHeaders)(resp, responseHeaders); resp.endWithoutBody(); }); return; } if (range && data instanceof Buffer) { let [start = 0, end = 0] = range.replace(/[^\-\d]+/g, '').split('-').map(Number); end = end || (start + range_size); const d = Uint8Array.prototype.slice.call(data, start, end); end = Math.min(end, start + d.length); resp.cork(() => { resp.writeStatus('206 Partial Content'); (0, exports.commonWriteHeaders)(resp, { 'Content-Type': type, 'Content-Range': `bytes ${start}-${end - 1}/${data.length}`, 'Content-Length': d.length, 'Accept-Ranges': 'bytes', ...responseHeaders, }); resp.end(d); }); return; } resp.cork(() => { resp.writeStatus('200 OK'); const headers = { 'Content-Type': type, 'Content-Encoding': gz ? 'gzip' : 'utf-8', 'Etag': newTag, ...responseHeaders, }; if (cache_filter(pathname, data?.length)) { headers['Cache-Control'] = 'public, max-age=3600'; headers['Last-Modified'] = new Date().toUTCString(); } (0, exports.commonWriteHeaders)(resp, headers); if (gz) { const temp = gzipStore.get(pathname); if (temp && temp.etag === newTag) { resp.end(temp.data); } else { const res = gzipSync(data); gzipStore.set(pathname, { data: res, etag: newTag }); resp.end(res); } } else { resp.end(data); } }); }; /** * 处理目录响应 * @param resp 响应对象 * @param pathname 当前路径 * @param obj 当前路径映射内存对象 */ const handleDirectory = (resp, pathname, obj) => { if (page_dir === false) { return handleNotFound(resp, pathname); } const files = []; if (_.isPlainObject(obj)) { for (let key in obj) { const isDir = _.isPlainObject(obj[key]); files.push({ name: key, path: _.pathname_arr(pathname).concat(key).join('/'), isDir, size: isDir ? 0 : (obj[key] && obj[key].length) }); } } const dir_body = _.renderHTML(page_dir, { title: '/' + pathname, pathname: _.pathname_dirname(pathname), files }); resp.cork(() => { resp.writeStatus('200 OK'); (0, exports.commonWriteHeaders)(resp, { 'Content-Type': 'text/html; charset=utf-8' }); resp.end(dir_body); }); }; const handleSSE = (ctx, item, body) => { const { resp } = ctx; const { interval = 1000, interval_beat = 30000, default_content = '', } = item; resp.cork(() => { resp.writeStatus('200 OK'); (0, exports.commonWriteHeaders)(resp, { 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Content-Type': 'text/event-stream', }); }); let interval1; const heartBeat = function heartBeat() { resp.cork(() => { resp.write(`data:${default_content}\n\n`); }); if (interval_beat) { interval1 = setTimeout(heartBeat, interval_beat); } }; let interval2; const loop = async function loop() { try { const res = await item.handler(body, ctx); if (res) { resp.cork(() => { resp.write(`data:${JSON.stringify(res)}\n\n`); }); } } catch (e) { logger_1.default.error('SSE LOOP:', e); } if (interval) { interval2 = setTimeout(loop, interval); } }; resp.onAborted(() => { clearTimeout(interval1); clearTimeout(interval2); }); loop(); heartBeat(); return false; }; return { handleSuccess, handleError, handleNotFound, handleDirectory, handleSSE, handleRedirect }; }; exports.createResponseHelper = createResponseHelper;