@codelook/proxy-server
Version:
HTTP/HTTPS proxy server with Docker deployment and Cloudflare Workers support
231 lines (194 loc) • 6.57 kB
JavaScript
/**
* 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;
}