/** * MX Proxy — HTTP/CONNECT proxy on HuggingFace Spaces * * Endpoints: * GET / → Web UI (proxy browser) * GET /health → Health check (keeps Space alive) * GET /stats → Proxy stats * ANY /proxy → HTTP relay: POST {url, method, headers, body} * GET /fetch?url= → Simple GET proxy * CONNECT support → Via HTTP upgrade (if HF reverse proxy allows) * * Auth: Simple token via ?token= or Authorization header */ import http from 'node:http'; import https from 'node:https'; import { URL } from 'node:url'; import { execSync } from 'node:child_process'; // ── Config ────────────────────────────────────────────────────────────────── const PORT = parseInt(process.env.PORT || '7860', 10); const PROXY_TOKEN = process.env.PROXY_TOKEN || 'mx-proxy-2026'; const MAX_BODY = 5 * 1024 * 1024; // 5MB max proxied request // ── Stats ─────────────────────────────────────────────────────────────────── let requestCount = 0; let proxiedCount = 0; let startTime = Date.now(); // ── Auth check ────────────────────────────────────────────────────────────── function checkAuth(req) { const url = new URL(req.url, `http://${req.headers.host}`); const tokenParam = url.searchParams.get('token'); const authHeader = req.headers['authorization']?.replace('Bearer ', ''); return tokenParam === PROXY_TOKEN || authHeader === PROXY_TOKEN; } // ── Fetch a URL and return the response ───────────────────────────────────── function fetchUrl(targetUrl, options = {}) { return new Promise((resolve, reject) => { const parsedUrl = new URL(targetUrl); const isHttps = parsedUrl.protocol === 'https:'; const httpModule = isHttps ? https : http; const reqOptions = { hostname: parsedUrl.hostname, port: parsedUrl.port || (isHttps ? 443 : 80), path: parsedUrl.pathname + parsedUrl.search, method: options.method || 'GET', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', 'Accept': options.accept || '*/*', 'Accept-Language': 'es-MX,es;q=0.9,en;q=0.8', ...options.headers, }, timeout: 30000, // Don't verify SSL for maximum compatibility rejectUnauthorized: false, }; const proxyReq = httpModule.request(reqOptions, (proxyRes) => { const chunks = []; proxyRes.on('data', chunk => chunks.push(chunk)); proxyRes.on('end', () => { resolve({ status: proxyRes.statusCode, headers: proxyRes.headers, body: Buffer.concat(chunks), }); }); }); proxyReq.on('error', reject); proxyReq.on('timeout', () => { proxyReq.destroy(); reject(new Error('Target server timeout (30s)')); }); if (options.body) { proxyReq.write(options.body); } proxyReq.end(); }); } // ── HTTP CONNECT tunnel ───────────────────────────────────────────────────── function handleConnect(req, socket, head) { if (!checkAuth(req)) { socket.write('HTTP/1.1 407 Proxy Auth Required\r\nProxy-Authenticate: Basic realm="proxy"\r\n\r\n'); socket.destroy(); return; } const [host, port] = req.url.split(':'); const targetPort = parseInt(port) || 443; const targetSocket = require('net').connect(targetPort, host, () => { proxiedCount++; socket.write('HTTP/1.1 200 Connection Established\r\n\r\n'); targetSocket.write(head); targetSocket.pipe(socket); socket.pipe(targetSocket); }); targetSocket.on('error', (err) => { console.error(`[CONNECT] Error: ${err.message}`); socket.destroy(); }); socket.on('error', () => targetSocket.destroy()); } // ── Web UI HTML ───────────────────────────────────────────────────────────── function getWebUI(req) { const host = req.headers.host; const token = PROXY_TOKEN; const baseUrl = `https://${host}`; return ` MX Proxy

🇲🇽 MX Proxy

HTTP Proxy via HuggingFace Spaces — IP del servidor (no MX, pero funcional)

📡 Proxy Rápido (GET)

🔧 Proxy Avanzado (POST)

⚙️ Configuración para usar como proxy del navegador

Usa estos endpoints para enrutar tráfico desde tu navegador o scripts:

${baseUrl}/fetch?url=TARGET&token=${token}
${baseUrl}/proxy

Ejemplo curl:
curl "${baseUrl}/fetch?url=https://api.ipify.org&token=${token}"

POST ejemplo:
curl -X POST "${baseUrl}/proxy" -H "Content-Type: application/json" -d '{"url":"https://httpbin.org/get","method":"GET","token":"${token}"}'

📊 Estado

