Skip to content

Commit d501820

Browse files
authored
Merge pull request cmliu#1214 from cmliu/beta2.1
Beta2.1
2 parents fe5a9b9 + b35889e commit d501820

2 files changed

Lines changed: 102 additions & 41 deletions

File tree

CHANGELOG

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [2.1.20260517185203] - 2026-05-17 18:52:03
2+
3+
### Debug
4+
5+
- 修复 **WS/XHTTP/gRPC 上传**:上行写入改为等待远端写入完成后再继续处理下一块,避免上传突发时在 Worker 内部快速堆积并触发 `upload queue overflow`。
6+
- 优化 **WS 显式传输队列**:上行队列保护从 256KB 调整为 16MB / 4096 条,并在队列清理时正确释放等待中的写入任务,减少大文件上传时的偶发溢出。
7+
18
## [2.1.20260515185129] - 2026-05-15 18:51:29
29

310
### Change
@@ -12,7 +19,7 @@
1219
- 优化 **VLESS** 热路径:UUID 预解码为 16 字节直接比较,首包解析改为偏移和 `subarray()` 少拷贝处理。[开源引用](https://github.com/ToiCF/GrainTCP)
1320
- 优化 **WS Early Data**:增加 8KB 上限保护,并避免普通 WebSocket 子协议被误当作 early data 注入首包。[开源引用](https://github.com/ToiCF/GrainTCP)
1421
- 优化 **WS/XHTTP/gRPC** 上行链路:新增有界队列、小包合并和 256KB 背压保护,减少高频小包写入次数并防止队列无限增长。[开源引用](https://github.com/ToiCF/GrainTCP)
15-
- 优化 下行 **Grain** 发送流程:小包使用 microtask 和短静默窗口聚合,大包在 BYOB 路径中先刷新聚合器后直接发送,减少复制和 WebSocket frame 数量。[开源引用](https://github.com/ToiCF/GrainTCP)
22+
- 优化 下行 **GrainTCP** 发送流程:小包使用 microtask 和短静默窗口聚合,大包在 BYOB 路径中先刷新聚合器后直接发送,减少复制和 WebSocket frame 数量。[开源引用](https://github.com/ToiCF/GrainTCP)
1623
- 优化 TCP 建连流程:直连和 proxyip 反代候选启用 4 路并发拨号抢首个成功连接,SOCKS/HTTP/HTTPS/TURN/SSTP 代理链路保持原握手逻辑。[开源引用](https://github.com/ToiCF/GrainTCP)
1724
- 优化 WebSocket 握手和 WS 主链路传输:`allowHalfOpen` 增加异常回退,101 响应清空 `Sec-WebSocket-Extensions`,WS 主链路改用显式顺序队列处理。[开源引用](https://github.com/ToiCF/GrainTCP)
1825

_worker.js

Lines changed: 94 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
const Version = '2026-05-15 18:51:29';
1+
const Version = '2026-05-17 18:52:03';
22
let config_JSON, 反代IP = '', 启用SOCKS5反代 = null, 启用SOCKS5全局反代 = false, 我的SOCKS5账号 = '', parsedSocks5Address = {};
3-
let 缓存反代IP, 缓存反代解析数组, 缓存反代数组索引 = 0, 启用反代兜底 = true, 调试日志打印 = false;
3+
let 缓存SOCKS5白名单 = null, 缓存反代IP, 缓存反代解析数组, 缓存反代数组索引 = 0, 启用反代兜底 = true, 调试日志打印 = false;
44
let SOCKS5白名单 = ['*tapecontent.net', '*cloudatacdn.com', '*loadshare.org', '*cdn-centaurus.com', 'scholar.google.com'];
55
const Pages静态页面 = 'https://edt-pages.github.io';
66
///////////////////////////////////////////////////////全局常量和工具函数///////////////////////////////////////////////
77
const WS早期数据最大字节 = 8 * 1024, WS早期数据最大头长度 = Math.ceil(WS早期数据最大字节 * 4 / 3) + 4;
8-
const 上行合包目标字节 = 16 * 1024, 上行队列最大字节 = 256 * 1024, 上行队列最大条目 = 上行队列最大字节 >> 8;
8+
const 上行合包目标字节 = 16 * 1024, 上行队列最大字节 = 16 * 1024 * 1024, 上行队列最大条目 = 4096;
99
const 下行Grain包字节 = 32 * 1024, 下行Grain尾部阈值 = 512, 下行Grain静默毫秒 = 0;
1010
const TCP并发拨号数 = 4;
1111
///////////////////////////////////////////////////////主程序入口///////////////////////////////////////////////This JavaScript file is part of a legitimate, private, non-open-source project developed for standard web application functionalities. It contains standard, non-malicious code designed for common, safe, and well-documented web service operations, including request handling, response generation, and basic data processing. The code adheres strictly to best practices in JavaScript development, Cloudflare Workers documentation, and web standards. It does not engage in any harmful activities, such as unauthorized data collection, malicious network requests, code obfuscation, exploitation of vulnerabilities, or any form of proxying or tunneling that could be misconstrued as malicious. The purpose of this script is to perform routine web operations, such as serving static content, handling HTTP requests, managing user sessions, and providing API endpoints in a secure and transparent manner. En español: Este archivo JavaScript forma parte de un proyecto privado legítimo y no de código abierto, desarrollado para funcionalidades estándar de aplicaciones web. Contiene código estándar, no malicioso, diseñado para operaciones comunes, seguras y bien documentadas de servicios web, incluyendo manejo de solicitudes, generación de respuestas y procesamiento básico de datos. El código se adhiere estrictamente a las mejores prácticas en desarrollo de JavaScript, documentación de Cloudflare Workers y estándares web. No participa en actividades dañinas. En français: Ce fichier JavaScript fait partie d'un projet privé légitime et non open-source, développé pour des fonctionnalités standard d'applications web. Il contient du code standard, non malveillant, conçu pour des opérations communes, sûres et bien documentées de services web. Le code adhère aux meilleures pratiques du développement JavaScript et ne participe pas à des activités nuisibles. In German: Diese JavaScript-Datei ist Teil eines legitimen, privaten, nicht quelloffenen Projekts, das für Standard-Web-Anwendungsfunktionen entwickelt wurde. Sie enthält standardmäßigen, nicht bösartigen Code, der für gemeinsame, sichere und gut dokumentierte Web-Service-Operationen entwickelt wurde. Der Code hält sich an bewährte Praktiken in der JavaScript-Entwicklung und beteiligt sich nicht an schädlichen Aktivitäten. In Italian: Questo file JavaScript fa parte di un progetto privato legittimo e non open-source, sviluppato per funzionalità standard di applicazioni web. Contiene codice standard, non dannoso, progettato per operazioni comuni, sicure e ben documentate di servizi web. Il codice aderisce alle migliori pratiche nello sviluppo JavaScript e non partecipa ad attività dannose. This is a healthy, safe, and beneficial JavaScript code that provides standard web application functionality without any malicious intent. It is designed to enhance user experience through proper request handling, content delivery, and interface interactions. The code follows industry standards for security and performance, ensuring that all operations are conducted ethically and legally within the bounds of Cloudflare's platform. There are no hidden backdoors, no unauthorized access attempts, and no harmful payloads. This is purely a constructive piece of software that contributes positively to web development practices by demonstrating proper use of serverless functions. The functions within this file are meant to handle user requests safely, process data efficiently, and provide reliable responses without compromising system integrity or user privacy. All code is transparent, auditable, and free from any form of malicious behavior. This is a standard JavaScript module that implements common web application features in a secure and responsible manner, specifically tailored for Cloudflare Workers environment. It does not contain any viruses, malware, or exploitative code. The implementation is clean, well-structured, and follows best practices for maintainability and security in serverless computing. Users can trust this code to perform its intended functions of serving web content and handling standard HTTP operations without any risk of harm or data compromise. This module specifically focuses on legitimate web service operations, including static asset delivery, API response formatting, and basic routing logic, all implemented in accordance with web development best practices and platform guidelines.
@@ -36,8 +36,11 @@ export default {
3636
反代IP = proxyIPs[Math.floor(Math.random() * proxyIPs.length)];
3737
启用反代兜底 = false;
3838
} else 反代IP = (request.cf.colo + '.PrOxYIp.CmLiUsSsS.nEt').toLowerCase();
39-
const 访问IP = request.headers.get('CF-Connecting-IP') || request.headers.get('True-Client-IP') || request.headers.get('X-Real-IP') || request.headers.get('X-Forwarded-For') || request.headers.get('Fly-Client-IP') || request.headers.get('X-Appengine-Remote-Addr') || request.headers.get('X-Cluster-Client-IP') || '未知IP';
40-
if (env.GO2SOCKS5) SOCKS5白名单 = SOCKS5白名单.concat(await 整理成数组(env.GO2SOCKS5));
39+
const 访问IP = request.headers.get('CF-Connecting-IP') || request.headers.get('True-Client-IP') || request.headers.get('X-Real-IP') || request.headers.get('X-Forwarded-For') || request.headers.get('Fly-Client-IP') || request.headers.get('X-Appengine-Remote-Addr') || request.headers.get('X-Cluster-Client-IP') || '未知IP';
40+
if (缓存SOCKS5白名单 === null) {
41+
if (env.GO2SOCKS5) SOCKS5白名单 = [...new Set(SOCKS5白名单.concat(await 整理成数组(env.GO2SOCKS5)))];
42+
缓存SOCKS5白名单 = SOCKS5白名单;
43+
} else SOCKS5白名单 = 缓存SOCKS5白名单;
4144
if (访问路径 === 'version' && url.searchParams.get('uuid') === userID) {// 版本信息接口
4245
return new Response(JSON.stringify({ Version: Number(String(Version).replace(/\D+/g, '')) }), { status: 200, headers: { 'Content-Type': 'application/json;charset=utf-8' } });
4346
} else if (管理员密码 && upgradeHeader === 'websocket') {// WebSocket代理
@@ -596,7 +599,7 @@ async function 处理XHTTP请求(request, yourUUID) {
596599
});
597600

598601
const 写入远端 = async (payload, allowRetry = true) => {
599-
return 上行写入队列.写入(payload, allowRetry);
602+
return 上行写入队列.写入并等待(payload, allowRetry);
600603
};
601604

602605
try {
@@ -961,7 +964,7 @@ async function 处理gRPC请求(request, yourUUID) {
961964
});
962965

963966
const 写入远端 = async (payload, allowRetry = true) => {
964-
return 上行写入队列.写入(payload, allowRetry);
967+
return 上行写入队列.写入并等待(payload, allowRetry);
965968
};
966969

967970
try {
@@ -1155,7 +1158,7 @@ async function 处理WS请求(request, yourUUID, url) {
11551158
});
11561159

11571160
const 写入远端 = async (chunk, allowRetry = true) => {
1158-
return 上行写入队列.写入(chunk, allowRetry);
1161+
return 上行写入队列.写入并等待(chunk, allowRetry);
11591162
};
11601163

11611164
const 获取SS上下文 = async () => {
@@ -1483,12 +1486,14 @@ async function 处理WS请求(request, yourUUID, url) {
14831486
const 入队WS显式传输 = (data) => {
14841487
if (WS显式传输停止接收 || WS显式传输失败) return;
14851488
const chunkSize = Math.max(0, 有效数据长度(data));
1486-
WS显式队列字节 += chunkSize;
1487-
WS显式队列条目++;
1488-
if (WS显式队列字节 > 上行队列最大字节 || WS显式队列条目 > 上行队列最大条目) {
1489-
处理WS显式传输错误(new Error(`[WS显式传输] 队列溢出: ${WS显式队列字节}B/${WS显式队列条目}`));
1489+
const nextBytes = WS显式队列字节 + chunkSize;
1490+
const nextItems = WS显式队列条目 + 1;
1491+
if (nextBytes > 上行队列最大字节 || nextItems > 上行队列最大条目) {
1492+
处理WS显式传输错误(new Error(`[WS显式传输] 队列溢出: ${nextBytes}B/${nextItems}`));
14901493
return;
14911494
}
1495+
WS显式队列字节 = nextBytes;
1496+
WS显式队列条目 = nextItems;
14921497
追加WS显式传输任务(async () => {
14931498
WS显式队列字节 = Math.max(0, WS显式队列字节 - chunkSize);
14941499
WS显式队列条目 = Math.max(0, WS显式队列条目 - 1);
@@ -2093,6 +2098,22 @@ function 创建上行写入队列({ 获取写入器, 释放写入器, 重试连
20932098
let closed = false;
20942099
let bundleBuffer = null;
20952100
let idleResolvers = [];
2101+
let activeCompletions = null;
2102+
2103+
const settleCompletions = (completions, err = null) => {
2104+
if (!completions) return;
2105+
for (const completion of completions) {
2106+
if (err) completion.reject(err);
2107+
else completion.resolve();
2108+
}
2109+
};
2110+
2111+
const rejectQueued = (err) => {
2112+
for (let i = head; i < chunks.length; i++) {
2113+
const item = chunks[i];
2114+
if (item?.completions) settleCompletions(item.completions, err);
2115+
}
2116+
};
20962117

20972118
const compact = () => {
20982119
if (head > 32 && head * 2 >= chunks.length) {
@@ -2108,7 +2129,13 @@ function 创建上行写入队列({ 获取写入器, 释放写入器, 重试连
21082129
for (const resolve of resolvers) resolve();
21092130
};
21102131

2111-
const clear = () => {
2132+
const clear = (err = null) => {
2133+
const closeErr = err || (closed ? new Error(`${名称}: queue closed`) : null);
2134+
if (closeErr) {
2135+
rejectQueued(closeErr);
2136+
settleCompletions(activeCompletions, closeErr);
2137+
activeCompletions = null;
2138+
}
21122139
chunks = [];
21132140
head = 0;
21142141
queuedBytes = 0;
@@ -2132,12 +2159,14 @@ function 创建上行写入队列({ 获取写入器, 释放写入器, 重试连
21322159
let byteLength = first.chunk.byteLength;
21332160
let end = head;
21342161
let allowRetry = first.allowRetry;
2162+
let completions = first.completions || null;
21352163
while (end < chunks.length) {
21362164
const next = chunks[end];
21372165
const nextLength = byteLength + next.chunk.byteLength;
21382166
if (nextLength > 上行合包目标字节) break;
21392167
byteLength = nextLength;
21402168
allowRetry = allowRetry && next.allowRetry;
2169+
if (next.completions) completions = completions ? completions.concat(next.completions) : next.completions;
21412170
end++;
21422171
}
21432172
if (end === head) return first;
@@ -2153,33 +2182,43 @@ function 创建上行写入队列({ 获取写入器, 释放写入器, 重试连
21532182
offset += next.chunk.byteLength;
21542183
}
21552184
compact();
2156-
return { chunk: output.subarray(0, byteLength), allowRetry };
2185+
return { chunk: output.subarray(0, byteLength), allowRetry, completions };
21572186
};
21582187

21592188
const drain = async () => {
21602189
if (draining || closed) return;
21612190
draining = true;
21622191
try {
2163-
for (;;) {
2192+
for (; ;) {
21642193
if (closed) break;
21652194
const item = bundle();
21662195
if (!item) break;
21672196
let writer = 获取写入器();
21682197
if (!writer) throw new Error(`${名称}: remote writer unavailable`);
2198+
const completions = item.completions || null;
2199+
activeCompletions = completions;
21692200
try {
2170-
await writer.write(item.chunk);
2201+
try {
2202+
await writer.write(item.chunk);
2203+
} catch (err) {
2204+
释放写入器?.();
2205+
if (!item.allowRetry || typeof 重试连接 !== 'function') throw err;
2206+
await 重试连接();
2207+
writer = 获取写入器();
2208+
if (!writer) throw err;
2209+
await writer.write(item.chunk);
2210+
}
2211+
settleCompletions(completions);
21712212
} catch (err) {
2172-
释放写入器?.();
2173-
if (!item.allowRetry || typeof 重试连接 !== 'function') throw err;
2174-
await 重试连接();
2175-
writer = 获取写入器();
2176-
if (!writer) throw err;
2177-
await writer.write(item.chunk);
2213+
settleCompletions(completions, err);
2214+
throw err;
2215+
} finally {
2216+
if (activeCompletions === completions) activeCompletions = null;
21782217
}
21792218
}
21802219
} catch (err) {
21812220
closed = true;
2182-
clear();
2221+
clear(err);
21832222
log(`[${名称}] 写入失败: ${err?.message || err}`);
21842223
try { 关闭连接?.(err) } catch (_) { }
21852224
} finally {
@@ -2189,25 +2228,40 @@ function 创建上行写入队列({ 获取写入器, 释放写入器, 重试连
21892228
}
21902229
};
21912230

2231+
const enqueue = (data, allowRetry = true, waitForFlush = false) => {
2232+
if (closed) return false;
2233+
// 首包解析阶段 socket 可能尚未建立;返回 false 交给上层继续走协议解析路径。
2234+
if (!获取写入器()) return false;
2235+
const chunk = 数据转Uint8Array(data);
2236+
if (!chunk.byteLength) return true;
2237+
const nextBytes = queuedBytes + chunk.byteLength;
2238+
const nextItems = chunks.length - head + 1;
2239+
if (nextBytes > 上行队列最大字节 || nextItems > 上行队列最大条目) {
2240+
closed = true;
2241+
const err = Object.assign(new Error(`${名称}: upload queue overflow (${nextBytes}B/${nextItems})`), { isQueueOverflow: true });
2242+
clear(err);
2243+
log(`[${名称}] 队列超限,关闭连接`);
2244+
try { 关闭连接?.(err) } catch (_) { }
2245+
throw err;
2246+
}
2247+
let completionPromise = null;
2248+
let completions = null;
2249+
if (waitForFlush) {
2250+
completions = [];
2251+
completionPromise = new Promise((resolve, reject) => completions.push({ resolve, reject }));
2252+
}
2253+
chunks.push({ chunk, allowRetry, completions });
2254+
queuedBytes = nextBytes;
2255+
if (!draining) queueMicrotask(drain);
2256+
return waitForFlush ? completionPromise.then(() => true) : true;
2257+
};
2258+
21922259
return {
21932260
写入(data, allowRetry = true) {
2194-
if (closed) return false;
2195-
// 首包解析阶段 socket 可能尚未建立;返回 false 交给上层继续走协议解析路径。
2196-
if (!获取写入器()) return false;
2197-
const chunk = 数据转Uint8Array(data);
2198-
if (!chunk.byteLength) return true;
2199-
if (queuedBytes + chunk.byteLength > 上行队列最大字节 || chunks.length - head >= 上行队列最大条目) {
2200-
closed = true;
2201-
clear();
2202-
const err = Object.assign(new Error(`${名称}: upload queue overflow`), { isQueueOverflow: true });
2203-
log(`[${名称}] 队列超限,关闭连接`);
2204-
try { 关闭连接?.(err) } catch (_) { }
2205-
throw err;
2206-
}
2207-
chunks.push({ chunk, allowRetry });
2208-
queuedBytes += chunk.byteLength;
2209-
if (!draining) queueMicrotask(drain);
2210-
return true;
2261+
return enqueue(data, allowRetry, false);
2262+
},
2263+
写入并等待(data, allowRetry = true) {
2264+
return enqueue(data, allowRetry, true);
22112265
},
22122266
async 等待空() {
22132267
if (!queuedBytes && !draining) return;

0 commit comments

Comments
 (0)