Files
ykxmz/_build/relay_chunked.py
T
xmz7891 b62ac865ea ykxmz 1.5.0:修复手表端 TLS 证书校验 + MTU 修复网络稳定性
被控端
- 修复 MTU 问题:夹 MSS + 路径 MTU 探测(公网可用率 48% -> 100%)
- 复用 HTTP 连接(免掉每次新建 TLS 的 30% 握手失败)
- 每分钟回头找局域网,自动切回快 400 倍的路径
- 画面心跳帧:静止时省流量,客户端仍知道流是活的
- 锁屏时给出可操作的提示

手表端
- 内置 GTS Root R4 / GlobalSign Root CA,修 SSLHandshakeException
- 连接错误翻译成人话(证书/时间/解析/超时分别提示)
- 经中转连上后用服务器报的地址刷新已配对记录
- 新增局域网连接页面、服务器延迟检测

服务器端
- 支持 chunked 请求体(修每 16 秒一次的连接错乱与大面积 502)
- 网络调优脚本 net-tune.sh(开机自动应用)
- 管理页:批量删除、网络体检、下载区
- 隧道改 HTTP/2 + 双连接

文档
- 许可协议 / 安装前必读 / 安装后须知(作者:小麦子)
2026-10-06 19:01:05 +08:00

149 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""中转支持 chunked 请求体 —— 这是 16 秒刷一次 400 / 大面积 502 的根因。
现象链:
1. Cloudflare 转发请求时可能用 Transfer-Encoding: chunked
(它拿不到 Content-Length 时就会这么干)
2. 中转的 _body() 只读 Content-Length,遇到 chunked 直接返回 b""
3. **chunked 的数据留在 socket 里没被读走**
4. 下一个请求解析时,第一行读到的是上个请求的 chunk 结束标记 "0"
5. BaseHTTPRequestHandler 报 "Bad request syntax ('0')",整条连接错位
6. cloudflared 看到"空闲连接上冒出一个 400 响应" -> Unsolicited response
7. 隧道到源站的连接废掉 -> 502 / 530
证据(设备上的隧道日志,每 16 秒刷一次,正好是长轮询间隔):
Unsolicited response received on idle HTTP channel starting with
"<!DOCTYPE HTML>... <p>Error code: 400</p>
<p>Message: Bad request syntax ('0').</p>..."
修法:_body() 支持 chunked,按标准解码,并且**一定要把数据读干净**
(不然残留物还是会污染下一条请求)。
"""
from __future__ import annotations
import sys
from pathlib import Path
RELAY = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay" r"\relay.py")
OLD = ''' def _body(self) -> bytes:
try:
length = int(self.headers.get("Content-Length") or 0)
except ValueError:
return b""
if length <= 0:
return b""
if length > MAX_BODY:
return b""
return self.rfile.read(length)'''
NEW = ''' def _body(self) -> bytes:
"""读请求体。支持 Content-Length 和 chunked 两种。
为什么必须支持 chunked:
Cloudflare 转发时如果拿不到原始 Content-Length,
就会用 Transfer-Encoding: chunked。
而 Python 的 BaseHTTPRequestHandler **不会**自动解码 chunked。
不读走的话,残留的 "0\\r\\n\\r\\n" 会被当成下一个请求行,
报 "Bad request syntax ('0')",整条连接错位 ——
表现就是隧道那边不停 502/530,可用率上不去。
"""
transfer = (self.headers.get("Transfer-Encoding") or "").lower()
if "chunked" in transfer:
return self._body_chunked()
try:
length = int(self.headers.get("Content-Length") or 0)
except ValueError:
return b""
if length <= 0:
return b""
if length > MAX_BODY:
# 超限也要读掉,否则残留数据同样会污染下一条请求
self._drain(length)
return b""
return self._read_exact(length)
def _read_exact(self, length: int) -> bytes:
"""保证读满 length 字节(rfile.read 在慢连接上可能读不满)。"""
chunks = []
remaining = length
while remaining > 0:
piece = self.rfile.read(remaining)
if not piece:
break
chunks.append(piece)
remaining -= len(piece)
return b"".join(chunks)
def _drain(self, length: int) -> None:
"""把多余的请求体读掉丢弃,避免污染下一个请求。"""
remaining = min(length, MAX_BODY * 2)
while remaining > 0:
piece = self.rfile.read(min(remaining, 65536))
if not piece:
break
remaining -= len(piece)
def _body_chunked(self) -> bytes:
"""按 RFC 7230 解码 chunked 请求体。
注意最后那个 0 长度的 chunk 后面还有一段 trailer(通常为空),
一定要读掉 —— 残留就是 "Bad request syntax ('0')" 的来源。
"""
data = bytearray()
while True:
line = self.rfile.readline(256)
if not line:
break
line = line.strip()
if not line:
continue
size_text = line.split(b";")[0].strip()
try:
size = int(size_text, 16)
except ValueError:
break
if size <= 0:
# 末尾 trailer:读到空行为止
while True:
trailer = self.rfile.readline(256)
if not trailer or trailer.strip() == b"":
break
break
if len(data) + size > MAX_BODY:
self._drain(size + 2)
break
data += self._read_exact(size)
self.rfile.readline(8) # 每个 chunk 结尾的 CRLF
return bytes(data)'''
def main() -> int:
text = RELAY.read_text(encoding="utf-8")
if "_body_chunked" in text:
print(" 已经修过")
return 0
if OLD not in text:
print(" [失败] 锚点没找到")
for index, line in enumerate(text.splitlines(), 1):
if "def _body" in line:
print(f" 实际 {index}: {line}")
return 1
text = text.replace(OLD, NEW, 1)
RELAY.write_text(text, encoding="utf-8", newline="\n")
import ast
try:
ast.parse(text)
print(" 已修:中转现在支持 chunked 请求体")
except SyntaxError as exc:
print(f" 语法错误: 行 {exc.lineno}: {exc.msg}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())