-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_worker.js
More file actions
217 lines (184 loc) · 8.81 KB
/
_worker.js
File metadata and controls
217 lines (184 loc) · 8.81 KB
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
export default {
async fetch(request, env, ctx) {
// ===================== 核心配置 =====================
const CONFIG = {
ENABLE_WHITELIST: (env.ENABLE_WHITELIST || "false") === "true", // 白名单启用开关:true=启用校验,false=关闭校验(允许所有域名)
DEFAULT_TARGET_HOST: env.DEFAULT_TARGET_HOST || "github.com", // 默认代理目标(GitHub)
DEFAULT_TARGET_PROTOCOL: env.DEFAULT_TARGET_PROTOCOL || "https" // 默认协议
};
CONFIG.ALLOWED_DOMAINS = (env.ALLOWED_DOMAINS || CONFIG.DEFAULT_TARGET_HOST || "")
.split(",")
.map(domain => domain.trim())
.filter(domain => domain && !domain.includes("*")) // 过滤空值+通配符
.filter((domain, index, self) => self.indexOf(domain) === index); // 去重
// ===================== 白名单校验工具函数(适配开关)=====================
function validateDomainWhitelist(hostname) {
// 若关闭白名单,直接通过(返回 undefined)
if (!CONFIG.ENABLE_WHITELIST) return;
// 开启白名单:校验白名单是否为空
if (CONFIG.ALLOWED_DOMAINS.length === 0) {
return new Response("白名单未设置(ALLOWED_DOMAINS 为空)", { status: 403 });
}
// 校验域名是否在白名单(支持子域名)
const isAllowed = CONFIG.ALLOWED_DOMAINS.some(allowed => hostname.endsWith(allowed));
if (!isAllowed) {
return new Response(
`禁止代理该域名(仅允许:${CONFIG.ALLOWED_DOMAINS.join(", ")})`,
{ status: 403 }
);
}
}
// ===================== 处理 HTTPS 代理的 CONNECT 隧道请求 =====================
if (request.method === "CONNECT") {
// 1. 提取目标 Host(格式:域名:端口,如 github.com:443)
const targetHost = request.headers.get("Host");
if (!targetHost) {
return new Response("CONNECT 请求缺少 Host 头", { status: 400 });
}
// 2. 解析目标端口(默认 HTTPS 端口 443)
const [targetHostname, targetPort] = targetHost.split(":");
const port = parseInt(targetPort) || 443;
// 3. 白名单校验(关键:接收返回值,校验失败直接响应)
const whitelistError = validateDomainWhitelist(targetHostname);
if (whitelistError) {
return whitelistError; // 白名单校验失败,直接返回 403
}
try {
// 5. 建立 TCP 隧道(使用 Cloudflare Workers 标准 API:connect 而非 Deno.connect)
const tcpSocket = await connect({ hostname, port });
// 6. 监听客户端关闭事件,主动释放 TCP 连接(避免资源泄漏)
request.signal.addEventListener("abort", () => {
tcpSocket.close().catch(err => console.error("关闭 TCP 连接失败:", err));
});
// 7. 返回隧道建立成功响应(标准 CONNECT 响应格式)
const response = new Response(null, {
status: 200,
statusText: "Connection Established",
headers: {
"Proxy-Agent": "Cloudflare-Workers-Proxy",
"Connection": "keep-alive",
"Proxy-Connection": "keep-alive" // 兼容部分客户端
}
});
// 8. 绑定 TCP 连接到响应(Worker 标准方法:withRedirect)
return response.withRedirect(tcpSocket);
} catch (error) {
console.error("CONNECT 隧道建立失败:", error);
return new Response(`CONNECT 隧道建立失败:${error.message}`, { status: 502 }); // 502 更符合网关错误语义
}
}
// ===================== 原有逻辑:保留默认代理和动态路径代理(适配白名单开关)=====================
const url = new URL(request.url);
// 处理 CORS 预检请求(包含 CONNECT 方法支持)
if (request.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, CONNECT",
"Access-Control-Allow-Headers": "*",
"Access-Control-Max-Age": "86400",
},
});
}
// 变量初始化
let targetHost, targetProtocol, targetUrl;
let isDynamicProxy = false;
// 检查是否是动态代理模式(路径中包含http://或https://)
const pathParts = url.pathname.split('/').filter(part => part);
if (pathParts.length > 0 && ['http:', 'https:'].includes(pathParts[0])) {
// 动态代理模式
isDynamicProxy = true;
targetProtocol = pathParts[0].replace(':', ''); // 'http' 或 'https'
targetHost = pathParts[1];
// 校验白名单(通过工具函数适配开关)
const whitelistError = validateDomainWhitelist(targetHost);
if (whitelistError) {
return whitelistError; // 白名单校验失败,直接返回 403
}
// 构建新的路径(去掉协议和域名部分)
const newPath = pathParts.slice(2).length > 0 ? `/${pathParts.slice(2).join('/')}` : '/';
// 构建目标URL
targetUrl = new URL(`${targetProtocol}://${targetHost}${newPath}${url.search}${url.hash}`);
} else {
// 默认代理GitHub模式(通过白名单工具函数校验,避免硬编码)
targetHost = CONFIG.DEFAULT_TARGET_HOST;
targetProtocol = CONFIG.DEFAULT_TARGET_PROTOCOL;
// 校验白名单(默认目标也需走校验,确保开关生效)
const whitelistError = validateDomainWhitelist(targetHost);
if (whitelistError) {
return whitelistError; // 白名单校验失败,直接返回 403
}
// 构建目标URL(使用原始路径)
targetUrl = new URL(`${targetProtocol}://${targetHost}${url.pathname}${url.search}${url.hash}`);
}
// 复制并修改请求头
const headers = new Headers(request.headers);
headers.set("Host", targetHost);
headers.set("Origin", `${targetProtocol}://${targetHost}`);
// 添加X-Forwarded-For头以保留客户端IP
const clientIP = request.headers.get("CF-Connecting-IP") || request.headers.get("X-Real-IP") || "";
if (clientIP) {
headers.set("X-Forwarded-For", clientIP);
}
// 移除可能引起问题的Cloudflare特定头
headers.delete("CF-IPCountry");
headers.delete("CF-RAY");
// 创建新请求
const newRequest = new Request(targetUrl, {
method: request.method,
headers: headers,
body: request.body,
duplex: request.duplex,
redirect: "manual", // 手动处理重定向
});
try {
// 尝试从缓存获取响应
const cache = caches.default;
const cachedResponse = await cache.match(request);
// 如果缓存存在且不是POST请求,直接返回缓存
if (cachedResponse && request.method !== "POST") {
const responseWithCORS = new Response(cachedResponse.body, cachedResponse);
responseWithCORS.headers.set("Access-Control-Allow-Origin", "*");
return responseWithCORS;
}
// 发送请求到目标服务器
const response = await fetch(newRequest);
// 处理重定向
if (response.redirected) {
const redirectedUrl = new URL(response.url);
if (isDynamicProxy) {
// 动态代理模式:将重定向URL转换为代理URL格式
const proxyRedirectUrl = new URL(request.url);
proxyRedirectUrl.pathname = `/${redirectedUrl.protocol}/${redirectedUrl.hostname}${redirectedUrl.pathname}`;
proxyRedirectUrl.search = redirectedUrl.search;
proxyRedirectUrl.hash = redirectedUrl.hash;
return Response.redirect(proxyRedirectUrl.toString(), response.status);
} else {
// 默认GitHub代理模式:保持在GitHub域名下重定向
if (redirectedUrl.hostname === CONFIG.DEFAULT_TARGET_HOST) {
redirectedUrl.hostname = url.hostname;
redirectedUrl.protocol = url.protocol;
return Response.redirect(redirectedUrl.toString(), response.status);
}
}
}
// 准备响应并添加CORS头
const responseWithCORS = new Response(response.body, response);
responseWithCORS.headers.set("Access-Control-Allow-Origin", "*");
// 缓存成功的GET请求(5分钟)
if (request.method === "GET" && response.ok) {
ctx.waitUntil(cache.put(request, responseWithCORS.clone()));
}
return responseWithCORS;
} catch (e) {
// 处理错误
return new Response(`代理请求失败: ${e.message}`, {
status: 500,
headers: {
"Content-Type": "text/plain",
"Access-Control-Allow-Origin": "*",
},
});
}
},
};