M207: UA 收成一份带联系方式 + 两处字符串副本(gh_proxy 键、用例注释)
1) UA 只有一份定义:core 的 `download::http::USER_AGENT` (`Unlucky-Launcher/<CARGO_PKG_VERSION> (+仓库 URL)`,不用邮箱)。 - 引擎侧:`EngineConfig::default().user_agent` 直接取它(config.rs 等量替换,仍是 400 行) - 带 header 侧:presenter 不再持有 UA 字面量,`get_with_headers` 发 `engine.config().user_agent` M205 复审核实过的缺口:Modrinth 收到的是引擎那份 `UnluckyLauncher/<ver>`(无连字符、无联系方式), 带联系方式的那份只发给 CurseForge 搜索 —— Modrinth 的 UA 政策在真正会被限流的路径上没被满足。 断言:core 一条钉形状(版本 == CARGO_PKG_VERSION、联系方式段 == 仓库 URL、不含 `@`); presenter 一条在回环上抓**发出去的字节**(两条路径的 UA 逐字节相同且 == EngineConfig::default())。 2) `app-iced/.../java/downloads/engine.rs`:`"download.gh_proxy"` 字面量改成 `KEY_GH_PROXY`。 3) `presenter/src/host/addon/http.rs` 的用例注释改成事实(Modrinth 走 core 的 EngineTransport, `get` 的无 header 分支生产零调用者),模块说明表格同步。 4) `docs/download-settings.md`:§6.1 / §6.1.1 / §7 按 M201(iced 真控件 + 快照通路)之后的现状重写。
This commit is contained in:
1 parent
a853986283
commit
e3ae71ac37
5 files changed
+184
-91
No files matched your search
@@ -51,7 +51,7 @@ pub struct EngineConfig {
|
||||
pub connect_timeout: Duration,
|
||||
/// 单次读超时(读空闲判定,不是整体超时),默认 30s。
|
||||
pub read_timeout: Duration,
|
||||
/// User-Agent。
|
||||
/// User-Agent(唯一定义在 `http::USER_AGENT`;两条 HTTP 路径发的都是这一份,别在这里另拼)。
|
||||
pub user_agent: String,
|
||||
/// 进度事件节流间隔,默认 250ms。
|
||||
pub progress_throttle: Duration,
|
||||
@@ -70,7 +70,7 @@ impl Default for EngineConfig {
|
||||
max_attempts_per_source: DEFAULT_MAX_ATTEMPTS_PER_SOURCE,
|
||||
connect_timeout: Duration::from_secs(10),
|
||||
read_timeout: Duration::from_secs(30),
|
||||
user_agent: concat!("UnluckyLauncher/", env!("CARGO_PKG_VERSION")).to_string(),
|
||||
user_agent: super::http::USER_AGENT.to_string(),
|
||||
progress_throttle: Duration::from_millis(250),
|
||||
backoff: BackoffPolicy::default(),
|
||||
chunked: ChunkedConfig::default(),
|
||||
|
||||
@@ -18,6 +18,24 @@ use super::error::{DownloadError, DownloadErrorKind};
|
||||
/// 重定向上限(承 HMCL 的 20 跳)。
|
||||
pub const MAX_REDIRECTS: usize = 20;
|
||||
|
||||
/// 站点可见的 `User-Agent`:`<产品名>/<版本> (+<仓库 URL>)`。**全仓唯一一份**(M207)。
|
||||
///
|
||||
/// 三段各自有理由:
|
||||
/// * `Unlucky-Launcher` —— 连字符写法(与仓库名一致)。此前引擎那一份是 `UnluckyLauncher`,于是同一个
|
||||
/// 产品对 Modrinth 与 CurseForge 报两个名字;
|
||||
/// * 版本取 `CARGO_PKG_VERSION` —— 写死的话只会一年一年落后(M205 之前写死成 `0.1`,那时版本已是 0.2.3);
|
||||
/// * `(+https://github.com/AskaEth/Unlucky-Launcher)` —— Modrinth 的 UA 政策要求带联系方式,否则限流。
|
||||
/// 联系方式用**仓库 URL**、不用邮箱:这个字符串会出现在公开流量里。
|
||||
///
|
||||
/// 消费者两处,都是引用、不是副本:`EngineConfig::default().user_agent`([`super::config`] 取它),
|
||||
/// 与 [`build_client`] 的 `.user_agent(…)`(reqwest 客户端的默认 UA)。于是引擎的每一条请求——包括
|
||||
/// presenter 里 `engine.client()` 直发的那条——发的都是这一份。
|
||||
pub const USER_AGENT: &str = concat!(
|
||||
"Unlucky-Launcher/",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
" (+https://github.com/AskaEth/Unlucky-Launcher)"
|
||||
);
|
||||
|
||||
/// 构造共享的 `reqwest::Client`(连接池、超时、重定向策略一次定好)。
|
||||
pub fn build_client(cfg: &EngineConfig) -> Result<reqwest::Client, DownloadError> {
|
||||
let mut headers = HeaderMap::new();
|
||||
@@ -132,7 +150,7 @@ pub fn html_mirror_reason(
|
||||
mod tests {
|
||||
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE};
|
||||
|
||||
use super::{html_mirror_reason, MAX_REDIRECTS};
|
||||
use super::{html_mirror_reason, MAX_REDIRECTS, USER_AGENT};
|
||||
|
||||
fn headers(content_type: Option<&str>, length: Option<u64>) -> HeaderMap {
|
||||
let mut map = HeaderMap::new();
|
||||
@@ -180,4 +198,31 @@ mod tests {
|
||||
fn redirect_cap_is_still_twenty() {
|
||||
assert_eq!(MAX_REDIRECTS, 20);
|
||||
}
|
||||
|
||||
/// UA 的三段都得对:产品名连字符写法、版本 == `CARGO_PKG_VERSION`、联系方式是仓库 URL 且不是邮箱。
|
||||
///
|
||||
/// 为什么不是「编译过就行」:`concat!` 只保证拼得起来——版本写回字面量、`(+…)` 那一段被顺手删掉、
|
||||
/// 产品名改回无连字符写法,都照样编得过。而这条 UA 会真的发到 Modrinth(M207 之前发的不是它)。
|
||||
///
|
||||
/// 抓得住:版本写死(`0.1` / `0.2` / 与 `CARGO_PKG_VERSION` 不等的任何值)⇒ 红;产品名回到
|
||||
/// `UnluckyLauncher`(无连字符)⇒ 红;联系方式段被删、被改或换成邮箱 ⇒ 红。
|
||||
/// 抓不住:真实 Modrinth / CurseForge 收到这条 UA 之后怎么限流(单测不出网),也抓不住
|
||||
/// 「仓库 URL 那个仓库还在不在」——这里钉的是字符串形状,不是站点的接受度。
|
||||
#[test]
|
||||
fn the_user_agent_carries_the_package_version_and_the_repo_url() {
|
||||
let ua = USER_AGENT;
|
||||
let rest = ua
|
||||
.strip_prefix("Unlucky-Launcher/")
|
||||
.expect("产品名是连字符写法(与仓库名一致)");
|
||||
let (version, contact) = rest.split_once(' ').expect("版本之后是联系方式段");
|
||||
assert_eq!(version, env!("CARGO_PKG_VERSION"), "UA: {ua}");
|
||||
assert_eq!(
|
||||
contact, "(+https://github.com/AskaEth/Unlucky-Launcher)",
|
||||
"联系方式那一段是 Modrinth 的 UA 政策要求,不许顺手删:{ua}"
|
||||
);
|
||||
assert!(
|
||||
!ua.contains('@'),
|
||||
"联系方式用仓库 URL、不用邮箱(公开流量里不放个人联系方式):{ua}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user