Skip to content

Commit d6ecae9

Browse files
authored
Create _worker.js
1 parent 063e207 commit d6ecae9

1 file changed

Lines changed: 187 additions & 0 deletions

File tree

_worker.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
const WS_READY_STATE_OPEN = 1;
2+
const WS_READY_STATE_CLOSING = 2;
3+
const CF_FALLBACK_IPS = ['[2a00:1098:2b::1:6815:5881]'];
4+
5+
// 复用 TextEncoder,避免重复创建
6+
const encoder = new TextEncoder();
7+
8+
import { connect } from 'cloudflare:sockets';
9+
10+
export default {
11+
async fetch(request, env, ctx) {
12+
try {
13+
const token = '';
14+
const upgradeHeader = request.headers.get('Upgrade');
15+
16+
if (!upgradeHeader || upgradeHeader.toLowerCase() !== 'websocket') {
17+
return new URL(request.url).pathname === '/'
18+
? new Response('WebSocket Proxy Server', { status: 200 })
19+
: new Response('Expected WebSocket', { status: 426 });
20+
}
21+
22+
if (token && request.headers.get('Sec-WebSocket-Protocol') !== token) {
23+
return new Response('Unauthorized', { status: 401 });
24+
}
25+
26+
const [client, server] = Object.values(new WebSocketPair());
27+
server.accept();
28+
29+
handleSession(server).catch(() => safeCloseWebSocket(server));
30+
31+
// 修复 spread 类型错误
32+
const responseInit = {
33+
status: 101,
34+
webSocket: client
35+
};
36+
37+
if (token) {
38+
responseInit.headers = { 'Sec-WebSocket-Protocol': token };
39+
}
40+
41+
return new Response(null, responseInit);
42+
43+
} catch (err) {
44+
return new Response(err.toString(), { status: 500 });
45+
}
46+
},
47+
};
48+
49+
async function handleSession(webSocket) {
50+
let remoteSocket, remoteWriter, remoteReader;
51+
let isClosed = false;
52+
53+
const cleanup = () => {
54+
if (isClosed) return;
55+
isClosed = true;
56+
57+
try { remoteWriter?.releaseLock(); } catch {}
58+
try { remoteReader?.releaseLock(); } catch {}
59+
try { remoteSocket?.close(); } catch {}
60+
61+
remoteWriter = remoteReader = remoteSocket = null;
62+
safeCloseWebSocket(webSocket);
63+
};
64+
65+
const pumpRemoteToWebSocket = async () => {
66+
try {
67+
while (!isClosed && remoteReader) {
68+
const { done, value } = await remoteReader.read();
69+
70+
if (done) break;
71+
if (webSocket.readyState !== WS_READY_STATE_OPEN) break;
72+
if (value?.byteLength > 0) webSocket.send(value);
73+
}
74+
} catch {}
75+
76+
if (!isClosed) {
77+
try { webSocket.send('CLOSE'); } catch {}
78+
cleanup();
79+
}
80+
};
81+
82+
const parseAddress = (addr) => {
83+
if (addr[0] === '[') {
84+
const end = addr.indexOf(']');
85+
return {
86+
host: addr.substring(1, end),
87+
port: parseInt(addr.substring(end + 2), 10)
88+
};
89+
}
90+
const sep = addr.lastIndexOf(':');
91+
return {
92+
host: addr.substring(0, sep),
93+
port: parseInt(addr.substring(sep + 1), 10)
94+
};
95+
};
96+
97+
const isCFError = (err) => {
98+
const msg = err?.message?.toLowerCase() || '';
99+
return msg.includes('proxy request') ||
100+
msg.includes('cannot connect') ||
101+
msg.includes('cloudflare');
102+
};
103+
104+
const connectToRemote = async (targetAddr, firstFrameData) => {
105+
const { host, port } = parseAddress(targetAddr);
106+
const attempts = [null, ...CF_FALLBACK_IPS];
107+
108+
for (let i = 0; i < attempts.length; i++) {
109+
try {
110+
remoteSocket = connect({
111+
hostname: attempts[i] || host,
112+
port
113+
});
114+
115+
if (remoteSocket.opened) await remoteSocket.opened;
116+
117+
remoteWriter = remoteSocket.writable.getWriter();
118+
remoteReader = remoteSocket.readable.getReader();
119+
120+
// 发送首帧数据
121+
if (firstFrameData) {
122+
await remoteWriter.write(encoder.encode(firstFrameData));
123+
}
124+
125+
webSocket.send('CONNECTED');
126+
pumpRemoteToWebSocket();
127+
return;
128+
129+
} catch (err) {
130+
// 清理失败的连接
131+
try { remoteWriter?.releaseLock(); } catch {}
132+
try { remoteReader?.releaseLock(); } catch {}
133+
try { remoteSocket?.close(); } catch {}
134+
remoteWriter = remoteReader = remoteSocket = null;
135+
136+
// 如果不是 CF 错误或已是最后尝试,抛出错误
137+
if (!isCFError(err) || i === attempts.length - 1) {
138+
throw err;
139+
}
140+
}
141+
}
142+
};
143+
144+
webSocket.addEventListener('message', async (event) => {
145+
if (isClosed) return;
146+
147+
try {
148+
const data = event.data;
149+
150+
if (typeof data === 'string') {
151+
if (data.startsWith('CONNECT:')) {
152+
const sep = data.indexOf('|', 8);
153+
await connectToRemote(
154+
data.substring(8, sep),
155+
data.substring(sep + 1)
156+
);
157+
}
158+
else if (data.startsWith('DATA:')) {
159+
if (remoteWriter) {
160+
await remoteWriter.write(encoder.encode(data.substring(5)));
161+
}
162+
}
163+
else if (data === 'CLOSE') {
164+
cleanup();
165+
}
166+
}
167+
else if (data instanceof ArrayBuffer && remoteWriter) {
168+
await remoteWriter.write(new Uint8Array(data));
169+
}
170+
} catch (err) {
171+
try { webSocket.send('ERROR:' + err.message); } catch {}
172+
cleanup();
173+
}
174+
});
175+
176+
webSocket.addEventListener('close', cleanup);
177+
webSocket.addEventListener('error', cleanup);
178+
}
179+
180+
function safeCloseWebSocket(ws) {
181+
try {
182+
if (ws.readyState === WS_READY_STATE_OPEN ||
183+
ws.readyState === WS_READY_STATE_CLOSING) {
184+
ws.close(1000, 'Server closed');
185+
}
186+
} catch {}
187+
}

0 commit comments

Comments
 (0)