Skip to content

fix(download): 下载链路走 ureq 自带的环境代理 + 补上读写超时 - #84

Open
std-external wants to merge 1 commit into
std-microblock:masterfrom
std-external:feat/download-proxy
Open

std-external wants to merge 1 commit into
std-microblock:masterfrom
std-external:feat/download-proxy

Conversation

@std-external

@std-external std-external commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

背景

社区反馈:接口(weg.fan)请求正常,但 Mod / Everest 文件下载卡住或失败。只读审查(v1.2.1,与 master d568b5b 相同)后有两处能解释:

  1. 下载链路完全不读代理:所有下载请求都经过 make_request(和 download_multi_thread 里的 HEAD),用的是 ureq 默认 agent,而 Cargo.toml 没开 proxy-from-env → proxy: None。前端的接口请求走 @tauri-apps/plugin-http(reqwest,默认吃环境代理),于是出现「接口走代理、下载硬直连」。
  2. 下载链路没有任何超时:grep -c timeout src-tauri/src/ureq.rs = 0,ureq 的默认 agent 是 timeout_connect: 30s, timeout_read: None, timeout_write: None。连接建立后服务端不再发数据,reader.read() 永远阻塞:不报错、不重试、进度停住;分段模式下 handles.join() 也因此永不返回。同仓库其它网络路径都设了超时(目录 60s、依赖图 20s)。

按 review 意见,不引入自定义设置项和独立的代理模块,直接使用 ureq 自带的 proxy-from-env。

改了什么(3 个文件,+82/−4)

src-tauri/Cargo.toml

-ureq = { version = "2", features = ["json", "gzip", "tls"] }
+ureq = { version = "2", features = ["json", "gzip", "tls", "proxy-from-env", "socks-proxy"] }

src-tauri/src/ureq.rs:加一个 download_agent(url),make_request 和分段下载的 HEAD 都改用它。

  • 非回环目标:AgentBuilder::new()...try_proxy_from_env(true) —— ALL_PROXY / HTTPS_PROXY / HTTP_PROXY(大小写都认),一个都没配就是直连,不需要任何设置项。
  • 回环目标(localhost / 127.0.0.0/8 / ::1):单独走一个显式 try_proxy_from_env(false) 的直连 agent。两个原因:本机地址不该被代理接管;而且开了 proxy-from-env 之后 AgentBuilder::new() 的默认值就是 true,不显式关掉的话直连 agent 也会被代理劫持(我本地就是这么被坑到的:测试里的本地 HTTP 服务器收不到请求,全挂在环境代理上,cargo test 在有 http_proxy 的机器上直接卡死)。
  • 读/写超时各 30s,连接超时沿用 ureq 默认的 30s。这是有意的行为变化:以前「慢但活着」的连接不会被判失败,现在若出现 30s 完全静默会被当成失败,交给已有的重试逻辑(先 probe_range 探测续传,失败回退单连接,最多 3 次),而不是永久挂住。
  • socks-proxy:不打开的话 ALL_PROXY=socks5://… 会在连接时报 SOCKS feature disabled(比直连失败更难懂)。不想要这个依赖的话去掉这个 feature 即可,socks 0.3.4 会从锁文件里消失。

验证

$ cargo test --lib
test result: ok. 108 passed; 0 failed        # 基线 107 passed + 新增 1 个
$ cargo fmt --check                          # 无输出

新增测试 loopback_targets_skip_the_proxy:127.0.0.1 / 127.1.2.3 / [::1] / localhost 走直连,gamebanana.com / 192.168.8.1 / localhost.example.com / 非法 URL 不走。既有的断线续传、Range 回退测试继续全绿(它们跑在 127.0.0.1 上,所以也能反过来证明回环直连生效)。

环境代理是否真的生效:另写了一个临时测试(未提交)——本地起一个假代理并把 http_proxy / HTTPS_PROXY 指向它,然后下载一个非回环 URL,断言代理收到的是 GET http://celeste.weg.fan/... HTTP/1.1(绝对 URI 请求行)、并且下载内容来自代理:

$ http_proxy=http://127.0.0.1:47890 HTTPS_PROXY=http://127.0.0.1:47890 cargo test --lib tmp_env_proxy
test backend::ureq::tests::tmp_env_proxy_is_used_for_non_loopback_targets ... ok

对没有代理的用户的影响

不设任何代理环境变量、系统也没开代理时:解析结果为空 → 直连,行为与改动前一致;唯一差别是新增的读/写超时。

遗留 / 没验证的点

  • 只读环境变量(ureq 自带能力就到这里)。Windows 上 Clash 那种「系统代理」开关写的是注册表、不写环境变量,这种情况本次覆盖不到;要覆盖得单独读系统设置。
  • 没有 no_proxy 支持(proxy-from-env 只做 Proxy::try_from_system),只保证回环地址直连。
  • 30s 读超时的取值没有实机验证过「慢速连接会不会被误伤」,改 ureq.rs 里的常量即可。
  • 开了 proxy-from-env 之后,ureq 的全局默认 agent 也会读环境代理,也就是说 everest.rs 的 Mod 目录接口和 backend.rs 的依赖图(都指向 celeste.weg.fan)现在也会走环境代理。这与前端 reqwest 的行为一致,但确实超出了下载链路的范围,如果需要保持它们直连,得在那两处也显式用 agent 构建。
  • 本次不动 useCnProxy 那条前端固定传 false、后端 let _ = use_cn_proxy 丢弃的死参数。

@std-external

Copy link
Copy Markdown
Contributor Author

