From e9b152af470f01c5f39974c615ea5c604447204e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=92=8B=E5=B0=8F=E9=99=8C?= Date: Sat, 31 Aug 2024 17:51:00 +0800 Subject: [PATCH] 1111 --- index.js | 165 +++++++++++++++++++++++++++---------------------------- 1 file changed, 82 insertions(+), 83 deletions(-) diff --git a/index.js b/index.js index 9391964..9b0e477 100644 --- a/index.js +++ b/index.js @@ -3,15 +3,24 @@ const https = require('https'); const url = require('url'); const querystring = require('querystring'); -const apiEndpoint = process.env.url || 'https://oss.x-php.com/alist/link'; const requestTimeout = 10000; // 10 seconds const cache = {}; +const args = process.argv.slice(2); -// Get port from environment variable or default to 9001 -const PORT = process.env.PORT || 9001; +let port = 9001; +let apiEndpoint = 'https://oss.x-php.com/alist/link'; -const server = http.createServer((req, res) => { +// 解析命令行参数 +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(); @@ -22,105 +31,95 @@ const server = http.createServer((req, res) => { const path = parsedUrl.pathname; const sign = parsedUrl.query.sign || ''; - if (sign == '' || path == '/') { + if (!sign || path === '/') { res.writeHead(400, { 'Content-Type': 'text/plain' }); res.end('Bad Request: Missing sign or path'); return; } - - // Check if the data is in cache and not expired - const cacheEntry = cache[path]; - if (cacheEntry && cacheEntry.expiration > Date.now()) { - // 清理所有过期的缓存 - Object.keys(cache).forEach(key => { - if (cache[key].expiration < Date.now()) { - delete cache[key]; - } - }); - - serveFromCache(cacheEntry, res); - 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 }; - // Construct the POST data - const postData = querystring.stringify({ path, sign }); - - // Request the real URL from the API - 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 { - const apiData = JSON.parse(data); - if (apiData.code === 200 && apiData.data && apiData.data.url) { - const { url: realUrl, cloudtype, expiration } = apiData.data; - // Cache the response if expiration is greater than 0 - if (expiration > 0) { - cache[path] = { - realUrl, - cloudtype, - expiration: Date.now() + expiration * 1000 - }; - } - fetchAndServe(realUrl, cloudtype, res); - } else { - res.writeHead(502, { 'Content-Type': 'text/plain' }); - res.end(apiData.message || 'Bad Gateway'); + if (expiration > 0) { + cache[path] = data; } - } catch (error) { + fetchAndServe(data, res); + } else { res.writeHead(502, { 'Content-Type': 'text/plain' }); - res.end('Bad Gateway: Failed to decode JSON'); + res.end(apiData.message || 'Bad Gateway'); } - }); - }); - - apiReq.on('error', (e) => { - if (e.code === 'ETIMEDOUT') { - res.writeHead(504, { 'Content-Type': 'text/plain' }); - res.end('Gateway Timeout'); - } else { - res.writeHead(500, { 'Content-Type': 'text/plain' }); - res.end('Internal Server Error'); + } catch (error) { + res.writeHead(502, { 'Content-Type': 'text/plain' }); + res.end('Bad Gateway: Failed to decode JSON'); } - }); - - apiReq.write(postData); - apiReq.end(); + } }); -function fetchAndServe(realUrl, cloudtype, res) { - const realReq = https.get(realUrl, { timeout: requestTimeout * 10 }, (realRes) => { +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, - 'cloudtype': cloudtype + 'Cloud-Type': data.cloudtype, + 'Cloud-Expiration': data.expiration, }); realRes.pipe(res); - }); - - realReq.on('error', (e) => { + }).on('error', (e) => { res.writeHead(502, { 'Content-Type': 'text/plain' }); - res.end(`Bad Gateway: ${realUrl}`); + res.end(`Bad Gateway: ${data.realUrl}`); }); -} +}; -function serveFromCache(cacheEntry, res) { - fetchAndServe(cacheEntry.realUrl, cacheEntry.cloudtype, res); -} - -server.listen(PORT, () => { - console.log(`Proxy server is running on http://localhost:${PORT}`); +server.listen(port, () => { + console.log(`Proxy server is running on http://localhost:${port}`); }); // Graceful shutdown @@ -136,4 +135,4 @@ process.on('SIGINT', () => { console.error('Forcing shutdown...'); process.exit(1); }, 10000); -}); +}); \ No newline at end of file