自建node云函数服务器

最近想自己搭建一个云函数服务器,大概就是可以自己编写一些云函数,实现动态加载这样子,理论是这样的,打算启动一个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();
  

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

相关推荐
TangDuoduo00053 小时前
【IO模型与并发服务器】
运维·服务器·网络·tcp/ip
FOREVER-Q3 小时前
Windows 下 Docker Desktop 快速入门与镜像管理
运维·服务器·windows·docker·容器
地球没有花3 小时前
gitlab cicd首次操作
运维·git·ci/cd·gitlab
武子康4 小时前
Java-172 Neo4j 访问方式实战:嵌入式 vs 服务器(含 Java 示例与踩坑)
java·服务器·数据库·sql·spring·nosql·neo4j
adnyting4 小时前
【Linux日新月异(五)】CentOS 7防火墙深度解析:firewalld全面指南
linux·运维·centos
IT瑞先生4 小时前
Docker容器使用手册——入门篇(上)
运维·docker·容器
CS_浮鱼5 小时前
【Linux】进程概念
linux·运维·服务器
青柚~6 小时前
【鲲鹏服务器麒麟系统arm架构部署docker】
服务器·arm开发·docker·架构
人工智能训练7 小时前
Ubuntu中如何进入root用户
linux·运维·服务器·人工智能·ubuntu·ai编程·root