Files
ykxmz/_build/relay_chunked.py
T
xmz7891 b705216e4a ykxmz 1.5.0锛氫慨澶嶆墜琛ㄧ TLS 璇佷功鏍¢獙 + MTU 淇缃戠粶绋冲畾鎬?
琚帶绔?- 淇 MTU 闂锛氬す MSS + 璺緞 MTU 鎺㈡祴锛堝叕缃戝彲鐢ㄧ巼 48% -> 100%锛?- 澶嶇敤 HTTP 杩炴帴锛堝厤鎺夋瘡娆℃柊寤?TLS 鐨?30% 鎻℃墜澶辫触锛?- 姣忓垎閽熷洖澶存壘灞€鍩熺綉锛岃嚜鍔ㄥ垏鍥炲揩 400 鍊嶇殑璺緞
- 鐢婚潰蹇冭烦甯э細闈欐鏃剁渷娴侀噺锛屽鎴风浠嶇煡閬撴祦鏄椿鐨?- 閿佸睆鏃剁粰鍑哄彲鎿嶄綔鐨勬彁绀?
鎵嬭〃绔?- 鍐呯疆 GTS Root R4 / GlobalSign Root CA锛屼慨 SSLHandshakeException
- 杩炴帴閿欒缈昏瘧鎴愪汉璇濓紙璇佷功/鏃堕棿/瑙f瀽/瓒呮椂鍒嗗埆鎻愮ず锛?- 缁忎腑杞繛涓婂悗鐢ㄦ湇鍔″櫒鎶ョ殑鍦板潃鍒锋柊宸查厤瀵硅褰?- 鏂板灞€鍩熺綉杩炴帴椤甸潰銆佹湇鍔″櫒寤惰繜妫€娴?
鏈嶅姟鍣ㄧ
- 鏀寔 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())