Cargando...
`; } // ── Main HTTP Server ──────────────────────────────────────────────────────── const server = http.createServer(async (req, res) => { requestCount++; const url = new URL(req.url, `http://${req.headers.host}`); try { // Health check — no auth needed if (url.pathname === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'alive', uptime: Math.floor((Date.now() - startTime) / 1000) })); return; } // Stats if (url.pathname === '/stats') { if (!checkAuth(req)) { res.writeHead(401); res.end('Unauthorized'); return; } let proxyIp = 'unknown'; try { proxyIp = await (await fetch('https://api.ipify.org?format=json')).json(); proxyIp = proxyIp.ip; } catch {} res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ proxyIp, proxiedCount, requestCount, uptime: `${Math.floor((Date.now() - startTime) / 60000)}m`, })); return; } // Web UI if (url.pathname === '/' || url.pathname === '') { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(getWebUI(req)); return; } // Simple GET proxy: /fetch?url=TARGET&token=TOKEN if (url.pathname === '/fetch') { if (!checkAuth(req)) { res.writeHead(401); res.end('Unauthorized — add ?token=YOUR_TOKEN'); return; } const targetUrl = url.searchParams.get('url'); if (!targetUrl) { res.writeHead(400); res.end('Missing ?url= parameter'); return; } console.log(`[PROXY] GET → ${targetUrl}`); try { const result = await fetchUrl(targetUrl, { method: 'GET' }); proxiedCount++; const contentType = result.headers['content-type'] || 'application/octet-stream'; res.writeHead(result.status, { 'Content-Type': contentType, 'X-Proxied-By': 'mx-proxy', 'Access-Control-Allow-Origin': '*', }); res.end(result.body); } catch (err) { res.writeHead(502, { 'Content-Type': 'text/plain' }); res.end(`Proxy error: ${err.message}`); } return; } // Advanced POST proxy: /proxy — body = {url, method, headers, body, token} if (url.pathname === '/proxy' && req.method === 'POST') { if (!checkAuth(req)) { res.writeHead(401); res.end('Unauthorized'); return; } const body = await new Promise((resolve, reject) => { const chunks = []; req.on('data', c => chunks.push(c)); req.on('end', () => resolve(Buffer.concat(chunks))); req.on('error', reject); }); let parsed; try { parsed = JSON.parse(body.toString()); } catch { res.writeHead(400); res.end('Invalid JSON body'); return; } const { url: targetUrl, method, headers, body: reqBody } = parsed; if (!targetUrl) { res.writeHead(400); res.end('Missing "url" in body'); return; } console.log(`[PROXY] ${method || 'GET'} → ${targetUrl}`); try { const result = await fetchUrl(targetUrl, { method: method || 'GET', headers: headers || {}, body: reqBody || undefined, }); proxiedCount++; // Return structured response const contentType = result.headers['content-type'] || ''; const isBinary = /image|video|audio|octet-stream|zip|pdf/.test(contentType); if (isBinary) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }); res.end(JSON.stringify({ status: result.status, headers: result.headers, bodyBase64: result.body.toString('base64'), bodySize: result.body.length, binary: true, })); } else { const text = result.body.toString('utf-8'); res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', }); let parsedBody = text; try { parsedBody = JSON.parse(text); } catch {} res.end(JSON.stringify({ status: result.status, headers: result.headers, body: parsedBody, bodySize: result.body.length, })); } } catch (err) { res.writeHead(502, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: err.message })); } return; } // CORS preflight if (req.method === 'OPTIONS') { res.writeHead(204, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Max-Age': '86400', }); res.end(); return; } // 404 res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not found. Endpoints: / /health /stats /fetch?url= /proxy'); } catch (err) { console.error('[Server] Error:', err.message); res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Server error: ' + err.message); } }); // ── CONNECT support for HTTP tunneling ────────────────────────────────────── server.on('connect', handleConnect); // ── Start ─────────────────────────────────────────────────────────────────── server.listen(PORT, '0.0.0.0', () => { console.log(`[MX-Proxy] Listening on 0.0.0.0:${PORT}`); console.log(`[MX-Proxy] Token: ${PROXY_TOKEN}`); console.log(`[MX-Proxy] Endpoints: / /health /stats /fetch?url= /proxy`); // Log the public IP try { const ip = execSync('curl -s --max-time 5 https://api.ipify.org').toString().trim(); console.log(`[MX-Proxy] Public IP: ${ip}`); } catch { console.log('[MX-Proxy] Could not determine public IP'); } }); // Keepalive setInterval(() => { http.get(`http://127.0.0.1:${PORT}/health`, () => {}).on('error', () => {}); }, 5 * 60 * 1000); // Memory log setInterval(() => { const mem = process.memoryUsage(); console.log(`[KeepAlive] RSS: ${(mem.rss/1024/1024).toFixed(1)}MB | Heap: ${(mem.heapUsed/1024/1024).toFixed(1)}MB | Uptime: ${Math.floor(process.uptime())}s | Proxied: ${proxiedCount}`); }, 60000);