f2e-server3
Version:
f2e-server 3.0
269 lines (268 loc) • 10.1 kB
JavaScript
;
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;