feat(config): support GitHub proxy for release downloads via ureq middleware - #541
Conversation
There was a problem hiding this comment.
Hey - 我发现了两个问题,并给出了一些整体性的反馈:
github_proxy()访问器可以对返回值进行规范化处理(例如使用trim_end_matches('/')),这样像apply_github_proxy这样的调用方就不需要每次都记得自己去移除结尾的斜杠。github_proxy的测试用例把std::env::set_var/remove_var包裹在unsafe块中,这是不必要的,而且可能会让人困惑;你可以去掉unsafe,如果需要的话,把测试标记为串行执行以避免测试之间的相互干扰。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `github_proxy()` accessor could normalize the value (e.g., `trim_end_matches('/')`) so callers like `apply_github_proxy` don't all have to remember to strip trailing slashes themselves.
- The `github_proxy` tests wrap `std::env::set_var`/`remove_var` in `unsafe` blocks, which is unnecessary and may be confusing; you can drop the `unsafe` and, if needed, mark the test as serial to avoid cross-test interference.
## Individual Comments
### Comment 1
<location path="crates/maa-cli/src/installer/maa_core.rs" line_range="34-40" />
<code_context>
+ url.split('/').nth(2) == Some("github.com") && url.contains("/releases/download/")
+}
+
+fn apply_github_proxy(manifest: &mut VersionManifest<core::Details>, proxy: &str) {
+ let proxy = proxy.trim_end_matches('/');
+ for asset in &mut manifest.details.assets {
+ if is_github_release_url(&asset.browser_download_url) {
+ asset.browser_download_url = format!("{proxy}/{}", asset.browser_download_url);
+ }
+ for mirror in &mut asset.mirrors {
+ if is_github_release_url(mirror) {
+ *mirror = format!("{proxy}/{mirror}");
</code_context>
<issue_to_address>
**issue:** 需要防范空 proxy 值,以避免生成无效的 URL。
当 `github_proxy` 被配置为空字符串时,`CLI_CONFIG.github_proxy()` 会返回 `Some("")`,并且在调用 `trim_end_matches('/')` 之后,该值仍然是空的。此时 `apply_github_proxy` 会生成类似 `/https://github.com/...` 的 URL。请在调用该函数前将空的 proxy 视为 `None`,或者在这里当 `proxy` 为空时提前返回,以避免生成无效的 URL。
</issue_to_address>
### Comment 2
<location path="crates/maa-cli/src/config/cli/mod.rs" line_range="47-51" />
<code_context>
+ ///
+ /// Returns the value of the `MAA_GITHUB_PROXY` environment variable if set
+ /// and non-empty, otherwise falls back to the configured value.
+ pub fn github_proxy(&self) -> Option<String> {
+ std::env::var("MAA_GITHUB_PROXY")
+ .ok()
+ .filter(|s| !s.is_empty())
+ .or_else(|| self.github_proxy.clone())
+ }
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 规范空配置值,并考虑避免在 `github_proxy` 中进行重复分配。
当前实现会把配置中的 `github_proxy = ""` 当作一个真实的代理(`Some("")`),而环境变量则会忽略空字符串。建议对配置进行规范化,将空字符串视为 `None`,以保持行为一致。另外,这个实现每次调用都会分配一个新的 `String`;如果这是一个热路径,返回 `Option<&str>` 或 `Option<Cow<'_, str>>` 可以避免重复克隆。
建议实现:
```rust
/// Get the GitHub proxy prefix.
///
/// Returns the value of the `MAA_GITHUB_PROXY` environment variable if set
/// and non-empty, otherwise falls back to the configured value.
///
/// Empty strings from either the environment or the configuration are
/// treated as if no proxy were configured.
pub fn github_proxy(&self) -> Option<std::borrow::Cow<'_, str>> {
// Prefer a non-empty value from the environment (owned string)
if let Ok(env_val) = std::env::var("MAA_GITHUB_PROXY") {
if !env_val.is_empty() {
return Some(std::borrow::Cow::Owned(env_val));
}
}
// Fall back to a non-empty configured value, borrowed to avoid cloning
self.github_proxy
.as_deref()
.filter(|s| !s.is_empty())
.map(std::borrow::Cow::Borrowed)
```
1. 如果你更喜欢更短的类型名,可以在模块顶部添加 `use std::borrow::Cow;`,然后把函数签名和函数体中的 `std::borrow::Cow<'_, str>` 替换为 `Cow<'_, str>`。
2. 这个改动会把 `github_proxy` 的返回类型从 `Option<String>` 改为 `Option<Cow<'_, str>>`。所有目前期望 `Option<String>` 的调用点都需要更新:
- 如果它们只需要借用的字符串,使用 `if let Some(proxy) = cfg.github_proxy() { /* &proxy */ }`。
- 如果需要拥有所有权的 `String`,调用 `cfg.github_proxy().map(|c| c.into_owned())`。
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据这些反馈改进后续的评审。
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- The
github_proxy()accessor could normalize the value (e.g.,trim_end_matches('/')) so callers likeapply_github_proxydon't all have to remember to strip trailing slashes themselves. - The
github_proxytests wrapstd::env::set_var/remove_varinunsafeblocks, which is unnecessary and may be confusing; you can drop theunsafeand, if needed, mark the test as serial to avoid cross-test interference.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `github_proxy()` accessor could normalize the value (e.g., `trim_end_matches('/')`) so callers like `apply_github_proxy` don't all have to remember to strip trailing slashes themselves.
- The `github_proxy` tests wrap `std::env::set_var`/`remove_var` in `unsafe` blocks, which is unnecessary and may be confusing; you can drop the `unsafe` and, if needed, mark the test as serial to avoid cross-test interference.
## Individual Comments
### Comment 1
<location path="crates/maa-cli/src/installer/maa_core.rs" line_range="34-40" />
<code_context>
+ url.split('/').nth(2) == Some("github.com") && url.contains("/releases/download/")
+}
+
+fn apply_github_proxy(manifest: &mut VersionManifest<core::Details>, proxy: &str) {
+ let proxy = proxy.trim_end_matches('/');
+ for asset in &mut manifest.details.assets {
+ if is_github_release_url(&asset.browser_download_url) {
+ asset.browser_download_url = format!("{proxy}/{}", asset.browser_download_url);
+ }
+ for mirror in &mut asset.mirrors {
+ if is_github_release_url(mirror) {
+ *mirror = format!("{proxy}/{mirror}");
</code_context>
<issue_to_address>
**issue:** Guard against empty proxy values to avoid generating invalid URLs.
When `github_proxy` is configured as an empty string, `CLI_CONFIG.github_proxy()` returns `Some("")`, and after `trim_end_matches('/')` the value is still empty. `apply_github_proxy` will then produce URLs like `/https://github.com/...`. Please either treat an empty proxy as `None` before calling this function or early-return here when `proxy` is empty to avoid generating invalid URLs.
</issue_to_address>
### Comment 2
<location path="crates/maa-cli/src/config/cli/mod.rs" line_range="47-51" />
<code_context>
+ ///
+ /// Returns the value of the `MAA_GITHUB_PROXY` environment variable if set
+ /// and non-empty, otherwise falls back to the configured value.
+ pub fn github_proxy(&self) -> Option<String> {
+ std::env::var("MAA_GITHUB_PROXY")
+ .ok()
+ .filter(|s| !s.is_empty())
+ .or_else(|| self.github_proxy.clone())
+ }
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Normalize empty config values and consider avoiding repeated allocations in `github_proxy`.
This currently treats `github_proxy = ""` in the config as a real proxy (`Some("")`), unlike the env var, which ignores empty strings. Consider normalizing config so empty strings are treated as `None` for consistency. Also, this allocates a new `String` on every call; if it’s on a hot path, returning `Option<&str>` or `Option<Cow<'_, str>>` would avoid repeated cloning.
Suggested implementation:
```rust
/// Get the GitHub proxy prefix.
///
/// Returns the value of the `MAA_GITHUB_PROXY` environment variable if set
/// and non-empty, otherwise falls back to the configured value.
///
/// Empty strings from either the environment or the configuration are
/// treated as if no proxy were configured.
pub fn github_proxy(&self) -> Option<std::borrow::Cow<'_, str>> {
// Prefer a non-empty value from the environment (owned string)
if let Ok(env_val) = std::env::var("MAA_GITHUB_PROXY") {
if !env_val.is_empty() {
return Some(std::borrow::Cow::Owned(env_val));
}
}
// Fall back to a non-empty configured value, borrowed to avoid cloning
self.github_proxy
.as_deref()
.filter(|s| !s.is_empty())
.map(std::borrow::Cow::Borrowed)
```
1. If you prefer shorter type names, you can add `use std::borrow::Cow;` near the top of the module and then change the function signature and body to use `Cow<'_, str>` instead of `std::borrow::Cow<'_, str>`.
2. This change alters the return type of `github_proxy` from `Option<String>` to `Option<Cow<'_, str>>`. All call sites that currently expect an `Option<String>` will need to be updated:
- If they only need a borrowed string, use `if let Some(proxy) = cfg.github_proxy() { /* &proxy */ }`.
- If they require an owned `String`, call `cfg.github_proxy().map(|c| c.into_owned())`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #541 +/- ##
==========================================
+ Coverage 72.07% 72.34% +0.27%
==========================================
Files 72 74 +2
Lines 6679 6813 +134
Branches 6679 6813 +134
==========================================
+ Hits 4814 4929 +115
- Misses 1523 1538 +15
- Partials 342 346 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
我个人不是很喜欢使用使用环境变量来做配置,因为实现和测试起来比较麻烦。我想知道其他 CLI 使用类似的环境变量来做 GITHUB 的代理吗? 此外如果只是对于 Core releases 来做的话,是不是放在 Core 这个 section 里面更合适? |
其他cli确实每怎么见过专给github设置代理的变量的,因为都是外国开发者没怎么考虑过,都使用通用的http_proxy。 #[test]
fn env_via_subprocess() {
let exe = std::env::current_exe().unwrap();
let output = std::process::Command::new(exe)
.env("MAA_GITHUB_PROXY", "https://env/")
.arg("--test-threads=1")
.arg("subprocess_github_proxy")
.output()
.unwrap();
assert!(output.status.success());
}
#[test]
fn subprocess_github_proxy() {
// 这个测试只在子进程中运行
let config = CLIConfig::default();
assert_eq!(config.github_proxy(), Some("https://env".into()));
}我不是很喜欢一直开启代理软件。 |
|
我觉得可以提供这个功能,不过我倾向于要么用环境变量,要么用设置二选一,设置测试起来比较方便。此外我觉得,其实这个可以用 ureq 的 milldeware 来做,可能这样比较方便, 而不是在设置里面到处打补丁。 |
327b543 to
f452e5c
Compare
…dleware Add GitHubProxyMiddleware in state.rs that transparently rewrites github.com release download URLs through the configured proxy prefix. The middleware is injected into the global AGENT at construction time, covering all downloads: maa-core install/update, CLI self-update, and hot-update. Configuration is via cli.toml only: github_proxy = "https://gh-proxy.org/"
592fc8e to
73bae7b
Compare
|
@wangl-cc 已按照你的建议用 ureq middleware 重新实现了:
|
AGENT previously read the proxy from CLI_CONFIG at construction time, which forced CLI_CONFIG initialization on any agent user (e.g. config import tests). Under the core_installer-only feature set, the sample cli.toml fails to parse (backend = "libgit2" without the git2 feature), panicking the whole test run. Read the proxy from a dedicated GITHUB_PROXY LazyLock that treats parse errors as disabled, keeping AGENT usable in all feature combinations.
|
LGTM,感谢贡献!! |
de9c4d9
功能说明
支持通过配置文件
cli.toml中的github_proxy字段设置 GitHub 代理前缀,所有通过 ureq 的 GitHub release 下载将被透明改写走代理。使用方式
实现方式
采用 ureq middleware 方案(感谢 @wangl-cc 的建议),在全局
AGENT构造时注入GitHubProxyMiddleware,对 host 为github.com且路径含/releases/download/的请求透明改写 URL。覆盖所有通过 AGENT 的下载:maa-core、CLI 自更新、hot-update。修改文件
crates/maa-cli/src/state.rs:新增rewrite_github_url()+GitHubProxyMiddleware,AGENT 构造时条件注入crates/maa-cli/src/config/cli/mod.rs:新增github_proxy配置字段及github_proxy()方法crates/maa-cli/config_examples/cli.toml:新增github_proxy配置示例及注释