码桶
发现社区成员的开源项目
_worker.js9.6 KB
export default {
async fetch(request, env, context) {
const url = new URL(request.url);
// ==========================================
// OPTIONS 预检:CORS 全开放
// ==========================================
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS, POST',
'Access-Control-Allow-Headers': '*',
'Access-Control-Max-Age': '86400',
}
});
}
const getErrorResponse = (statusCode, message) => {
return new Response(message, {
status: statusCode,
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
});
};
// ==========================================
// 1. 提取路径与兜底拦截
// ==========================================
let rawPath = url.pathname.slice(1);
if (!rawPath.includes('http:/') && !rawPath.includes('https:/')) {
return getErrorResponse(400, '目标 URL 解析失败,结构残缺。请检查请求地址。');
}
// ==========================================
// 2. 提取目标 URL 与伪装域名
// ==========================================
let customRefererHost = '';
const customHostRegex = /^([a-zA-Z0-9][-a-zA-Z0-9]{0,62}(?:\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+)\/(https?:\/.*)$/i;
const match = rawPath.match(customHostRegex);
let targetUrlStr;
if (match) {
customRefererHost = match[1];
targetUrlStr = match[2] + url.search;
} else {
targetUrlStr = rawPath + url.search;
}
// 规范化双斜杠 (避免 https:/ 导致的 URL 解析失败)
targetUrlStr = targetUrlStr.replace(/^(https?:\/)(?!\/)/i, '$1/');
let targetUrl;
try {
targetUrl = new URL(targetUrlStr);
} catch (e) {
return getErrorResponse(400, '目标 URL 解析失败,请检查请求地址。');
}
// ==========================================
// 3. 禁止代理自身
// ==========================================
if (targetUrl.hostname === url.hostname) {
return getErrorResponse(403, '禁止代理自身域名,请勿套娃。');
}
// ==========================================
// 4. 请求方法白名单
// ==========================================
if (!['GET', 'HEAD', 'POST'].includes(request.method)) {
return new Response('Method Not Allowed', { status: 405 });
}
// ==========================================
// 5. IP 提取与 Header 构建准备
// ==========================================
const clientIp =
request.headers.get('CF-Connecting-IP') ||
request.headers.get('X-Forwarded-For')?.split(',')[0].trim() ||
request.headers.get('X-Real-IP') ||
'Unknown_IP';
const upstreamIp = clientIp !== 'Unknown_IP' ? `${clientIp}, 127.0.0.1` : 'Not Sent';
const baseHeaders = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Connection': 'keep-alive'
};
const passHeaders = ['accept', 'accept-encoding', 'accept-language', 'range', 'content-type'];
const buildHeaders = (mode) => {
const headers = new Headers(baseHeaders);
const debug = { referer: '', ua: '' };
const clientUA = request.headers.get('User-Agent');
if (clientUA && clientUA.trim() !== '') {
headers.set('User-Agent', clientUA);
debug.ua = clientUA;
} else {
const fallback = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
headers.set('User-Agent', fallback);
debug.ua = fallback + ' (Fallback)';
}
for (const h of passHeaders) {
const v = request.headers.get(h);
if (v) headers.set(h, v);
}
if (clientIp !== 'Unknown_IP') {
headers.set('X-Forwarded-For', upstreamIp);
headers.set('X-Real-IP', clientIp);
}
if (customRefererHost) {
debug.referer = `https://${customRefererHost}/ (Forced by User)`;
headers.set('Referer', `https://${customRefererHost}/`);
headers.set('Origin', `https://${customRefererHost}`);
} else if (mode === 'disguise') {
debug.referer = targetUrl.origin + '/ (Auto Disguise Mode)';
headers.set('Referer', targetUrl.origin + '/');
headers.set('Origin', targetUrl.origin);
} else {
debug.referer = 'None (Clean Mode)';
}
return { headers, debug };
};
// 修复:严谨的重定向 URL 构建逻辑(处理相对路径与绝对路径)
const buildProxyUrl = (location, withDisguise) => {
// 算出上游真正的绝对 URL
const absoluteTarget = new URL(location, targetUrl).href;
const prefix = (withDisguise && customRefererHost)
? url.origin + '/' + customRefererHost + '/'
: url.origin + '/';
return prefix + absoluteTarget;
};
// ==========================================
// 6. 主请求逻辑(修复 POST 流重用问题)
// ==========================================
const FETCH_TIMEOUT = 30000;
const ANTI_HOTLINK_CODES = [403, 401, 451, 404];
// 如果是 POST 请求,提前 clone 一份流备用
let bodyForClean = null;
let bodyForDisguise = null;
if (request.method === 'POST') {
const [b1, b2] = request.body ? request.body.tee() : [null, null];
bodyForClean = b1;
bodyForDisguise = b2;
}
const fetchWithTimeout = async (bodyStream, headersDebug, timeoutMs) => {
const ctrl = new AbortController();
const tid = setTimeout(() => ctrl.abort(), timeoutMs);
const opts = {
method: request.method,
redirect: 'manual',
headers: headersDebug.headers,
signal: ctrl.signal,
};
if (request.method === 'POST') {
opts.body = bodyStream;
}
try {
const resp = await fetch(targetUrl.href, opts);
return { response: resp, debug: headersDebug.debug, aborted: false };
} catch (err) {
if (err.name === 'AbortError') {
return { response: null, debug: headersDebug.debug, aborted: true };
}
throw err;
} finally {
clearTimeout(tid);
}
};
try {
// 第一步:Clean 模式
const cleanHeaders = buildHeaders('clean');
let result = await fetchWithTimeout(bodyForClean, cleanHeaders, FETCH_TIMEOUT);
if (result.aborted) {
return getErrorResponse(504, '请求超时,目标服务器响应过慢。');
}
let finalDebug = result.debug;
let response = result.response;
let usedMode = 'clean';
// 第二步:智能重试(只在防盗链状态码且未自定义 Referer 时触发)
if (!customRefererHost && ANTI_HOTLINK_CODES.includes(response.status)) {
const disguiseHeaders = buildHeaders('disguise');
result = await fetchWithTimeout(bodyForDisguise, disguiseHeaders, FETCH_TIMEOUT);
if (result.aborted) {
return getErrorResponse(504, '请求超时,目标服务器响应过慢。');
}
finalDebug = result.debug;
response = result.response;
usedMode = 'disguise';
}
// ==========================================
// 7. 重定向改写
// ==========================================
if ([301, 302, 307, 308].includes(response.status)) {
const location = response.headers.get('Location');
if (location) {
const rewritten = buildProxyUrl(location, usedMode === 'disguise');
return new Response(null, {
status: response.status,
statusText: response.statusText,
headers: {
'Location': rewritten,
'Access-Control-Allow-Origin': '*',
'Access-Control-Expose-Headers': 'Location',
}
});
}
}
// ==========================================
// 8. 包装并输出最终响应
// ==========================================
const responseHeaders = new Headers();
// 注意:去掉了 content-encoding,防止 Cloudflare 解压后导致乱码
const headersToCopy = [
'content-type', 'content-length', 'content-range',
'accept-ranges', 'cache-control', 'expires',
'last-modified', 'etag', 'content-disposition'
];
for (const h of headersToCopy) {
const v = response.headers.get(h);
if (v) responseHeaders.set(h, v);
}
responseHeaders.set('Access-Control-Allow-Origin', '*');
responseHeaders.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS, POST');
responseHeaders.set('Access-Control-Allow-Headers', '*');
responseHeaders.delete('Set-Cookie');
responseHeaders.set('X-Debug-Fake-Referer', finalDebug.referer);
responseHeaders.set('X-Debug-Upstream-IP', upstreamIp);
responseHeaders.set('X-Debug-Upstream-UA', finalDebug.ua);
responseHeaders.set('X-Debug-Proxy-Status', `${usedMode} → Layer-1: ${response.status}`);
responseHeaders.set('Access-Control-Expose-Headers',
'X-Debug-Fake-Referer, X-Debug-Upstream-IP, X-Debug-Upstream-UA, X-Debug-Proxy-Status, Content-Range, Content-Disposition');
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders
});
} catch (error) {
return getErrorResponse(500, `网关异常: ${error.message}`);
}
}
};