import express from 'express'; import { loadConfig, isDevMode, getPort } from './config.js'; import { logInfo, logError } from './logger.js'; import router from './routes.js'; import { initializeAuth } from './auth.js'; import { initializeUserAgentUpdater } from './user-agent-updater.js'; import './sls-logger.js'; // 初始化阿里云日志服务 import { sanitizeForLog } from './log-sanitizer.js'; // ============================================================================ // 全局错误处理 - 必须在应用启动前注册 // ============================================================================ let isShuttingDown = false; /** * 优雅关闭服务器 */ function gracefulShutdown(reason, exitCode = 1) { if (isShuttingDown) { return; } isShuttingDown = true; console.error(`\n${'='.repeat(80)}`); console.error(`🛑 Server shutting down: ${reason}`); console.error(`${'='.repeat(80)}\n`); // 给正在处理的请求一些时间完成 setTimeout(() => { process.exit(exitCode); }, 3000); } /** * 处理未捕获的 Promise Rejection */ process.on('unhandledRejection', (reason, promise) => { const errorInfo = { type: 'unhandledRejection', reason: reason instanceof Error ? reason.message : String(reason), stack: reason instanceof Error ? reason.stack : undefined, timestamp: new Date().toISOString() }; console.error(`\n${'='.repeat(80)}`); console.error('⚠️ Unhandled Promise Rejection'); console.error('='.repeat(80)); console.error(`Time: ${errorInfo.timestamp}`); console.error(`Reason: ${errorInfo.reason}`); if (errorInfo.stack && isDevMode()) { console.error(`Stack: ${errorInfo.stack}`); } console.error('='.repeat(80) + '\n'); logError('Unhandled Promise Rejection', sanitizeForLog(errorInfo)); // 在生产环境中,unhandledRejection 可能表示严重问题,考虑退出 // 但为了服务稳定性,这里只记录不退出 // 如需更严格的处理,可取消下面的注释: // gracefulShutdown('Unhandled Promise Rejection', 1); }); /** * 处理未捕获的异常 */ process.on('uncaughtException', (error) => { const errorInfo = { type: 'uncaughtException', message: error.message, stack: error.stack, timestamp: new Date().toISOString() }; console.error(`\n${'='.repeat(80)}`); console.error('💥 Uncaught Exception'); console.error('='.repeat(80)); console.error(`Time: ${errorInfo.timestamp}`); console.error(`Error: ${errorInfo.message}`); if (errorInfo.stack) { console.error(`Stack: ${errorInfo.stack}`); } console.error('='.repeat(80) + '\n'); logError('Uncaught Exception', sanitizeForLog(errorInfo)); // uncaughtException 后进程状态不确定,必须退出 gracefulShutdown('Uncaught Exception', 1); }); /** * 处理进程信号 */ process.on('SIGTERM', () => { logInfo('Received SIGTERM signal'); gracefulShutdown('SIGTERM', 0); }); process.on('SIGINT', () => { logInfo('Received SIGINT signal'); gracefulShutdown('SIGINT', 0); }); // ============================================================================ const app = express(); app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ extended: true, limit: '50mb' })); app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key, anthropic-version'); if (req.method === 'OPTIONS') { return res.sendStatus(200); } next(); }); app.use(router); app.get('/', (req, res) => { res.json({ name: 'droid2api', version: '1.0.0', description: 'OpenAI Compatible API Proxy', endpoints: [ 'GET /v1/models', 'POST /v1/chat/completions', 'POST /v1/responses', 'POST /v1/messages', 'POST /v1/messages/count_tokens' ] }); }); // 404 处理 - 捕获所有未匹配的路由 app.use((req, res, next) => { const errorInfo = { timestamp: new Date().toISOString(), method: req.method, url: req.originalUrl || req.url, path: req.path, query: req.query, params: req.params, body: req.body, headers: { 'content-type': req.headers['content-type'], 'user-agent': req.headers['user-agent'], 'origin': req.headers['origin'], 'referer': req.headers['referer'] }, ip: req.ip || req.connection.remoteAddress }; const safeQuery = sanitizeForLog(errorInfo.query); const safeBody = sanitizeForLog(errorInfo.body); const safeHeaders = sanitizeForLog(errorInfo.headers); const safeIp = sanitizeForLog(errorInfo.ip); console.error('\n' + '='.repeat(80)); console.error('❌ 非法请求地址'); console.error('='.repeat(80)); console.error(`时间: ${errorInfo.timestamp}`); console.error(`方法: ${errorInfo.method}`); console.error(`地址: ${errorInfo.url}`); console.error(`路径: ${errorInfo.path}`); if (Object.keys(errorInfo.query).length > 0) { console.error(`查询参数: ${JSON.stringify(safeQuery, null, 2)}`); } if (errorInfo.body && Object.keys(errorInfo.body).length > 0) { console.error(`请求体: ${JSON.stringify(safeBody, null, 2)}`); } console.error(`客户端IP: ${safeIp}`); console.error(`User-Agent: ${safeHeaders['user-agent'] || 'N/A'}`); if (safeHeaders.referer) { console.error(`来源: ${safeHeaders.referer}`); } console.error('='.repeat(80) + '\n'); logError('Invalid request path', sanitizeForLog(errorInfo)); res.status(404).json({ error: 'Not Found', message: `路径 ${req.method} ${req.path} 不存在`, timestamp: errorInfo.timestamp, availableEndpoints: [ 'GET /v1/models', 'POST /v1/chat/completions', 'POST /v1/responses', 'POST /v1/messages', 'POST /v1/messages/count_tokens' ] }); }); // 错误处理中间件 app.use((err, req, res, next) => { logError('Unhandled error', err); res.status(500).json({ error: 'Internal server error', message: isDevMode() ? err.message : undefined }); }); (async () => { try { loadConfig(); logInfo('Configuration loaded successfully'); logInfo(`Dev mode: ${isDevMode()}`); // Initialize User-Agent version updater initializeUserAgentUpdater(); // Initialize auth system (load and setup API key if needed) // This won't throw error if no auth config is found - will use client auth await initializeAuth(); const PORT = getPort(); logInfo(`Starting server on port ${PORT}...`); const server = app.listen(PORT) .on('listening', () => { logInfo(`Server running on http://localhost:${PORT}`); logInfo('Available endpoints:'); logInfo(' GET /v1/models'); logInfo(' POST /v1/chat/completions'); logInfo(' POST /v1/responses'); logInfo(' POST /v1/messages'); logInfo(' POST /v1/messages/count_tokens'); }) .on('error', (err) => { if (err.code === 'EADDRINUSE') { console.error(`\n${'='.repeat(80)}`); console.error(`ERROR: Port ${PORT} is already in use!`); console.error(''); console.error('Please choose one of the following options:'); console.error(` 1. Stop the process using port ${PORT}:`); console.error(` lsof -ti:${PORT} | xargs kill`); console.error(''); console.error(' 2. Change the port in config.json:'); console.error(' Edit config.json and modify the "port" field'); console.error(`${'='.repeat(80)}\n`); process.exit(1); } else { logError('Failed to start server', err); process.exit(1); } }); } catch (error) { logError('Failed to start server', error); process.exit(1); } })();