1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
| // 需要反代的网站域名 const upstream = 'example.com';
// 自定义的路径(可选,默认根路径) const upstream_path = '/';
// 是否启用HTTPS协议 const https = true;
// 是否禁用缓存 const disable_cache = false;
// 最大可处理的HTML响应体大小(5MB) const MAX_HTML_SIZE = 5 * 1024 * 1024;
// 替换文本规则(留空即可,不需要改) const replace_dict = { // '旧域名': '新域名', };
// 监听Fetch事件 addEventListener('fetch', event => { event.respondWith(fetchAndApply(event.request)); });
// 主逻辑 async function fetchAndApply(request) { let url = new URL(request.url); const url_hostname = url.hostname; const proxy_protocol = https ? 'https:' : 'http:';
// 设置协议 url.protocol = proxy_protocol; // 设置目标域名 url.host = upstream; // 处理路径拼接(修正双斜杠问题) if (upstream_path !== '/') { const cleanUpstreamPath = upstream_path.replace(/\/+$/, ''); url.pathname = cleanUpstreamPath + url.pathname; }
// 设置请求头 const method = request.method; const new_request_headers = new Headers(request.headers); // 修改 Host 为目标域名 new_request_headers.set('Host', upstream); new_request_headers.delete('Origin');
// WebSocket 连接处理 const upgradeHeader = new_request_headers.get('Upgrade'); const isWebSocket = upgradeHeader && upgradeHeader.toLowerCase() === 'websocket'; if (isWebSocket) { return fetch(url.href, { method, headers: new_request_headers }); }
// 使用 manual 模式处理重定向 const fetchOptions = { method, headers: new_request_headers, redirect: 'manual' };
// 处理请求体(POST/PUT 等) if (method !== 'GET' && method !== 'HEAD') { fetchOptions.body = request.body; }
// 发起请求 const original_response = await fetch(url.href, fetchOptions);
// 处理重定向 if ([301, 302, 303, 307, 308].includes(original_response.status)) { const location = original_response.headers.get('Location'); if (location) { let newLocation = location; try { const redirectUrl = new URL(location, url.href); if (redirectUrl.host === upstream) { redirectUrl.host = url_hostname; redirectUrl.protocol = proxy_protocol.slice(0, -1); newLocation = redirectUrl.toString(); } } catch (e) { if (location.startsWith('/')) { newLocation = `${proxy_protocol}//${url_hostname}${location}`; } else { const basePath = url.pathname.substring(0, url.pathname.lastIndexOf('/') + 1); newLocation = `${proxy_protocol}//${url_hostname}${basePath}${location}`; } } const redirectHeaders = new Headers(original_response.headers); redirectHeaders.set('Location', newLocation); return new Response(null, { status: original_response.status, statusText: original_response.statusText, headers: redirectHeaders }); } }
// 克隆响应 const original_response_clone = original_response.clone(); let response_headers = new Headers(original_response.headers); let response_body = null;
// 缓存控制 if (disable_cache) { response_headers.set('Cache-Control', 'no-store, no-cache, must-revalidate'); }
// CORS 头 response_headers.set('Access-Control-Allow-Origin', '*'); response_headers.set('Access-Control-Allow-Credentials', 'true'); // 删除安全限制头 ['content-security-policy', 'content-security-policy-report-only', 'clear-site-data', 'x-frame-options'].forEach(header => { response_headers.delete(header); });
// 处理响应内容 const content_type = response_headers.get('content-type') || ''; const content_length = parseInt(response_headers.get('content-length') || '0'); const content_encoding = response_headers.get('content-encoding') || ''; let body_modified = false; if (Object.keys(replace_dict).length > 0 && content_type.includes('text/html') && !content_encoding && content_length < MAX_HTML_SIZE) { try { response_body = await replace_response_text(original_response_clone, upstream, url_hostname); body_modified = true; } catch (e) { response_body = original_response_clone.body; } } else { response_body = original_response_clone.body; }
if (body_modified) { response_headers.delete('content-length'); }
return new Response(response_body, { status: original_response.status, statusText: original_response.statusText, headers: response_headers }); }
// 替换响应文本 async function replace_response_text(response, upstream_domain, host_name) { let text = await response.text(); for (const [key, value] of Object.entries(replace_dict)) { let search = key; let replacement = value; if (key.includes('$upstream')) { search = search.replace(/\$upstream/g, upstream_domain); } if (key.includes('$custom_domain')) { search = search.replace(/\$custom_domain/g, host_name); } if (value.includes('$upstream')) { replacement = replacement.replace(/\$upstream/g, upstream_domain); } if (value.includes('$custom_domain')) { replacement = replacement.replace(/\$custom_domain/g, host_name); } const escapedSearch = search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); text = text.replace(new RegExp(escapedSearch, 'g'), replacement); } return text; }
|