const http = require('http'); const https = require('https'); const url = require('url'); const querystring = require('querystring'); const fs = require('fs'); const pathModule = require('path'); const crypto = require('crypto'); const CACHE_DIR_NAME = '.cache'; const DEFAULT_PORT = 9001; const DEFAULT_API_ENDPOINT = 'http://183.6.121.121:9519/'; // 创建缓存目录结构 const cacheDir = pathModule.join(__dirname, CACHE_DIR_NAME); if (!fs.existsSync(cacheDir)) { fs.mkdirSync(cacheDir, { recursive: true }); console.log(`Cache directory created: ${cacheDir}`); } // 根据缓存键创建子目录,避免单个目录文件过多 function getCacheSubDir(cacheKey) { // 使用缓存键的前两个字符作为子目录名 const subDir = cacheKey.substring(0, 2); const fullPath = pathModule.join(cacheDir, subDir); if (!fs.existsSync(fullPath)) { fs.mkdirSync(fullPath, { recursive: true }); } return fullPath; } // 缓存配置 const META_CACHE_EXPIRY_MS = 30 * 60 * 1000; // 30分钟 const metaCache = {}; // 存储meta信息的内存缓存 const contentCache = {}; // 存储内容的内存索引 // 下载任务管理和并发控制 const MAX_CONCURRENT_DOWNLOADS = 5; // 最大并发下载数 const MAX_QUEUE_SIZE = 50; // 最大队列长度 const MAX_CLIENTS_PER_TASK = 10; // 每个任务最大客户端数 const REQUEST_TIMEOUT = 30000; // 请求超时时间(30秒) const downloadTasks = new Map(); // 下载任务管理器 const downloadQueue = []; // 下载队列 let activeDownloads = 0; // 当前活跃下载数 // 服务器启动时间 const serverStartTime = Date.now(); // 下载任务状态 const DOWNLOAD_STATUS = { PENDING: 'pending', DOWNLOADING: 'downloading', COMPLETED: 'completed', FAILED: 'failed', PAUSED: 'paused' }; // 下载任务类 class DownloadTask { constructor(url, cacheKey, metaCacheFile, contentCacheFile) { this.id = `${cacheKey}_${Date.now()}`; this.url = url; this.cacheKey = cacheKey; this.metaCacheFile = metaCacheFile; this.contentCacheFile = contentCacheFile; this.status = DOWNLOAD_STATUS.PENDING; this.waitingClients = []; this.error = null; this.createdAt = Date.now(); // 进度跟踪 this.progress = { totalSize: 0, downloadedSize: 0, percentage: 0, speed: 0, // bytes/second eta: 0, // estimated time remaining in seconds startTime: null, lastUpdateTime: null, lastDownloadedSize: 0 }; // 状态管理 this.retryCount = 0; this.maxRetries = 3; this.lastError = null; this.pauseRequested = false; } addClient(res, rangeHeader) { this.waitingClients.push({ res, rangeHeader, addedAt: Date.now() }); } removeClient(res) { this.waitingClients = this.waitingClients.filter(client => client.res !== res); } hasClients() { return this.waitingClients.length > 0; } // 更新下载进度 updateProgress(downloadedBytes, totalBytes) { const now = Date.now(); if (!this.progress.startTime) { this.progress.startTime = now; } this.progress.totalSize = totalBytes; this.progress.downloadedSize = downloadedBytes; this.progress.percentage = totalBytes > 0 ? (downloadedBytes / totalBytes) * 100 : 0; // 计算下载速度 if (this.progress.lastUpdateTime) { const timeDiff = (now - this.progress.lastUpdateTime) / 1000; // seconds const sizeDiff = downloadedBytes - this.progress.lastDownloadedSize; if (timeDiff > 0) { this.progress.speed = sizeDiff / timeDiff; // 计算预计剩余时间 const remainingBytes = totalBytes - downloadedBytes; this.progress.eta = this.progress.speed > 0 ? remainingBytes / this.progress.speed : 0; } } this.progress.lastUpdateTime = now; this.progress.lastDownloadedSize = downloadedBytes; } // 获取进度信息 getProgressInfo() { return { id: this.id, url: this.url, status: this.status, progress: { ...this.progress }, clientCount: this.waitingClients.length, retryCount: this.retryCount, error: this.lastError, createdAt: this.createdAt }; } // 设置错误状态 setError(error) { this.lastError = error; this.error = error; this.status = DOWNLOAD_STATUS.FAILED; } // 请求暂停 requestPause() { this.pauseRequested = true; } // 检查是否应该重试 shouldRetry() { return this.retryCount < this.maxRetries && this.status === DOWNLOAD_STATUS.FAILED; } // 增加重试次数 incrementRetry() { this.retryCount++; } } // 下载任务管理器函数 function getOrCreateDownloadTask(url, cacheKey, metaCacheFile, contentCacheFile) { let task = downloadTasks.get(cacheKey); if (!task) { task = new DownloadTask(url, cacheKey, metaCacheFile, contentCacheFile); downloadTasks.set(cacheKey, task); } return task; } function startNextDownload() { if (activeDownloads >= MAX_CONCURRENT_DOWNLOADS || downloadQueue.length === 0) { return; } const task = downloadQueue.shift(); if (task && task.status === DOWNLOAD_STATUS.PENDING) { activeDownloads++; task.status = DOWNLOAD_STATUS.DOWNLOADING; console.log(`Starting download: ${task.url} (${activeDownloads}/${MAX_CONCURRENT_DOWNLOADS})`); executeDownload(task); } } function completeDownload(task, success = true) { activeDownloads = Math.max(0, activeDownloads - 1); task.status = success ? DOWNLOAD_STATUS.COMPLETED : DOWNLOAD_STATUS.FAILED; if (!success) { // 清理失败的下载任务 downloadTasks.delete(task.cacheKey); } console.log(`Download ${success ? 'completed' : 'failed'}: ${task.url} (${activeDownloads}/${MAX_CONCURRENT_DOWNLOADS})`); // 启动下一个下载任务 setTimeout(startNextDownload, 100); } function cleanupExpiredTasks() { const now = Date.now(); const expiredTasks = []; downloadTasks.forEach((task, key) => { // 清理超过1小时且没有客户端的已完成任务 if (task.status === DOWNLOAD_STATUS.COMPLETED && !task.hasClients() && (now - task.startTime) > 3600000) { expiredTasks.push(key); } // 清理超过10分钟的失败任务 else if (task.status === DOWNLOAD_STATUS.FAILED && (now - task.startTime) > 600000) { expiredTasks.push(key); } }); expiredTasks.forEach(key => { downloadTasks.delete(key); }); if (expiredTasks.length > 0) { console.log(`Cleaned up ${expiredTasks.length} expired download tasks`); } } // 定期清理过期任务 setInterval(cleanupExpiredTasks, 300000); // 每5分钟清理一次 // 创建HTTP服务器 const server = http.createServer(async (req, res) => { const parsedUrl = url.parse(req.url, true); const path = parsedUrl.pathname; const clientIP = req.connection.remoteAddress || req.socket.remoteAddress || '127.0.0.1'; // 处理favicon请求 if (path === '/favicon.ico') { res.writeHead(204); res.end(); return; } // 处理状态查询请求 if (path === '/status') { const tasks = Array.from(downloadTasks.values()).map(task => task.getProgressInfo()); const status = { activeDownloads, queueLength: downloadQueue.length, totalTasks: downloadTasks.size, tasks: tasks.slice(0, 20), // 只返回前20个任务 serverInfo: { maxConcurrentDownloads: MAX_CONCURRENT_DOWNLOADS, maxQueueSize: MAX_QUEUE_SIZE, maxClientsPerTask: MAX_CLIENTS_PER_TASK, requestTimeout: REQUEST_TIMEOUT, uptime: Date.now() - serverStartTime } }; res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify(status, null, 2)); return; } // 处理任务控制请求 if (path.startsWith('/control/')) { const action = path.split('/')[2]; // pause, resume, cancel const taskId = parsedUrl.query.taskId; if (!taskId) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Missing taskId parameter' })); return; } const task = downloadTasks.get(taskId); if (!task) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Task not found' })); return; } let result = { success: false, message: '' }; switch (action) { case 'pause': if (task.status === DOWNLOAD_STATUS.DOWNLOADING) { task.requestPause(); result = { success: true, message: 'Pause requested' }; } else { result = { success: false, message: 'Task is not downloading' }; } break; case 'resume': if (task.status === DOWNLOAD_STATUS.PAUSED || task.status === DOWNLOAD_STATUS.FAILED) { task.status = DOWNLOAD_STATUS.PENDING; task.pauseRequested = false; if (!downloadQueue.includes(task)) { downloadQueue.push(task); } startNextDownload(); result = { success: true, message: 'Resume requested' }; } else { result = { success: false, message: 'Task cannot be resumed' }; } break; case 'cancel': task.requestPause(); task.status = DOWNLOAD_STATUS.FAILED; task.setError('Cancelled by user'); // 通知所有等待的客户端 task.waitingClients.forEach(({ res: clientRes }) => { if (!clientRes.headersSent && !clientRes.destroyed) { clientRes.writeHead(499, { 'Content-Type': 'text/plain' }); clientRes.end('Download cancelled'); } }); task.waitingClients = []; result = { success: true, message: 'Task cancelled' }; break; default: result = { success: false, message: 'Invalid action' }; } res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify(result)); return; } // 设置请求超时 const timeout = setTimeout(() => { if (!res.headersSent) { res.writeHead(408, { 'Content-Type': 'text/plain' }); res.end('Request Timeout'); } }, REQUEST_TIMEOUT); // 设置CORS头,自动获取Origin const origin = req.headers.origin || req.headers.referer || '*'; res.setHeader('Access-Control-Allow-Origin', origin); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, HEAD'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, Accept, Origin'); res.setHeader('Access-Control-Allow-Credentials', 'true'); res.setHeader('Access-Control-Max-Age', '86400'); // 处理预检请求 if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } // 生成缓存键并创建分层目录结构 const cacheKey = crypto.createHash('md5').update(req.url).digest('hex'); const cacheSubDir = getCacheSubDir(cacheKey); const metaCacheFile = pathModule.join(cacheSubDir, `${cacheKey}.meta`); const contentCacheFile = pathModule.join(cacheSubDir, `${cacheKey}.content`); // 检查meta缓存是否有效 const now = Date.now(); let metaInfo = metaCache[cacheKey]; let metaExpired = !metaInfo || (now - metaInfo.timestamp > META_CACHE_EXPIRY_MS); // 如果meta缓存过期,尝试从文件读取 if (metaExpired && fs.existsSync(metaCacheFile)) { try { const metaData = JSON.parse(fs.readFileSync(metaCacheFile, 'utf8')); if (now - metaData.timestamp <= META_CACHE_EXPIRY_MS) { metaInfo = metaData; metaCache[cacheKey] = metaInfo; metaExpired = false; console.log(`Meta cache loaded from file for: ${req.url}`); } } catch (err) { console.error(`Error reading meta cache file: ${err.message}`); } } // 如果meta缓存有效且内容缓存存在,直接返回缓存内容 if (!metaExpired && fs.existsSync(contentCacheFile)) { console.log(`Serving from cache: ${req.url}`); const rangeHeader = req.headers.range; // 检查是否可以从完整缓存文件提供Range请求 if (rangeHeader && fs.existsSync(contentCacheFile)) { try { const stat = fs.statSync(contentCacheFile); const expectedSize = parseInt(metaInfo.headers['content-length'] || '0', 10); if (stat.size === expectedSize) { const ranges = parseRange(rangeHeader, expectedSize); if (ranges) { console.log(`Serving range request from complete cache: ${contentCacheFile}`); serveFromCache(res, metaInfo, contentCacheFile, rangeHeader, req); return; } } else if (stat.size > 0 && stat.size < expectedSize) { // 部分下载的文件,检查是否可以提供请求的范围 const ranges = parseRange(rangeHeader, expectedSize); if (ranges && !Array.isArray(ranges)) { const { start, end } = ranges; if (end < stat.size) { // 请求的范围在已下载的部分内 console.log(`Serving range request from partial cache: ${contentCacheFile}`); serveFromCache(res, metaInfo, contentCacheFile, rangeHeader); return; } } } } catch (err) { console.error(`Error checking cached file for range request: ${err.message}`); } } serveFromCache(res, metaInfo, contentCacheFile, rangeHeader, req); return; } // 需要请求原始API - 使用下载任务管理器 console.log(`Fetching from API: ${req.url}`); const rangeHeader = req.headers.range; // 获取或创建下载任务 const task = getOrCreateDownloadTask(req.url, cacheKey, metaCacheFile, contentCacheFile); // 检查队列是否已满 if (downloadQueue.length >= MAX_QUEUE_SIZE && task.status === DOWNLOAD_STATUS.PENDING) { console.log(`Download queue full, rejecting request: ${req.url}`); clearTimeout(timeout); res.writeHead(503, { 'Content-Type': 'text/plain' }); res.end('Service Unavailable - Queue Full'); return; } // 检查任务客户端数量限制 if (task.waitingClients.length >= MAX_CLIENTS_PER_TASK) { console.log(`Too many clients for task, rejecting: ${req.url}`); clearTimeout(timeout); res.writeHead(503, { 'Content-Type': 'text/plain' }); res.end('Service Unavailable - Too Many Clients'); return; } // 添加客户端到等待列表 task.addClient(res, rangeHeader); // 处理客户端断开连接 res.on('close', () => { clearTimeout(timeout); task.removeClient(res); console.log(`Client disconnected from task: ${task.id}`); }); // 处理响应完成 res.on('finish', () => { clearTimeout(timeout); }); if (task.status === DOWNLOAD_STATUS.COMPLETED) { // 任务已完成,直接从缓存提供内容 console.log(`Serving completed download from cache: ${req.url}`); serveFromCache(res, metaCache[cacheKey], contentCacheFile, rangeHeader, req); task.removeClient(res); } else if (task.status === DOWNLOAD_STATUS.DOWNLOADING) { // 任务正在下载中,客户端等待 console.log(`Client waiting for ongoing download: ${req.url}`); } else if (task.status === DOWNLOAD_STATUS.PENDING) { // 将任务加入下载队列 if (!downloadQueue.includes(task)) { downloadQueue.push(task); console.log(`Added to download queue: ${req.url}`); } startNextDownload(); } else if (task.status === DOWNLOAD_STATUS.FAILED) { // 任务失败,重新尝试 task.status = DOWNLOAD_STATUS.PENDING; task.error = null; if (!downloadQueue.includes(task)) { downloadQueue.push(task); } startNextDownload(); } }); // 从缓存提供内容,支持多段Range和HTTP缓存 function serveFromCache(res, metaInfo, contentCacheFile, rangeHeader, req) { // 设置响应头,保持CORS头 const headers = { ...metaInfo.headers }; // 确保CORS头不被覆盖 if (!headers['access-control-allow-origin']) { headers['access-control-allow-origin'] = res.getHeader('access-control-allow-origin') || '*'; } // 确保Content-Type正确设置,特别是对于视频文件 if (!headers['content-type']) { const ext = pathModule.extname(contentCacheFile).toLowerCase(); const mimeTypes = { '.mp4': 'video/mp4', '.webm': 'video/webm', '.ogg': 'video/ogg', '.avi': 'video/x-msvideo', '.mov': 'video/quicktime', '.wmv': 'video/x-ms-wmv', '.flv': 'video/x-flv', '.mkv': 'video/x-matroska', '.m4v': 'video/mp4', '.3gp': 'video/3gpp', '.ts': 'video/mp2t' }; headers['content-type'] = mimeTypes[ext] || 'application/octet-stream'; } // 确保Accept-Ranges头存在,这对视频播放很重要 headers['accept-ranges'] = 'bytes'; // 处理Range请求 if (rangeHeader && fs.existsSync(contentCacheFile)) { const stat = fs.statSync(contentCacheFile); const fileSize = stat.size; // 生成ETag和Last-Modified const etag = `"${stat.mtime.getTime()}-${fileSize}"`; const lastModified = stat.mtime.toUTCString(); // 设置缓存相关头 headers['etag'] = etag; headers['last-modified'] = lastModified; headers['cache-control'] = 'public, max-age=3600, must-revalidate'; // 1小时缓存,必须重新验证 headers['vary'] = 'Accept-Encoding, Range'; // 根据编码和范围请求变化 // 检查条件请求 const ifNoneMatch = req && req.headers['if-none-match']; const ifModifiedSince = req && req.headers['if-modified-since']; // 304 Not Modified 检查 if ((ifNoneMatch && ifNoneMatch === etag) || (ifModifiedSince && new Date(ifModifiedSince) >= stat.mtime)) { res.writeHead(304, headers); res.end(); return; } // 解析Range头 const ranges = parseRange(rangeHeader, fileSize); if (ranges) { // 单段Range请求 if (!Array.isArray(ranges)) { const { start, end } = ranges; const chunkSize = (end - start) + 1; // 设置206 Partial Content响应头 headers['content-range'] = `bytes ${start}-${end}/${fileSize}`; headers['accept-ranges'] = 'bytes'; headers['content-length'] = chunkSize; res.writeHead(206, headers); // 创建范围读取流 const fileStream = fs.createReadStream(contentCacheFile, { start, end }); fileStream.pipe(res); fileStream.on('error', (err) => { console.error(`Error reading cache file: ${err.message}`); res.statusCode = 500; res.end('Internal Server Error'); }); return; } // 多段Range请求 - 暂时不支持,返回完整文件 console.log('Multi-range request detected, serving full file instead'); } } // 普通请求或Range解析失败 const stat = fs.statSync(contentCacheFile); // 生成ETag和Last-Modified const etag = `"${stat.mtime.getTime()}-${stat.size}"`; const lastModified = stat.mtime.toUTCString(); // 设置缓存相关头 headers['etag'] = etag; headers['last-modified'] = lastModified; headers['cache-control'] = 'public, max-age=3600, must-revalidate'; // 1小时缓存,必须重新验证 headers['vary'] = 'Accept-Encoding, Range'; // 根据编码和范围请求变化 // 检查条件请求 const ifNoneMatch = req && req.headers['if-none-match']; const ifModifiedSince = req && req.headers['if-modified-since']; // 304 Not Modified 检查 if ((ifNoneMatch && ifNoneMatch === etag) || (ifModifiedSince && new Date(ifModifiedSince) >= stat.mtime)) { res.writeHead(304, headers); res.end(); return; } res.writeHead(200, headers); // 创建文件读取流并直接管道到响应 const fileStream = fs.createReadStream(contentCacheFile); fileStream.pipe(res); // 处理错误 fileStream.on('error', (err) => { console.error(`Error reading cache file: ${err.message}`); res.statusCode = 500; res.end('Internal Server Error'); }); } // 获取Content-Type function getContentType(url) { const ext = pathModule.extname(url).toLowerCase(); const mimeTypes = { '.mp4': 'video/mp4', '.webm': 'video/webm', '.ogg': 'video/ogg', '.avi': 'video/x-msvideo', '.mov': 'video/quicktime', '.wmv': 'video/x-ms-wmv', '.flv': 'video/x-flv', '.mkv': 'video/x-matroska', '.m4v': 'video/mp4', '.3gp': 'video/3gpp', '.ts': 'video/mp2t' }; return mimeTypes[ext] || 'application/octet-stream'; } // 解析Range头,支持多段Range function parseRange(rangeHeader, fileSize) { if (!rangeHeader || !rangeHeader.startsWith('bytes=')) { return null; } const rangeSpec = rangeHeader.substring(6); const ranges = []; // 支持多个范围,如 bytes=0-499,1000-1499 const rangeParts = rangeSpec.split(','); for (const part of rangeParts) { const trimmed = part.trim(); const [start, end] = trimmed.split('-').map(s => s ? parseInt(s, 10) : undefined); let rangeStart, rangeEnd; if (start !== undefined && end !== undefined) { // 完整范围: 0-499 rangeStart = start; rangeEnd = end; } else if (start !== undefined && end === undefined) { // 从某位置到结尾: 500- rangeStart = start; rangeEnd = fileSize - 1; } else if (start === undefined && end !== undefined) { // 最后几个字节: -500 rangeStart = Math.max(0, fileSize - end); rangeEnd = fileSize - 1; } else { continue; // 无效范围 } // 验证范围有效性 if (rangeStart >= fileSize || rangeEnd >= fileSize || rangeStart > rangeEnd || rangeStart < 0) { continue; } ranges.push({ start: rangeStart, end: rangeEnd }); } return ranges.length > 0 ? (ranges.length === 1 ? ranges[0] : ranges) : null; } // 执行下载任务 function executeDownload(task) { const targetUrl = new URL(task.url, DEFAULT_API_ENDPOINT); const protocol = targetUrl.protocol === 'https:' ? https : http; const options = { method: 'GET', headers: { 'User-Agent': 'Mozilla/5.0 (compatible; AlistProxy/1.0)' } }; // 检查是否有缓存文件,如果有则添加条件请求头 if (fs.existsSync(task.contentCacheFile)) { try { const stat = fs.statSync(task.contentCacheFile); const etag = `"${stat.mtime.getTime()}-${stat.size}"`; const lastModified = stat.mtime.toUTCString(); // 添加条件请求头,让上游服务器判断是否需要重新下载 options.headers['if-none-match'] = etag; options.headers['if-modified-since'] = lastModified; } catch (err) { console.log(`Error reading cache file stats: ${err.message}`); } } const proxyReq = protocol.request(targetUrl, options, (proxyRes) => { console.log(`Response status: ${proxyRes.statusCode} for ${task.url}`); // 处理304 Not Modified响应 if (proxyRes.statusCode === 304) { console.log(`304 Not Modified, serving from cache: ${task.url}`); // 从缓存提供内容 if (fs.existsSync(task.contentCacheFile) && fs.existsSync(task.metaCacheFile)) { try { const cachedMeta = JSON.parse(fs.readFileSync(task.metaCacheFile, 'utf8')); // 为所有等待的客户端提供缓存内容 task.waitingClients.forEach(({ res: clientRes, rangeHeader }) => { if (!clientRes.headersSent && !clientRes.destroyed) { serveFromCache(clientRes, cachedMeta, task.contentCacheFile, rangeHeader, null); } }); task.status = DOWNLOAD_STATUS.COMPLETED; completeDownload(task, true); return; } catch (err) { console.error(`Error serving 304 from cache: ${err.message}`); } } // 如果缓存文件不存在,继续正常下载 console.log(`Cache file missing for 304 response, continuing download`); } const metaInfo = { timestamp: Date.now(), headers: proxyRes.headers, statusCode: proxyRes.statusCode }; // 更新内存缓存 metaCache[task.cacheKey] = metaInfo; // 写入meta缓存文件 fs.writeFile(task.metaCacheFile, JSON.stringify(metaInfo), (err) => { if (err) console.error(`Error writing meta cache: ${err.message}`); }); if (proxyRes.statusCode >= 200 && proxyRes.statusCode < 300) { const contentLength = parseInt(proxyRes.headers['content-length'] || '0', 10); task.updateProgress(0, contentLength); // 创建临时文件用于断点续传 const tempFile = `${task.contentCacheFile}.tmp`; const fileStream = fs.createWriteStream(tempFile); let downloadedBytes = 0; // 进度跟踪变量 let lastLoggedSize = 0; let lastLogTime = Date.now(); // 监听数据流 proxyRes.on('data', (chunk) => { downloadedBytes += chunk.length; task.updateProgress(downloadedBytes, contentLength); // 每1MB或每5秒输出一次进度日志 const now = Date.now(); if (downloadedBytes - lastLoggedSize >= 1024 * 1024 || now - lastLogTime >= 5000) { const progress = task.getProgressInfo(); console.log(`Download progress [${task.id}]: ${progress.progress.percentage.toFixed(1)}% ` + `(${formatBytes(progress.progress.downloadedSize)}/${formatBytes(progress.progress.totalSize)}) ` + `Speed: ${formatBytes(progress.progress.speed)}/s ` + `ETA: ${formatTime(progress.progress.eta)}`); lastLoggedSize = downloadedBytes; lastLogTime = now; } // 检查是否请求暂停 if (task.pauseRequested) { console.log(`Download paused for task: ${task.id}`); task.status = DOWNLOAD_STATUS.PAUSED; proxyRes.destroy(); if (fileStream && !fileStream.destroyed) { fileStream.end(); } return; } // 检查等待的Range请求客户端 const clientsToServe = []; task.waitingClients.forEach((client, index) => { if (client.res.destroyed) { return; } if (client.rangeHeader) { const range = parseRange(client.rangeHeader, contentLength); if (range && !Array.isArray(range)) { const { start, end } = range; // 检查请求的范围是否已经下载完成 if (downloadedBytes > end) { console.log(`Serving range to waiting client: ${start}-${end} (downloaded: ${downloadedBytes})`); clientsToServe.push({ client, range, index }); } } } }); // 为可以服务的客户端提供Range响应 clientsToServe.forEach(({ client, range, index }) => { try { const { start, end } = range; const chunkSize = (end - start) + 1; // 设置响应头 const headers = { 'content-type': getContentType(task.url), 'accept-ranges': 'bytes', 'content-range': `bytes ${start}-${end}/${contentLength}`, 'content-length': chunkSize, 'access-control-allow-origin': '*', 'cache-control': 'public, max-age=3600' }; client.res.writeHead(206, headers); // 直接从临时文件读取指定范围的数据 const rangeStream = fs.createReadStream(tempFile, { start, end }); rangeStream.pipe(client.res); rangeStream.on('error', (err) => { console.error(`Error reading range from temp file: ${err.message}`); if (!client.res.headersSent) { client.res.writeHead(500); client.res.end('Internal Server Error'); } }); // 从等待列表中移除该客户端 task.waitingClients.splice(index, 1); console.log(`Served range ${start}-${end} from temp file to waiting client`); } catch (err) { console.error(`Error serving range to client: ${err.message}`); } }); // 向非Range请求的等待客户端发送数据 task.waitingClients.forEach(client => { if (!client.res.destroyed && !client.rangeHeader) { // 设置响应头(仅第一次) if (!client.headersSent) { const headers = { ...proxyRes.headers }; if (!headers['access-control-allow-origin']) { headers['access-control-allow-origin'] = client.res.getHeader('access-control-allow-origin') || '*'; } headers['accept-ranges'] = 'bytes'; client.res.writeHead(200, headers); client.headersSent = true; } // 发送数据块 try { client.res.write(chunk); } catch (err) { console.error(`Error writing to client: ${err.message}`); task.removeClient(client.res); } } }); }); // 将数据写入文件 proxyRes.pipe(fileStream); proxyRes.on('end', () => { // 结束所有客户端响应 task.waitingClients.forEach(client => { if (!client.res.destroyed) { try { client.res.end(); } catch (err) { console.error(`Error ending client response: ${err.message}`); } } }); task.waitingClients = []; // 完成文件写入 fileStream.end(); }); fileStream.on('finish', () => { fs.rename(tempFile, task.contentCacheFile, (err) => { if (err) { console.error(`Error saving cache file: ${err.message}`); completeDownload(task, false); } else { console.log(`Cache saved for: ${task.url}`); completeDownload(task, true); } }); }); fileStream.on('error', (err) => { console.error(`Error writing cache file: ${err.message}`); fs.unlink(tempFile, () => {}); completeDownload(task, false); }); } else { // 非成功响应 task.waitingClients.forEach(client => { if (!client.res.destroyed) { const headers = { ...proxyRes.headers }; if (!headers['access-control-allow-origin']) { headers['access-control-allow-origin'] = client.res.getHeader('access-control-allow-origin') || '*'; } client.res.writeHead(proxyRes.statusCode, headers); proxyRes.pipe(client.res); } }); completeDownload(task, false); } }); proxyReq.on('error', (err) => { console.error(`Download request error: ${err.message}`); task.error = err.message; // 向所有等待的客户端发送错误响应 task.waitingClients.forEach(client => { if (!client.res.destroyed) { client.res.statusCode = 502; client.res.end('Bad Gateway'); } }); task.waitingClients = []; completeDownload(task, false); }); proxyReq.end(); } // 从API获取内容(保留用于非大文件请求) function fetchFromApi(req, res, cacheKey, metaCacheFile, contentCacheFile, rangeHeader) { // 构建目标URL const targetUrl = new URL(req.url, DEFAULT_API_ENDPOINT); // 选择合适的协议模块 const protocol = targetUrl.protocol === 'https:' ? https : http; // 准备请求选项 const options = { method: req.method, headers: { ...req.headers } }; // 删除可能导致问题的头信息 delete options.headers.host; // 检查是否有缓存文件,如果有则添加条件请求头 if (fs.existsSync(contentCacheFile)) { try { const stat = fs.statSync(contentCacheFile); const etag = `"${stat.mtime.getTime()}-${stat.size}"`; const lastModified = stat.mtime.toUTCString(); // 添加条件请求头,让上游服务器判断是否需要重新下载 options.headers['if-none-match'] = etag; options.headers['if-modified-since'] = lastModified; } catch (err) { console.log(`Error reading cache file stats: ${err.message}`); } } // 检查是否有正在进行的下载任务 const existingTask = downloadTasks.get(cacheKey); if (existingTask && rangeHeader) { const range = parseRange(rangeHeader, existingTask.totalSize || Number.MAX_SAFE_INTEGER); if (range && !Array.isArray(range)) { const { start, end } = range; // 检查请求的范围是否已经下载 if (fs.existsSync(contentCacheFile)) { const stat = fs.statSync(contentCacheFile); if (stat.size > end) { // 请求的范围已经下载完成,从部分文件提供内容 console.log(`Serving range from partial download: ${req.url} (${start}-${end}, downloaded: ${stat.size})`); // 创建临时的meta信息 const tempMeta = { headers: { 'content-type': getContentType(req.url), 'accept-ranges': 'bytes', 'access-control-allow-origin': '*' }, statusCode: 200 }; serveFromCache(res, tempMeta, contentCacheFile, rangeHeader, req); return; } } // 如果请求的范围还没下载完,将客户端添加到等待列表 console.log(`Adding client to waiting list for ongoing download: ${req.url}`); existingTask.addClient(res, rangeHeader); return; } } // 如果是Range请求且缓存文件已存在,检查是否可以从缓存提供部分内容 if (rangeHeader && fs.existsSync(contentCacheFile)) { const stat = fs.statSync(contentCacheFile); const range = parseRange(rangeHeader, stat.size); if (range) { // 尝试从缓存读取meta信息来验证文件完整性 try { const metaData = JSON.parse(fs.readFileSync(metaCacheFile, 'utf8')); if (metaData.headers && metaData.headers['content-length']) { const expectedSize = parseInt(metaData.headers['content-length'], 10); if (stat.size === expectedSize) { // 缓存文件完整,直接提供Range响应 console.log(`Serving range from complete cache: ${req.url}`); serveFromCache(res, metaData, contentCacheFile, rangeHeader, req); return; } } } catch (err) { // 忽略meta文件读取错误,继续正常流程 } } } // 创建请求 const proxyReq = protocol.request(targetUrl, options, (proxyRes) => { // 处理304 Not Modified响应 if (proxyRes.statusCode === 304) { console.log(`304 Not Modified, serving from cache: ${req.url}`); // 从缓存提供内容 if (fs.existsSync(contentCacheFile) && fs.existsSync(metaCacheFile)) { try { const cachedMeta = JSON.parse(fs.readFileSync(metaCacheFile, 'utf8')); serveFromCache(res, cachedMeta, contentCacheFile, rangeHeader, req); return; } catch (err) { console.error(`Error serving 304 from cache: ${err.message}`); } } // 如果缓存文件不存在,返回304给客户端 res.writeHead(304, { 'Access-Control-Allow-Origin': res.getHeader('access-control-allow-origin') || '*', 'Cache-Control': 'public, max-age=3600' }); res.end(); return; } // 记录meta信息 const metaInfo = { timestamp: Date.now(), headers: proxyRes.headers, statusCode: proxyRes.statusCode }; // 更新内存缓存 metaCache[cacheKey] = metaInfo; // 写入meta缓存文件 fs.writeFile(metaCacheFile, JSON.stringify(metaInfo), (err) => { if (err) console.error(`Error writing meta cache: ${err.message}`); }); // 设置响应头,保持CORS头 const responseHeaders = { ...proxyRes.headers }; // 确保CORS头不被覆盖 if (!responseHeaders['access-control-allow-origin']) { responseHeaders['access-control-allow-origin'] = res.getHeader('access-control-allow-origin') || '*'; } // 确保Content-Type正确设置,特别是对于视频文件 if (!responseHeaders['content-type']) { const ext = pathModule.extname(req.url).toLowerCase(); const mimeTypes = { '.mp4': 'video/mp4', '.webm': 'video/webm', '.ogg': 'video/ogg', '.avi': 'video/x-msvideo', '.mov': 'video/quicktime', '.wmv': 'video/x-ms-wmv', '.flv': 'video/x-flv', '.mkv': 'video/x-matroska', '.m4v': 'video/mp4', '.3gp': 'video/3gpp', '.ts': 'video/mp2t' }; responseHeaders['content-type'] = mimeTypes[ext] || 'application/octet-stream'; } // 确保Accept-Ranges头存在 responseHeaders['accept-ranges'] = 'bytes'; res.writeHead(proxyRes.statusCode, responseHeaders); // 如果是成功的响应,缓存内容 if (proxyRes.statusCode >= 200 && proxyRes.statusCode < 300) { // 对于Range请求的206响应,不进行缓存,直接转发 if (proxyRes.statusCode === 206 || rangeHeader) { console.log(`Streaming range response: ${req.url}`); proxyRes.pipe(res); } else { // 完整内容响应,进行缓存 const tempFile = `${contentCacheFile}.tmp`; const fileStream = fs.createWriteStream(tempFile); // 将响应写入文件并同时发送给客户端 proxyRes.pipe(fileStream); proxyRes.pipe(res); // 完成后重命名临时文件 fileStream.on('finish', () => { fs.rename(tempFile, contentCacheFile, (err) => { if (err) console.error(`Error saving cache file: ${err.message}`); else console.log(`Cache saved for: ${req.url}`); }); }); // 处理错误 fileStream.on('error', (err) => { console.error(`Error writing cache file: ${err.message}`); fs.unlink(tempFile, () => {}); // 删除临时文件 }); } } else { // 非成功响应直接转发,不缓存内容 proxyRes.pipe(res); } }); // 处理请求错误 proxyReq.on('error', (err) => { console.error(`Proxy request error: ${err.message}`); res.statusCode = 502; res.end('Bad Gateway'); }); // 如果原始请求有body,转发它 if (['POST', 'PUT', 'PATCH'].includes(req.method)) { req.pipe(proxyReq); } else { proxyReq.end(); } } // 格式化字节数 function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // 格式化时间(秒) function formatTime(seconds) { if (!seconds || seconds === Infinity) return 'Unknown'; const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); if (hours > 0) { return `${hours}h ${minutes}m ${secs}s`; } else if (minutes > 0) { return `${minutes}m ${secs}s`; } else { return `${secs}s`; } } // 启动服务器 server.listen(DEFAULT_PORT, () => { console.log(`Proxy server running at http://localhost:${DEFAULT_PORT}/`); console.log(`Proxying requests to: ${DEFAULT_API_ENDPOINT}`); console.log(`Cache directory: ${cacheDir}`); console.log(`Meta cache expiry: ${META_CACHE_EXPIRY_MS / 60000} minutes`); });