const http = require('http'); const https = require('https'); const url = require('url'); const querystring = require('querystring'); const requestTimeout = 10000; // 10 seconds const cache = {}; const args = process.argv.slice(2); let port = 9001; let apiEndpoint = 'https://oss.x-php.com/alist/link'; // 解析命令行参数 args.forEach(arg => { const [key, value] = arg.split('='); if (key === 'port') { port = parseInt(value, 10); } else if (key === 'api') { apiEndpoint = value; } }); const server = http.createServer(async (req, res) => { if (req.url === '/favicon.ico') { res.writeHead(204); res.end(); return; } const parsedUrl = url.parse(req.url, true); const path = parsedUrl.pathname; const sign = parsedUrl.query.sign || ''; if (!sign || path === '/') { res.writeHead(400, { 'Content-Type': 'text/plain' }); res.end('Bad Request: Missing sign or path'); return; } if (isCacheValid(path)) { cleanExpiredCache(); fetchAndServe(cache[path], res); } else { delete cache[path]; // Remove expired cache entry if exists try { const apiData = await fetchApiData(path, sign); if (apiData.code === 200 && apiData.data && apiData.data.url) { const { url: realUrl, cloudtype, expiration } = apiData.data; const data = { realUrl, cloudtype, expiration: expiration * 1000 }; if (expiration > 0) { cache[path] = data; } fetchAndServe(data, res); } else { res.writeHead(502, { 'Content-Type': 'text/plain' }); res.end(apiData.message || 'Bad Gateway'); } } catch (error) { res.writeHead(502, { 'Content-Type': 'text/plain' }); res.end('Bad Gateway: Failed to decode JSON'); } } }); const isCacheValid = (path) => cache[path] && cache[path].expiration > Date.now(); const cleanExpiredCache = () => { Object.keys(cache).forEach(key => { if (cache[key].expiration < Date.now()) { delete cache[key]; } }); }; const fetchApiData = (path, sign) => { return new Promise((resolve, reject) => { const postData = querystring.stringify({ path, sign }); const apiReq = https.request(apiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json', 'Content-Length': Buffer.byteLength(postData), 'sign': sign }, timeout: requestTimeout }, (apiRes) => { let data = ''; apiRes.on('data', chunk => data += chunk); apiRes.on('end', () => { try { resolve(JSON.parse(data)); } catch (error) { reject(error); } }); }); apiReq.on('error', reject); apiReq.write(postData); apiReq.end(); }); }; const fetchAndServe = (data, res) => { https.get(data.realUrl, { timeout: requestTimeout * 10 }, (realRes) => { res.writeHead(realRes.statusCode, { ...realRes.headers, 'Cloud-Type': data.cloudtype, 'Cloud-Expiration': data.expiration, }); realRes.pipe(res); }).on('error', (e) => { res.writeHead(502, { 'Content-Type': 'text/plain' }); res.end(`Bad Gateway: ${data.realUrl}`); }); }; server.listen(port, () => { console.log(`Proxy server is running on http://localhost:${port}`); }); // Graceful shutdown process.on('SIGINT', () => { console.log('Received SIGINT. Shutting down gracefully...'); server.close(() => { console.log('Server closed.'); process.exit(0); }); // Force shutdown after 10 seconds if not closed setTimeout(() => { console.error('Forcing shutdown...'); process.exit(1); }, 10000); });