const http = require('http'); const https = require('https'); const fs = require('fs').promises; const path = require('path'); const { URL } = require('url'); const PORT = 3000; const CACHE_DIR = path.join(__dirname, '.cache'); const CACHE_EXPIRY = 30 * 24 * 60 * 60 * 1000; // 30 days in milliseconds const CLEAN_INTERVAL = 24 * 60 * 60 * 1000; // 1 day in milliseconds // Ensure the cache directory exists fs.mkdir(CACHE_DIR, { recursive: true }).catch(console.error); // Helper function to get cache file path const getCacheFilePath = (requestUrl) => { const urlObj = new URL(requestUrl); const sanitizedUrl = (urlObj.host + urlObj.pathname).replace(/[^a-z0-9]/gi, '_').toLowerCase(); return path.join(CACHE_DIR, sanitizedUrl); }; // Function to clean up expired cache files const cleanUpCache = async () => { try { const files = await fs.readdir(CACHE_DIR); const now = Date.now(); for (const file of files) { const filePath = path.join(CACHE_DIR, file); const stats = await fs.stat(filePath); if (now - stats.mtimeMs > CACHE_EXPIRY) { await fs.unlink(filePath); } } } catch (err) { console.error('Error cleaning up cache:', err); } }; // Schedule cache clean-up at regular intervals setInterval(cleanUpCache, CLEAN_INTERVAL); // Function to handle proxying and caching const handleRequest = async (req, res) => { const targetUrl = `https://oss.x-php.com${req.url}`; const cacheFilePath = getCacheFilePath(targetUrl); try { // Check if the cache file exists and is still valid const cacheStats = await fs.stat(cacheFilePath); const now = Date.now(); if (now - cacheStats.mtimeMs < CACHE_EXPIRY) { // Serve from cache const cachedData = JSON.parse(await fs.readFile(cacheFilePath, 'utf8')); res.writeHead(cachedData.statusCode, cachedData.headers); res.end(Buffer.from(cachedData.body, 'base64')); return; } } catch (err) { // Cache file does not exist or is invalid, proceed to fetch from the target URL } // Fetch from the target URL https.get(targetUrl, (proxyRes) => { let data = []; proxyRes.on('data', (chunk) => { data.push(chunk); }); proxyRes.on('end', async () => { const responseData = Buffer.concat(data); if (proxyRes.statusCode === 200 && proxyRes.headers['content-type'] && proxyRes.headers['content-type'].startsWith('image/')) { // Save the response to cache if it is an image const cacheData = { statusCode: proxyRes.statusCode, headers: proxyRes.headers, body: responseData.toString('base64') }; await fs.writeFile(cacheFilePath, JSON.stringify(cacheData)).catch(console.error); } // Serve the response res.writeHead(proxyRes.statusCode, proxyRes.headers); res.end(responseData); }); }).on('error', (err) => { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Error fetching the resource'); }); }; // Create an HTTP server const server = http.createServer(handleRequest); server.listen(PORT, () => { console.log(`Proxy server running at http://localhost:${PORT}`); });