本地验证记录(Linux + nightly,复用 /var/cache/orange-tmp/celemod-target 构建缓存):

$ cargo test --lib
test backend::net_proxy::tests::configured_agent_sends_requests_through_the_proxy ... ok
test backend::net_proxy::tests::environment_ignores_unusable_values ... ok
test backend::net_proxy::tests::environment_uses_scheme_proxy_then_all_proxy ... ok
test backend::net_proxy::tests::excluded_entries_are_parsed ... ok
test backend::net_proxy::tests::explicit_setting_beats_environment ... ok
test backend::net_proxy::tests::macos_settings_are_read_from_scutil_output ... ok
test backend::net_proxy::tests::no_proxy_and_loopback_keep_direct_connections ... ok
test backend::net_proxy::tests::proxy_urls_are_normalized ... ok
test backend::net_proxy::tests::windows_settings_are_read_from_registry_output ... ok
test result: ok. 116 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

其中 configured_agent_sends_requests_through_the_proxy 是「代理确实生效」的证据:本地起一个假代理,配了代理的 agent 发出去的是 GET http://gamebanana.com/file HTTP/1.1(绝对 URI 请求行,请求落到了代理,不是目标主机)。

跨平台分支的类型检查(这台机器是 Linux,没有 Windows/macOS 真机):把 net_proxy.rs 原样放进一个桩 crate(ureq / url / parking_lot 用同样签名的本地桩),对两个 target 做 cargo check --lib --tests:

$ cargo check --target x86_64-pc-windows-gnu --lib --tests   # exit=0, errors=0
$ cargo check --target x86_64-apple-darwin  --lib --tests   # exit=0, errors=0

说明:这只能证明平台分支能过类型检查,reg.exe / scutil 的真实输出没有在真机上验证过(解析逻辑是按常见输出格式写的单元测试)。

前端这次没有截图:vite 预览在没有 Tauri 运行时的浏览器里会白屏(__TAURI_INTERNALS__ 不存在),要出图得整包构建 Tauri 应用。设置页那一行就是「下载」区多了一个文本输入框 + 一行说明,tsc --noEmit 干净。

@std-microblock

Copy link
Copy Markdown
Owner

怎么写这么复杂 ureq自己没有支持系统代理的工具吗 加上就可以了吧 没必要加个单独的设置

…ead/write timeouts

下载链路(src-tauri/src/ureq.rs)之前用 ureq 的默认 agent:既不读代理,也没有读写
超时。前端接口走 reqwest(默认吃环境代理)能通,而文件下载是硬直连,于是网络受限时
会出现「接口正常、下载卡住」;即便连上了,ureq 默认没有读超时,服务端不再发数据时
读操作会一直阻塞,既不会报错也不会触发已有的重试。

- 打开 ureq 的 `proxy-from-env`:非回环目标的下载 agent 用
  `try_proxy_from_env(true)`,`ALL_PROXY` / `HTTPS_PROXY` / `HTTP_PROXY`(大小写都认)
  一个都没配时就是直连,不需要额外的设置项
- 回环地址(localhost / 127.0.0.0/8 / ::1)单独走直连 agent:本机地址不该被代理接管,
  而且开了这个 feature 之后 `AgentBuilder::new()` 默认就吃环境代理,必须显式关掉
- 补上 ureq 默认没有的读/写超时(各 30s,连接超时沿用 30s):连接建立后服务端长时间
  不发数据时读操作报错,交给已有的断点重试/单连接回退处理,而不是永久卡死
- 同时打开 `socks-proxy`:否则 `ALL_PROXY=socks5://…` 会在连接时报
  `SOCKS feature disabled`
@std-external std-external changed the title feat(download): 下载链路支持代理(设置/环境变量/系统代理)并补上读写超时 fix(download): 下载链路走 ureq 自带的环境代理 + 补上读写超时 Oct 9, 2026
@std-external

Copy link
Copy Markdown
Contributor Author

有道理,已经砍了:自定义设置项、net_proxy.rs 那个模块、前端/命令都删掉了,现在就是 ureq 自带的 proxy-from-env:

fn download_agent(url: &str) -> &'static ureq::Agent {
    // 非回环目标:.try_proxy_from_env(true)(ALL_PROXY / HTTPS_PROXY / HTTP_PROXY,没配就直连)
    // 回环目标:显式 .try_proxy_from_env(false) 的直连 agent
    // 两者都补 timeout_read / timeout_write = 30s(ureq 默认没有,这就是卡住不重试那条)
}

改动只剩 ureq.rs 里这一个函数 + Cargo.toml 两个 feature,diff 从 876 行降到 82 行。

两个坑顺便说一下:

  1. 开了 proxy-from-env 之后 AgentBuilder::new() 的默认值就是 try_proxy_from_env: true,所以直连 agent 也必须显式写 .try_proxy_from_env(false)。我第一版没写,结果回环地址也被环境代理接管 —— 本地测试服务器收不到请求,在有 http_proxy 的机器上 cargo test 直接卡死。现在回环地址固定直连,这条也顺手让测试在有代理的环境下能跑(本地 108 个测试全绿,cargo fmt --check 干净)。
  2. 顺手开了 socks-proxy:不然 ALL_PROXY=socks5://… 会在连接时报 SOCKS feature disabled,比直连失败更难排查。不想要这个依赖去掉这一行就行。

另外说明一下边界:这只读环境变量,Windows 上 Clash 那种「系统代理」开关(写注册表)覆盖不到,no_proxy 也不支持(ureq 的 try_from_system 就这些)。要覆盖系统代理得另外读注册表/scutil,可以再说。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants