修复自查发现的门禁与体验问题:格式、超时态语义、压缩 CPU 边界
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m48s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m3s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m33s
Project CI / Frontend tests (pull_request) Successful in 3m24s
Project CI / Backend tests (pull_request) Successful in 7m29s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m55s
Project CI / Repository checks (pull_request) Successful in 6m33s
Project CI / Native shell tests (pull_request) Successful in 9m0s

- 新增 Rust 测试补齐 cargo fmt;ESLint 按仓库约定收口:常量与解析函数拆到 platformGameLoadingSurfaceModel.ts,删除 GAME_PLAY_STARTUP_TIMEOUT_MS 常量别名(测试直接用共享常量)
- 共享加载面新增 stalled:宿主判定等待不再推进时停掉进度动画并把 aria-busy 置回 false(去掉与 role=status 重复的 aria-live)
- 后台试玩超时时传入 stalled,避免「一边转圈一边说超时」
- Accept-Encoding 权重参数名改为大小写不敏感(RFC 9110 §12.5.3),GZIP;Q=0 视为拒绝并补用例
- 发行资源压缩分级:≥2 MiB 改用 level 1(zlib 端实测约 1/3 耗时、压缩比差约 3%),小文件仍用 level 6
- 文档:主规范补压缩与条件请求契约(并写明 60 秒窗口与撤销窗口不放宽)、nginx README 修正 Pingora 压缩关闭时的口径、pitfalls 补 CPU 边界与措辞修正
This commit is contained in:
2026-10-05 18:02:48 +08:00
parent f58f51052c
commit d7c4f82d7d
13 changed files with 138 additions and 69 deletions
@@ -927,11 +927,16 @@ fn accepts_gzip_encoding(header: Option<&HeaderValue>) -> bool {
return false;
}
!parts.any(|parameter| {
parameter
.trim()
.strip_prefix("q=")
.and_then(|value| value.trim().parse::<f32>().ok())
.is_some_and(|quality| quality <= 0.0)
// 权重参数名大小写不敏感(RFC 9110 §12.5.3):`Q=0` 与 `q=0` 一样是明确拒绝。
let parameter = parameter.trim();
let Some((name, value)) = parameter.split_once('=') else {
return false;
};
name.eq_ignore_ascii_case("q")
&& value
.trim()
.parse::<f32>()
.is_ok_and(|quality| quality <= 0.0)
})
})
}
@@ -945,8 +950,21 @@ fn is_compressible_release_content_type(content_type: &str) -> bool {
|| content_type.contains("application/wasm")
}
/// 超过这个体积改用更快的压缩级别。
///
/// 单文件上限是 64 MiB,而发行网关是公开无鉴权端点:release 构建下 level 6 实测
/// 1.3 MiB→10 ms、8 MiB→59 ms、64 MiB→522 ms 纯 CPU,每请求重算会占满 worker 线程。
/// 大文件改用 level 1(zlib 端实测约为 level 6 的 1/3 耗时、压缩比只差约 3%),
/// 小文件仍用 level 6 拿更好的比例。
const RELEASE_COMPRESSION_FAST_ABOVE_BYTES: usize = 2 * 1024 * 1024;
fn gzip_release_asset(content: &[u8]) -> Option<Vec<u8>> {
let mut encoder = GzEncoder::new(Vec::new(), GzipCompression::new(6));
let level = if content.len() >= RELEASE_COMPRESSION_FAST_ABOVE_BYTES {
GzipCompression::fast()
} else {
GzipCompression::new(6)
};
let mut encoder = GzEncoder::new(Vec::new(), level);
encoder.write_all(content).ok()?;
encoder.finish().ok()
}
@@ -4933,8 +4951,14 @@ mod tests {
#[test]
fn release_asset_etag_is_scoped_to_version_and_path() {
let etag = release_asset_etag("version_1", "assets/phaser.min.js");
assert_eq!(etag, release_asset_etag("version_1", "assets/phaser.min.js"));
assert_ne!(etag, release_asset_etag("version_2", "assets/phaser.min.js"));
assert_eq!(
etag,
release_asset_etag("version_1", "assets/phaser.min.js")
);
assert_ne!(
etag,
release_asset_etag("version_2", "assets/phaser.min.js")
);
assert_ne!(etag, release_asset_etag("version_1", "assets/game.js"));
assert!(etag.starts_with('"'), "ETag 必须是带引号的实体标记:{etag}");
}
@@ -4942,7 +4966,13 @@ mod tests {
#[test]
fn release_asset_conditional_request_matches_lists_and_weak_validators() {
let etag = "\"abc\"";
for accepted in ["*", "\"abc\"", "W/\"abc\"", "\"zzz\", \"abc\"", " W/\"abc\" "] {
for accepted in [
"*",
"\"abc\"",
"W/\"abc\"",
"\"zzz\", \"abc\"",
" W/\"abc\" ",
] {
assert!(
if_none_match_matches(
Some(&HeaderValue::from_str(accepted).expect("header")),
@@ -4967,17 +4997,21 @@ mod tests {
fn release_asset_gzip_acceptance_honours_quality_zero() {
for accepted in ["gzip", "GZIP", "*", "br, gzip;q=0.8", "deflate, gzip"] {
assert!(
accepts_gzip_encoding(Some(
&HeaderValue::from_str(accepted).expect("header")
)),
accepts_gzip_encoding(Some(&HeaderValue::from_str(accepted).expect("header"))),
"应接受 gzip:{accepted}"
);
}
for rejected in ["", "br", "gzip;q=0", "*;q=0.0", "identity"] {
for rejected in [
"",
"br",
"gzip;q=0",
"*;q=0.0",
"identity",
"gzip;Q=0",
"GZIP;Q=0.000",
] {
assert!(
!accepts_gzip_encoding(Some(
&HeaderValue::from_str(rejected).expect("header")
)),
!accepts_gzip_encoding(Some(&HeaderValue::from_str(rejected).expect("header"))),
"不应接受 gzip:{rejected}"
);
}
@@ -5125,9 +5159,15 @@ mod tests {
},
)
.expect("首次响应");
assert_eq!(served.headers().get(header::ETAG).expect("ETag"), etag.as_str());
assert_eq!(
served.headers().get(header::CACHE_CONTROL).expect("缓存策略"),
served.headers().get(header::ETAG).expect("ETag"),
etag.as_str()
);
assert_eq!(
served
.headers()
.get(header::CACHE_CONTROL)
.expect("缓存策略"),
cache_control
);
@@ -5145,7 +5185,10 @@ mod tests {
.expect("条件请求");
assert_eq!(not_modified.status(), StatusCode::NOT_MODIFIED);
assert_eq!(
not_modified.headers().get(header::CACHE_CONTROL).expect("缓存策略"),
not_modified
.headers()
.get(header::CACHE_CONTROL)
.expect("缓存策略"),
cache_control
);
assert_eq!(