UNPKG

@codelook/proxy-server

Version:

HTTP/HTTPS proxy server with Docker deployment and Cloudflare Workers support

231 lines (194 loc) 6.57 kB
/** * Cloudflare Workers 代理服务 * * 部署步骤: * 1. 安装 wrangler: npm install -g wrangler * 2. 登录 Cloudflare: wrangler login * 3. 配置 wrangler.toml * 4. 部署: wrangler publish */ // 配置 const CONFIG = { // 是否启用认证 AUTH_ENABLED: true, // 认证令牌(建议使用环境变量) AUTH_TOKEN: globalThis.PROXY_AUTH_TOKEN || 'pst_YOUR_TOKEN_HERE', // 允许的目标域名(空数组表示允许所有) ALLOWED_DOMAINS: [], // 禁止的目标域名 BLOCKED_DOMAINS: ['localhost', '127.0.0.1', '0.0.0.0'], // 速率限制(每分钟最大请求数) RATE_LIMIT: 100, // CORS 配置 CORS_ORIGINS: ['*'], }; // 主处理函数 addEventListener('fetch', event => { event.respondWith(handleRequest(event.request, event)); }); async function handleRequest(request, event) { // CORS 预检请求 if (request.method === 'OPTIONS') { return handleCORS(request); } // 健康检查 if (new URL(request.url).pathname === '/health') { return new Response(JSON.stringify({ status: 'ok', region: request.cf?.colo }), { headers: { 'Content-Type': 'application/json' }, }); } // 认证检查 if (CONFIG.AUTH_ENABLED && !isAuthenticated(request)) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' }, }); } // 速率限制检查 const clientIp = request.headers.get('CF-Connecting-IP') || 'unknown'; const rateLimitKey = `rate_limit:${clientIp}`; // 注意:这里的速率限制是简化版,实际使用需要 Durable Objects 或 KV // const isRateLimited = await checkRateLimit(rateLimitKey); // if (isRateLimited) { // return new Response(JSON.stringify({ error: 'Too Many Requests' }), { // status: 429, // headers: { 'Content-Type': 'application/json' }, // }); // } try { // 获取目标 URL const targetUrl = extractTargetUrl(request); if (!targetUrl) { return new Response(JSON.stringify({ error: 'Missing target URL' }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 验证目标 URL const validation = validateTargetUrl(targetUrl); if (!validation.valid) { return new Response(JSON.stringify({ error: validation.error }), { status: 400, headers: { 'Content-Type': 'application/json' }, }); } // 构建代理请求 const proxyRequest = new Request(targetUrl, { method: request.method, headers: buildProxyHeaders(request), body: request.body, redirect: 'follow', }); // 发送请求 const response = await fetch(proxyRequest); // 构建响应 const proxyResponse = new Response(response.body, { status: response.status, statusText: response.statusText, headers: buildResponseHeaders(response), }); return proxyResponse; } catch (error) { console.error('Proxy error:', error); return new Response(JSON.stringify({ error: 'Proxy Error', message: error.message }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } } // 认证检查 function isAuthenticated(request) { const authHeader = request.headers.get('Authorization'); if (!authHeader) return false; const match = authHeader.match(/^Bearer (.+)$/i); if (!match) return false; return match[1] === CONFIG.AUTH_TOKEN; } // 提取目标 URL function extractTargetUrl(request) { // 方式1:从请求头获取 const targetHeader = request.headers.get('X-Target-URL'); if (targetHeader) return targetHeader; // 方式2:从查询参数获取 const url = new URL(request.url); const targetParam = url.searchParams.get('target'); if (targetParam) return targetParam; // 方式3:从路径获取 const pathMatch = url.pathname.match(/^\/proxy\/(.+)/); if (pathMatch) { return decodeURIComponent(pathMatch[1]); } return null; } // 验证目标 URL function validateTargetUrl(targetUrl) { try { const url = new URL(targetUrl); // 检查协议 if (!['http:', 'https:'].includes(url.protocol)) { return { valid: false, error: 'Invalid protocol' }; } // 检查禁止的域名 if (CONFIG.BLOCKED_DOMAINS.includes(url.hostname)) { return { valid: false, error: 'Blocked domain' }; } // 检查允许的域名 if (CONFIG.ALLOWED_DOMAINS.length > 0 && !CONFIG.ALLOWED_DOMAINS.includes(url.hostname)) { return { valid: false, error: 'Domain not allowed' }; } return { valid: true }; } catch { return { valid: false, error: 'Invalid URL' }; } } // 构建代理请求头 function buildProxyHeaders(request) { const headers = new Headers(request.headers); // 删除 Cloudflare 特定的头 headers.delete('CF-Connecting-IP'); headers.delete('CF-IPCountry'); headers.delete('CF-RAY'); headers.delete('CF-Visitor'); // 删除代理相关的头 headers.delete('X-Target-URL'); headers.delete('Authorization'); // 添加转发头 headers.set('X-Forwarded-For', request.headers.get('CF-Connecting-IP') || 'unknown'); headers.set('X-Forwarded-Proto', 'https'); return headers; } // 构建响应头 function buildResponseHeaders(response) { const headers = new Headers(response.headers); // 添加 CORS 头 headers.set('Access-Control-Allow-Origin', '*'); headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Target-URL'); // 添加代理标识 headers.set('X-Proxy-By', 'Cloudflare Workers'); return headers; } // 处理 CORS 预检请求 function handleCORS(request) { const headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Target-URL', 'Access-Control-Max-Age': '86400', }; return new Response(null, { status: 204, headers }); } // 速率限制检查(简化版,实际使用需要 KV 或 Durable Objects) async function checkRateLimit(key) { // 这里需要使用 Cloudflare KV 或 Durable Objects 来实现 // 示例代码: // const count = await RATE_LIMIT_KV.get(key); // if (count && parseInt(count) >= CONFIG.RATE_LIMIT) { // return true; // } // await RATE_LIMIT_KV.put(key, (parseInt(count || '0') + 1).toString(), { expirationTtl: 60 }); return false; }