最近想自己搭建一个云函数服务器,大概就是可以自己编写一些云函数,实现动态加载这样子,理论是这样的,打算启动一个Node进程,搭建一个HTTP服务,固定路径/api/function ,参数:
java
{ functionName:"文件名",methodName:'方法名称', params:{'参数1'} }
接收到请求的时候,根据functionName 去拼接文件名,并用import 导入成一个对象,最后调用对应的方法和参数,在这里我还加了一个缓存,避免短时间调用时不走重复加载,代码如下:
javascript
// 导入Node.js的http模块
import http from 'http';
import url from 'url';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
// 获取当前文件的目录路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 创建HTTP服务器对象
const funServer = {
server: null,
port: 3000,
routes: {}, // 存储路由
functionsDir: path.join(__dirname, 'functions'), // 云函数目录
functionCache: {}, // 云函数缓存
cacheTimeout: 60000, // 缓存过期时间(毫秒)
// 初始化服务器
init() {
this.server = http.createServer(this.handleRequest.bind(this));
this.setupRoutes();
this.initFunctionsDir();
this.startCacheCleanup();
},
// 启动缓存清理定时器
startCacheCleanup() {
// 每60秒检查一次缓存,避免太频繁清理
setInterval(() => {
this.cleanupCache();
}, 60000);
console.log('缓存清理定时器已启动,每60秒运行一次');
},
// 清理过期缓存
cleanupCache() {
const now = Date.now();
const cacheKeys = Object.keys(this.functionCache);
let clearedCount = 0;
console.log(`开始缓存清理: 当前缓存数量 ${cacheKeys.length}`);
cacheKeys.forEach(key => {
const cacheItem = this.functionCache[key];
if (cacheItem) {
// 基于最后访问时间进行过期检查
if (now - cacheItem.lastAccessTime > this.cacheTimeout) {
delete this.functionCache[key];
console.log(`缓存已过期并清除: ${key}, 最后访问时间: ${new Date(cacheItem.lastAccessTime).toISOString()}, 当前时间: ${new Date(now).toISOString()}, 超过${this.cacheTimeout/1000}秒未访问`);
clearedCount++;
}
}
});
console.log(`缓存清理完成: 清理了 ${clearedCount} 个过期缓存,剩余缓存数量 ${Object.keys(this.functionCache).length}`);
},
// 从缓存获取模块
getCachedModule(modulePath) {
const cacheItem = this.functionCache[modulePath];
if (cacheItem) {
const now = Date.now();
console.log(`检查缓存: ${modulePath}, 最后访问时间: ${new Date(cacheItem.lastAccessTime).toISOString()}, 当前时间: ${new Date(now).toISOString()}, 间隔: ${now - cacheItem.lastAccessTime}ms`);
// 检查是否过期(基于最后访问时间)
if (now - cacheItem.lastAccessTime <= this.cacheTimeout) {
// 更新最后访问时间
cacheItem.lastAccessTime = now;
console.log(`使用缓存: ${modulePath}, 已更新最后访问时间`);
return cacheItem.module;
}
// 如果过期了,删除缓存
delete this.functionCache[modulePath];
console.log(`缓存已过期: ${modulePath}, 最后访问时间: ${new Date(cacheItem.lastAccessTime).toISOString()}, 超过${this.cacheTimeout/1000}秒未访问`);
}
return null;
},
// 缓存模块
cacheModule(modulePath, module) {
const now = Date.now();
this.functionCache[modulePath] = {
module,
timestamp: now, // 首次创建时间
lastAccessTime: now // 最后访问时间
};
console.log(`模块已缓存: ${modulePath}, 创建时间: ${new Date(now).toISOString()}`);
},
// 清除指定函数的缓存
clearFunctionCache(functionName) {
const modulePath = `file://${path.join(this.functionsDir, `${functionName}.js`)}`;
if (this.functionCache[modulePath]) {
delete this.functionCache[modulePath];
console.log(`清除缓存: ${modulePath}`);
return true;
}
return false;
},
// 清除所有缓存
clearAllCache() {
const cacheCount = Object.keys(this.functionCache).length;
this.functionCache = {};
console.log(`已清除所有缓存,共${cacheCount}个模块`);
return cacheCount;
},
// 设置路由
setupRoutes() {
// 根路径
this.routes['GET /'] = this.handleRoot;
// API路径
this.routes['GET /api/status'] = this.handleStatus;
this.routes['GET /api/data'] = this.handleData;
// 云函数执行路径
this.routes['POST /api/function'] = this.handleFunctionExecution;
// 云函数重新加载路径
this.routes['POST /api/reload-function'] = this.handleReloadFunction;
// 清除所有缓存路径
this.routes['POST /api/clear-cache'] = this.handleClearCache;
// 404处理
this.routes['notFound'] = this.handleNotFound;
},
// 初始化云函数目录
initFunctionsDir() {
if (!fs.existsSync(this.functionsDir)) {
fs.mkdirSync(this.functionsDir, { recursive: true });
console.log(`创建云函数目录: ${this.functionsDir}`);
}
},
// 启动服务器
start() {
this.server.listen(this.port, () => {
console.log(`服务器已启动,监听端口 ${this.port}`);
console.log(`访问地址: http://localhost:${this.port}`);
console.log(`API状态: http://localhost:${this.port}/api/status`);
console.log(`API数据: http://localhost:${this.port}/api/data`);
console.log(`云函数执行: http://localhost:${this.port}/api/function (POST)`);
console.log(`云函数重载: http://localhost:${this.port}/api/reload-function (POST)`);
console.log(`清除缓存: http://localhost:${this.port}/api/clear-cache (POST)`);
console.log(`云函数目录: ${this.functionsDir}`);
console.log(`云函数缓存过期时间: ${this.cacheTimeout / 1000}秒(基于最后访问时间)`);
});
},
// 处理请求的方法
handleRequest(req, res) {
try {
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
const method = req.method;
console.log(`收到请求: ${method} ${path}`);
// 构建路由键
const routeKey = `${method} ${path}`;
// 查找对应的处理函数
const handler = this.routes[routeKey] || this.routes['notFound'];
// 对于需要解析请求体的POST请求
if (method === 'POST' && (path === '/api/function' || path === '/api/reload-function' || path === '/api/clear-cache')) {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
try {
const bodyData = body ? JSON.parse(body) : {};
handler.call(this, req, res, parsedUrl, bodyData);
} catch (parseError) {
this.sendJSON(res, { error: '无效的JSON格式' }, 400);
}
});
} else {
// 传递请求和响应对象给处理函数
handler.call(this, req, res, parsedUrl);
}
} catch (error) {
this.handleError(req, res, error);
}
},
// 根路径处理
handleRoot(req, res) {
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Node.js HTTP服务器 - 云函数平台</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
.api-section { margin-top: 30px; }
h2 { color: #555; }
.api-links { margin-top: 20px; }
.api-links a { margin-right: 20px; color: #0066cc; text-decoration: none; }
.api-links a:hover { text-decoration: underline; }
.code-block { background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0; font-family: monospace; }
.note { background: #e8f4f8; padding: 10px; border-left: 4px solid #0066cc; }
</style>
</head>
<body>
<h1>Hello World!</h1>
<p>这是一个使用Node.js创建的HTTP服务器,支持云函数执行功能。</p>
<div class="api-section">
<h2>API端点</h2>
<div class="api-links">
<a href="/api/status">API状态</a>
<a href="/api/data">API数据</a>
</div>
</div>
<div class="api-section">
<h2>云函数执行</h2>
<p>使用POST请求到 <code>/api/function</code> 来执行云函数</p>
<div class="code-block">
<strong>请求格式:</strong><br>
POST /api/function<br>
Content-Type: application/json<br>
<br>
{
"functionName": "函数文件名",
"methodName": "方法名",
"params": {}
}
</div>
<div class="note">
<strong>注意:</strong> 云函数文件需要放在 <code>functions</code> 目录下,并且导出相应的方法。
</div>
</div>
</body>
</html>
`;
res.end(html);
},
// API状态处理
handleStatus(req, res) {
this.sendJSON(res, {
status: 'ok',
message: '服务器运行正常',
timestamp: new Date().toISOString()
});
},
// API数据处理
handleData(req, res) {
this.sendJSON(res, {
data: [
{ id: 1, name: '项目1', value: Math.random() * 100 },
{ id: 2, name: '项目2', value: Math.random() * 100 },
{ id: 3, name: '项目3', value: Math.random() * 100 }
],
count: 3,
timestamp: new Date().toISOString()
});
},
// 404处理
handleNotFound(req, res) {
res.writeHead(404, {'Content-Type': 'text/plain; charset=utf-8'});
res.end('404 - 页面未找到');
},
// 错误处理
handleError(req, res, error) {
console.error('服务器错误:', error);
res.writeHead(500, {'Content-Type': 'text/plain; charset=utf-8'});
res.end('500 - 服务器内部错误');
},
// 处理云函数执行
async handleFunctionExecution(req, res, parsedUrl, bodyData) {
try {
const { functionName, methodName, params = {} } = bodyData;
// 参数验证
if (!functionName || !methodName) {
return this.sendJSON(res, { error: '缺少必要参数: functionName 和 methodName' }, 400);
}
// 安全检查 - 防止路径遍历攻击
if (functionName.includes('../') || functionName.includes('..\\')) {
return this.sendJSON(res, { error: '不允许的函数名' }, 403);
}
const functionPath = path.join(this.functionsDir, `${functionName}.js`);
// 检查文件是否存在
if (!fs.existsSync(functionPath)) {
return this.sendJSON(res, { error: `函数文件不存在: ${functionName}.js` }, 404);
}
console.log(`执行云函数: ${functionName}.${methodName}`);
// 动态导入函数模块
const modulePath = `file://${functionPath}`;
let module;
let fromCache = false;
// 先尝试从缓存获取模块
module = this.getCachedModule(modulePath);
if (module) {
fromCache = true;
console.log(`从缓存加载模块: ${functionName}`);
} else {
// 在ESM中,我们不使用require.cache,而是使用时间戳来强制重新加载
console.log(`首次加载模块: ${functionName}`);
module = await import(modulePath + '?' + Date.now()); // 添加时间戳确保重新加载
this.cacheModule(modulePath, module);
}
// 检查方法是否存在
if (typeof module[methodName] !== 'function') {
return this.sendJSON(res, { error: `方法不存在: ${methodName}` }, 404);
}
// 执行函数并获取结果
const startTime = Date.now();
const result = await module[methodName](params);
const executionTime = Date.now() - startTime;
// 返回执行结果
this.sendJSON(res, {
success: true,
result: result,
executionTime: `${executionTime}ms`,
functionName: functionName,
methodName: methodName,
fromCache: fromCache // 标记是否来自缓存
});
} catch (error) {
console.error('云函数执行错误:', error);
this.sendJSON(res, {
success: false,
error: error.message || '云函数执行失败',
stack: error.stack
}, 500);
}
},
// 处理云函数重新加载
handleReloadFunction(req, res, parsedUrl, bodyData) {
try {
const { functionName, sourceId } = bodyData;
if (!functionName) {
return this.sendJSON(res, { error: '缺少必要参数: functionName' }, 400);
}
// 安全检查 - 防止路径遍历攻击
if (functionName.includes('../') || functionName.includes('..\\')) {
return this.sendJSON(res, { error: '不允许的函数名' }, 403);
}
// 检查函数是否存在于任何文件源
const functionFilePath = `${functionName}.js`;
const existsInSource = async () => {
// 1. 检查分布式文件源
try {
if (sourceId) {
// 从指定文件源检查
const fileResult = await fileSourceManager.getFileFromSource(sourceId, functionFilePath);
return !!fileResult;
} else {
// 检查任何可用文件源
const fileResult = await fileSourceManager.findFile(functionFilePath);
return !!fileResult;
}
} catch (error) {
console.error('检查文件源时出错:', error);
}
// 2. 检查本地文件系统作为后备
const localFunctionPath = path.join(this.functionsDir, `${functionName}.js`);
return fs.existsSync(localFunctionPath);
};
existsInSource().then(exists => {
if (!exists) {
return this.sendJSON(res, { error: `函数文件不存在: ${functionName}.js (未在任何文件源中找到)` }, 404);
}
// 清除缓存
const cleared = this.clearFunctionCache(functionName);
this.sendJSON(res, {
success: true,
message: cleared ? `已重新加载函数: ${functionName}` : `函数不在缓存中,但已准备好重新加载: ${functionName}`,
functionName: functionName,
sourceId: sourceId || 'all'
});
}).catch(error => {
console.error('检查文件是否存在时出错:', error);
return this.sendJSON(res, { error: '检查函数文件时出错' }, 500);
});
} catch (error) {
console.error('重新加载函数错误:', error);
this.sendJSON(res, {
success: false,
error: error.message || '重新加载函数失败'
}, 500);
}
},
// 从字符串动态导入模块
async importModuleFromString(code, functionName) {
// 创建临时URL以导入字符串作为模块
const moduleId = `function_${functionName}_${Date.now()}`;
// 在Node.js中,我们需要使用动态import和eval的组合
// 为了安全考虑,我们需要在沙箱环境中执行代码
try {
// 将代码转换为ES模块格式
const wrappedCode = `
// 函数包装器
${code}
// 导出所有模块导出
export default module.exports || {};
`;
// 使用Module.createRequire创建一个独立的模块上下文
const { Module } = await import('module');
const require = Module.createRequire(import.meta.url);
// 创建一个临时模块对象
const tempModule = {
exports: {}
};
// 使用函数构造器执行代码,提供安全的上下文
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
const executeCode = new AsyncFunction('module', 'exports', 'require', '__dirname', '__filename', wrappedCode);
// 执行代码
await executeCode(tempModule, tempModule.exports, require, this.functionsDir, path.join(this.functionsDir, `${functionName}.js`));
// 返回模块导出
return tempModule.exports;
} catch (error) {
console.error(`动态导入模块失败: ${functionName}`, error);
throw error;
}
},
// 处理清除所有缓存
handleClearCache(req, res) {
try {
const clearedCount = this.clearAllCache();
this.sendJSON(res, {
success: true,
message: `已清除所有缓存`,
clearedCount: clearedCount
});
} catch (error) {
console.error('清除缓存错误:', error);
this.sendJSON(res, {
success: false,
error: error.message || '清除缓存失败'
}, 500);
}
},
// 发送JSON响应
sendJSON(res, data, statusCode = 200) {
res.writeHead(statusCode, {'Content-Type': 'application/json; charset=utf-8'});
res.end(JSON.stringify(data, null, 2));
}
};
// 启动服务器
funServer.init();
funServer.start();

完美实现,后续还可以接入微服务的逻辑实现分布式的云函数加载,调用