Skip to content

feat: add route response cache MVP#65

Merged
vansour merged 1 commit intomainfrom
feat/cache-support-mvp
Apr 27, 2026
Merged

feat: add route response cache MVP#65
vansour merged 1 commit intomainfrom
feat/cache-support-mvp

Conversation

@vansour
Copy link
Copy Markdown
Owner

@vansour vansour commented Apr 27, 2026

Summary

  • add cache zone and route cache policy models across core/config/check output
  • implement the route-level HTTP response cache MVP with disk metadata/body storage and in-memory index
  • wire proxy lookup/store flow with X-Cache HIT/MISS/BYPASS/EXPIRED markers
  • document the cache support phase plan and current phase 0-3 completion status

Verification

  • cargo test --workspace --locked --quiet
  • cargo check --workspace --all-targets --message-format short
  • git diff --check

Copilot AI review requested due to automatic review settings April 27, 2026 10:43
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Warning

Rate limit exceeded

@vansour has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 29 minutes and 2 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: bea34f45-514c-41b4-ae38-d5210d4f7594

📥 Commits

Reviewing files that changed from the base of the PR and between 4300378 and adb9ff2.

📒 Files selected for processing (79)
  • CACHE_SUPPORT_PLAN.md
  • crates/rginx-app/src/check/render.rs
  • crates/rginx-app/src/check/routes.rs
  • crates/rginx-app/src/check/summary.rs
  • crates/rginx-config/src/compile/cache.rs
  • crates/rginx-config/src/compile/mod.rs
  • crates/rginx-config/src/compile/route.rs
  • crates/rginx-config/src/compile/tests.rs
  • crates/rginx-config/src/compile/tests/cache.rs
  • crates/rginx-config/src/compile/tests/http3.rs
  • crates/rginx-config/src/compile/tests/listeners.rs
  • crates/rginx-config/src/compile/tests/route.rs
  • crates/rginx-config/src/compile/tests/route/regex.rs
  • crates/rginx-config/src/compile/tests/server_settings.rs
  • crates/rginx-config/src/compile/tests/server_tls.rs
  • crates/rginx-config/src/compile/tests/upstream_defaults.rs
  • crates/rginx-config/src/compile/tests/upstream_fallbacks.rs
  • crates/rginx-config/src/compile/tests/upstream_server_name.rs
  • crates/rginx-config/src/compile/tests/upstream_tls.rs
  • crates/rginx-config/src/compile/tests/upstream_transport.rs
  • crates/rginx-config/src/compile/tests/vhosts.rs
  • crates/rginx-config/src/compile/tests/vhosts/listener_conflicts.rs
  • crates/rginx-config/src/compile/tests/vhosts/nezha.rs
  • crates/rginx-config/src/load/tests.rs
  • crates/rginx-config/src/model.rs
  • crates/rginx-config/src/model/cache.rs
  • crates/rginx-config/src/model/route.rs
  • crates/rginx-config/src/validate.rs
  • crates/rginx-config/src/validate/cache.rs
  • crates/rginx-config/src/validate/route.rs
  • crates/rginx-config/src/validate/tests.rs
  • crates/rginx-config/src/validate/tests/cache.rs
  • crates/rginx-config/src/validate/tests/route.rs
  • crates/rginx-config/src/validate/tests/vhosts.rs
  • crates/rginx-config/src/validate/vhost.rs
  • crates/rginx-core/src/config.rs
  • crates/rginx-core/src/config/cache.rs
  • crates/rginx-core/src/config/route.rs
  • crates/rginx-core/src/config/snapshot.rs
  • crates/rginx-core/src/config/tests.rs
  • crates/rginx-core/src/lib.rs
  • crates/rginx-http/Cargo.toml
  • crates/rginx-http/src/cache/entry.rs
  • crates/rginx-http/src/cache/load.rs
  • crates/rginx-http/src/cache/mod.rs
  • crates/rginx-http/src/cache/policy.rs
  • crates/rginx-http/src/cache/request.rs
  • crates/rginx-http/src/cache/store.rs
  • crates/rginx-http/src/cache/tests.rs
  • crates/rginx-http/src/handler/dispatch/mod.rs
  • crates/rginx-http/src/handler/dispatch/route.rs
  • crates/rginx-http/src/handler/tests/routing/authorize.rs
  • crates/rginx-http/src/handler/tests/routing/handle.rs
  • crates/rginx-http/src/handler/tests/routing/selection.rs
  • crates/rginx-http/src/handler/tests/support.rs
  • crates/rginx-http/src/lib.rs
  • crates/rginx-http/src/proxy/clients/tests.rs
  • crates/rginx-http/src/proxy/forward/attempt.rs
  • crates/rginx-http/src/proxy/forward/cache.rs
  • crates/rginx-http/src/proxy/forward/failure.rs
  • crates/rginx-http/src/proxy/forward/mod.rs
  • crates/rginx-http/src/proxy/forward/setup.rs
  • crates/rginx-http/src/proxy/forward/streaming.rs
  • crates/rginx-http/src/proxy/forward/success.rs
  • crates/rginx-http/src/proxy/forward/types.rs
  • crates/rginx-http/src/proxy/health/registry/tests.rs
  • crates/rginx-http/src/proxy/tests/cache.rs
  • crates/rginx-http/src/proxy/tests/mod.rs
  • crates/rginx-http/src/router/tests.rs
  • crates/rginx-http/src/state/helpers.rs
  • crates/rginx-http/src/state/lifecycle/reload.rs
  • crates/rginx-http/src/state/mod.rs
  • crates/rginx-http/src/state/snapshots/active.rs
  • crates/rginx-http/src/state/tests/support.rs
  • crates/rginx-http/src/state/tls_runtime/bindings/tests.rs
  • crates/rginx-http/src/transition/tests.rs
  • crates/rginx-runtime/src/bootstrap/listeners/tests.rs
  • crates/rginx-runtime/src/bootstrap/shutdown/tests.rs
  • crates/rginx-runtime/src/health/tests.rs
📝 Walkthrough

Walkthrough

为 rginx 引入可配置的反向代理响应缓存:包含配置模型、验证与编译、核心缓存类型与键模板、磁盘存储/索引加载、CacheManager 查找与存储、与代理转发流水线的集成及大量测试覆盖。

Changes

Cohort / File(s) Summary
规划文档
CACHE_SUPPORT_PLAN.md
新增分阶段实现规范,定义MVP边界、配置/校验/存储设计和后续阶段要点。
配置模型与反序列化
crates/rginx-config/src/model.rs, crates/rginx-config/src/model/cache.rs, crates/rginx-config/src/model/route.rs
新增 CacheZoneConfig/CacheRouteConfig,在 Config/LocationConfig 中加入 cache_zones/cache 字段(serde default)。
配置验证
crates/rginx-config/src/validate/*.rs
crates/rginx-config/src/validate/tests/*
新增缓存区与路由缓存验证逻辑(名称唯一、路径、数值范围、方法/状态/模板校验)并增加相应单测。
配置编译
crates/rginx-config/src/compile/cache.rs, crates/rginx-config/src/compile/...
将用户配置编译为运行时 CacheZone/RouteCachePolicy,解析/归一化方法、状态码、键模板,做溢出/范围检查。
核心类型与键模板
crates/rginx-core/src/config/cache.rs, .../route.rs, .../snapshot.rs, .../config.rs, .../lib.rs
新增 CacheZoneRouteCachePolicyCacheKeyTemplate 与渲染上下文;在 Route 和 ConfigSnapshot 中加入缓存相关字段并导出类型。
应用检查输出
crates/rginx-app/src/check/*.rs
扩展 rginx check 汇总以显示 cache_zonescache_enabled_routes 并打印分区详情。
HTTP 缓存实现
crates/rginx-http/src/cache/*, crates/rginx-http/Cargo.toml
实现 CacheManager、磁盘 entry/load/store、索引加载/回收、存储策略(TTL、大小、绕过规则)、并添加 serde_json 依赖与大量测试。
代理/转发集成
crates/rginx-http/src/proxy/forward/*, crates/rginx-http/src/handler/dispatch/*
在转发路径上做前置 lookup(命中直接返回),在上游成功后做条件存储,新增缓存上下文/状态传播与失败路径的缓存标记。
状态与生命周期
crates/rginx-http/src/state/*
将 CacheManager 纳入 PreparedState/SharedState,提供 lookup/store/mark 辅助方法,并在重载提交时更新缓存句柄。
存量测试与工装更新
多处 */tests/*(compile/validate/proxy/handler/state/...)
更新大量测试夹具以初始化 cache_zonesRoute.cache,并新增缓存单元/集成测试与针对损坏、限额、绕过等场景的用例。

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Dispatcher as Route Dispatcher
    participant CacheManager as Cache Manager
    participant Disk as Cache Storage
    participant Upstream as Upstream Server

    Client->>Dispatcher: HTTP Request (GET)
    Dispatcher->>CacheManager: lookup(request, scheme, policy)
    alt Cache Hit
        CacheManager->>Disk: read metadata & body
        Disk-->>CacheManager: cached response
        CacheManager-->>Dispatcher: Hit(response)
        Dispatcher-->>Client: 200 OK (x-cache: HIT)
    else Cache Miss/Bypass
        CacheManager-->>Dispatcher: Miss(store_ctx) / Bypass(status)
        Dispatcher->>Upstream: forward request
        Upstream-->>Dispatcher: upstream response
        alt Cacheable and store_ctx present
            Dispatcher->>CacheManager: store_response(store_ctx, response)
            CacheManager->>Disk: write metadata + body (atomic)
            Disk-->>CacheManager: write result
            CacheManager-->>Dispatcher: stored / error
            Dispatcher-->>Client: 200 OK (x-cache: MISS)
        else Not storable or bypass
            Dispatcher-->>Client: response (x-cache: BYPASS or MISS)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 我是爱跳跃的小兔子,写了缓存歌:
请求来时先敲门,键模版轻快转,
磁盘藏着往日光,索引守着每次返。
MISS 存下,HIT 归来,X-Cache 闪金牌,
小兔子笑着说:缓存上线,欢喜排排! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题"feat: add route response cache MVP"清晰且准确地概括了此拉取请求的主要变更——实现路由级HTTP响应缓存的MVP版本。
Description check ✅ Passed 描述详细阐述了缓存模型的添加、MVP实现、代理集成和文档编写,与代码变更内容完全相关。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cache-support-mvp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7555133a4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/rginx-http/src/cache/mod.rs Outdated
Comment thread crates/rginx-http/src/cache/mod.rs Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an MVP route-level HTTP response cache: new cache zone + route cache policy configuration flows through config load/validate/compile into runtime, and the proxy pipeline is updated to do cache lookup/store with X-Cache markers.

Changes:

  • Add cache zone + route cache policy models (core/config), plus config parsing/validation/compilation and rginx check summary output.
  • Implement an MVP response cache in rginx-http (disk metadata/body storage + in-memory index) and wire it into the proxy forwarding flow (HIT/MISS/BYPASS/EXPIRED).
  • Add tests covering config validation/compile and proxy/cache behavior, plus a cache support phase plan doc.

Reviewed changes

Copilot reviewed 71 out of 71 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/rginx-runtime/src/health/tests.rs Update snapshot construction for new cache_zones field.
crates/rginx-runtime/src/bootstrap/shutdown/tests.rs Update snapshot/route construction for new cache fields.
crates/rginx-runtime/src/bootstrap/listeners/tests.rs Update snapshot construction for new cache_zones field.
crates/rginx-http/src/transition/tests.rs Update snapshot construction for new cache_zones field.
crates/rginx-http/src/state/tls_runtime/bindings/tests.rs Update snapshot construction for new cache_zones field.
crates/rginx-http/src/state/tests/support.rs Update test snapshots/routes for new cache fields.
crates/rginx-http/src/state/mod.rs Add cache manager to shared state + module wiring.
crates/rginx-http/src/state/lifecycle/reload.rs Reload path updates cache manager on config reload.
crates/rginx-http/src/state/helpers.rs Build cache manager during state preparation.
crates/rginx-http/src/state/cache.rs SharedState cache lookup/store helpers.
crates/rginx-http/src/router/tests.rs Update route test fixtures for new Route.cache field.
crates/rginx-http/src/proxy/tests/mod.rs Add proxy cache test module + snapshot updates.
crates/rginx-http/src/proxy/tests/cache.rs Integration-style tests for MISS→HIT and BYPASS markers.
crates/rginx-http/src/proxy/health/registry/tests.rs Update snapshot construction for new cache_zones field.
crates/rginx-http/src/proxy/forward/types.rs Add downstream cache policy to request context/options.
crates/rginx-http/src/proxy/forward/success.rs Apply cache store / cache status marking on upstream success.
crates/rginx-http/src/proxy/forward/streaming.rs Signature update to pass downstream context by reference.
crates/rginx-http/src/proxy/forward/setup.rs Signature update to pass downstream context by reference.
crates/rginx-http/src/proxy/forward/attempt.rs Wire cache lookup before upstream attempt + cache store/status on outcomes.
crates/rginx-http/src/proxy/clients/tests.rs Update snapshot construction for new cache_zones field.
crates/rginx-http/src/lib.rs Expose new cache module within crate.
crates/rginx-http/src/handler/tests/support.rs Update test config/route helpers for cache fields.
crates/rginx-http/src/handler/tests/routing/selection.rs Update routing tests for new Route.cache field.
crates/rginx-http/src/handler/tests/routing/handle.rs Update routing tests for new Route.cache field.
crates/rginx-http/src/handler/tests/routing/authorize.rs Update routing tests for new Route.cache field.
crates/rginx-http/src/handler/dispatch/route.rs Pass route cache policy into downstream request options.
crates/rginx-http/src/handler/dispatch/mod.rs Carry route cache policy in execution context.
crates/rginx-http/src/cache/tests.rs Unit tests for cache manager/key/TTL/storability behavior.
crates/rginx-http/src/cache/mod.rs New cache implementation (disk entries + in-memory index + policy).
crates/rginx-http/Cargo.toml Add serde_json dependency for cache metadata encoding.
crates/rginx-core/src/lib.rs Re-export cache types (zone/policy/key template).
crates/rginx-core/src/config/tests.rs Update config tests for new snapshot/route cache fields.
crates/rginx-core/src/config/snapshot.rs Add cache_zones to runtime snapshot.
crates/rginx-core/src/config/route.rs Add Route.cache policy field.
crates/rginx-core/src/config/cache.rs New core cache model + cache key template parsing/rendering.
crates/rginx-core/src/config.rs Wire mod cache and re-export cache types.
crates/rginx-config/src/validate/vhost.rs Thread cache zone names through vhost validation.
crates/rginx-config/src/validate/tests/vhosts.rs Update vhost validation test fixtures for new route cache field.
crates/rginx-config/src/validate/tests/route.rs Update route validation test fixtures for new route cache field.
crates/rginx-config/src/validate/tests/cache.rs New validation tests for cache zones and route cache policy constraints.
crates/rginx-config/src/validate/tests.rs Add cache test module + base config now includes cache fields.
crates/rginx-config/src/validate/route.rs Validate cache handler compatibility + route cache references.
crates/rginx-config/src/validate/cache.rs New validators for cache zone definitions and route cache policy.
crates/rginx-config/src/validate.rs Validate cache zones and pass cache zone name set into location/vhost validation.
crates/rginx-config/src/model/route.rs Add LocationConfig.cache to config model.
crates/rginx-config/src/model/cache.rs New config model structs for cache zone + route cache policy.
crates/rginx-config/src/model.rs Add top-level cache_zones to config and re-export cache config types.
crates/rginx-config/src/load/tests.rs Ensure config loader deserializes cache zone + route cache policy.
crates/rginx-config/src/compile/tests/vhosts/nezha.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/vhosts/listener_conflicts.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/vhosts.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/upstream_transport.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/upstream_tls.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/upstream_server_name.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/upstream_fallbacks.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/upstream_defaults.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/server_tls.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/server_settings.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/route/regex.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/route.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/listeners.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/http3.rs Update compile test fixtures for new cache fields.
crates/rginx-config/src/compile/tests/cache.rs New compile tests verifying cache zones + route policy compilation and defaults.
crates/rginx-config/src/compile/tests.rs Add cache module + update common test location fixture.
crates/rginx-config/src/compile/route.rs Compile LocationConfig.cache into Route.cache.
crates/rginx-config/src/compile/mod.rs Compile cache zones into snapshot and wire new compile module.
crates/rginx-config/src/compile/cache.rs New compilation logic for cache zones and route cache policies.
crates/rginx-app/src/check/summary.rs Include cache zone + cache-enabled route counts in rginx check summary.
crates/rginx-app/src/check/routes.rs Track cache-enabled routes in route transport detail counts.
crates/rginx-app/src/check/render.rs Render cache zone details + cache metrics in check output.
CACHE_SUPPORT_PLAN.md Add cache support phased plan + status documentation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/rginx-http/src/cache/mod.rs Outdated
Comment thread crates/rginx-http/src/cache/mod.rs Outdated
Comment thread crates/rginx-http/src/cache/mod.rs Outdated
Comment thread crates/rginx-http/src/cache/mod.rs Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 27, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
crates/rginx-http/src/proxy/tests/mod.rs (1)

197-213: 🧹 Nitpick | 🔵 Trivial

可使用已导入的 HashMap

文件顶部第 3 行已 use std::collections::{HashMap, VecDeque};,第 202 行可直接写成 cache_zones: HashMap::new(),与同函数第 211 行已使用的 upstreamsHashMap)保持风格一致。无功能影响。

♻️ 建议的小幅修改
-        cache_zones: std::collections::HashMap::new(),
+        cache_zones: HashMap::new(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/rginx-http/src/proxy/tests/mod.rs` around lines 197 - 213, The
cache_zones field in snapshot_with_upstreams_map is constructed with a
fully-qualified std::collections::HashMap::new(); change it to use the
already-imported HashMap by replacing that expression with HashMap::new() so it
matches the style used for the upstreams parameter and other code; update the
initializer in function snapshot_with_upstreams_map (field cache_zones)
accordingly.
crates/rginx-http/src/handler/dispatch/mod.rs (1)

47-53: 🧹 Nitpick | 🔵 Trivial

建议考虑用 Arc 包装 RouteCachePolicy 以避免每请求克隆开销。

RouteExecutionContext 在每个进入路由的请求上都会构造一次,route.cache.clone() 也会随之执行(第 170 行)。RouteCachePolicy 内部包含 zone: Stringmethods: Vec<Method>statuses: Vec<StatusCode>key: CacheKeyTemplate(后者又包含 raw: Stringparts: Vec<CacheKeyTemplatePart>)。这些都是堆分配类型,每次克隆都会产生新的内存分配。

建议将 Route.cache 定义为 Option<Arc<RouteCachePolicy>>,或在 RouteExecutionContext 中持有 Option<Arc<RouteCachePolicy>>,使每请求路径仅做引用计数克隆,避免不必要的堆分配。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/rginx-http/src/handler/dispatch/mod.rs` around lines 47 - 53,
RouteExecutionContext currently clones RouteCachePolicy per request (via
route.cache.clone()), causing heavy heap allocations; change the type of the
cache to use reference-counting instead: make Route.cache (or the cache field
inside RouteExecutionContext) Option<Arc<RouteCachePolicy>> and update all call
sites to clone the Arc (Arc::clone) instead of deep-cloning RouteCachePolicy;
ensure you import std::sync::Arc and update any code that previously accessed
fields or constructed RouteCachePolicy to account for the Arc (dereference or
clone the Arc when needed) and replace route.cache.clone() with
Arc::clone(&route.cache.as_ref().unwrap()) or equivalent safe handling for the
Option.
crates/rginx-http/src/state/lifecycle/reload.rs (1)

117-128: ⚠️ Potential issue | 🟡 Minor

缓存管理器应在新配置对外可见前完成切换。

当前代码在 line 124 释放 self.inner.write() 临界区后,新的 revision/config/clients 立即对其他读者可见,但 self.cache 直到 line 126 才被更新。这个窗口内的请求会用新配置中的路由 cache 策略去查询旧的 CacheManager

若新配置新增或重命名了 cache zone,旧 CacheManager.zones 中不存在该名称,lookup() 会直接返回 CacheLookup::Bypass(见 crates/rginx-http/src/cache/mod.rs line 143-144)。反向场景(删除 zone)也会造成策略与存储不一致。

虽然窗口很短,但这是可避免的瑕疵——只需将 cache 的切换提前到 state.config = prepared.config.clone() 之前,或至少在同一 self.inner.write() 临界区内完成。建议调整为:先更新 self.cache,再更新 self.inner 中的配置,确保新配置对外暴露时缓存管理器已就位。

🔧 建议的顺序调整
+        // Swap cache before exposing the new config so route policies and
+        // the cache manager stay consistent for any concurrent request.
+        *self.cache.write().unwrap_or_else(|poisoned| poisoned.into_inner()) = prepared.cache;
+
         let next_revision = {
             let mut state = self.inner.write().await;
             let next_revision = state.revision + 1;
             state.revision = next_revision;
             state.config = prepared.config.clone();
             state.clients = prepared.clients;
             next_revision
         };
-
-        *self.cache.write().unwrap_or_else(|poisoned| poisoned.into_inner()) = prepared.cache;
         *self.listener_tls_acceptors.write().await = merged_acceptors;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/rginx-http/src/state/lifecycle/reload.rs` around lines 117 - 128, The
update sequence exposes new state.config/clients before swapping the cache; move
the cache swap so the CacheManager is updated before the new config becomes
visible: write prepared.cache into self.cache (the prepared.cache assignment
currently following the inner.write() block) either before modifying state
inside the same self.inner.write().await critical section or immediately before
entering it; then update state.revision, state.config, and state.clients inside
that same write lock, keep the listener acceptors assignment
(*self.listener_tls_acceptors.write().await = merged_acceptors) and the
revisions send (self.revisions.send(next_revision)) as-is after state and cache
are swapped so readers never observe a config that references an out-of-date
CacheManager.
crates/rginx-config/src/model.rs (1)

30-45: 🧹 Nitpick | 🔵 Trivial

建议:将 pub use cache::{...} 上移以保持与其他模块导出的一致性。

文件中所有其他子模块的 pub use 声明(listenerrouteruntimeservertlsupstreamvhost)都集中放置在 Config 结构体定义之前。本次新增的 pub use cache::{CacheRouteConfig, CacheZoneConfig}; 被放在了第 45 行(Config 定义之后),破坏了文件原有的“先 use、再 struct 定义”的组织约定,建议挪到与其他 pub use 同一区域。

♻️ 建议的调整
 pub use vhost::VirtualHostConfig;
+pub use cache::{CacheRouteConfig, CacheZoneConfig};

 #[derive(Debug, Clone, Deserialize)]
 pub struct Config {
@@
     #[serde(default)]
     pub servers: Vec<VirtualHostConfig>,
 }
-pub use cache::{CacheRouteConfig, CacheZoneConfig};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/rginx-config/src/model.rs` around lines 30 - 45, Move the pub
re-export for the cache types so it sits with the other module exports before
the Config struct: relocate the line `pub use cache::{CacheRouteConfig,
CacheZoneConfig};` to the block where the other `pub use` statements for
`listener`, `route`, `runtime`, `server`, `tls`, `upstream`, and `vhost` are
declared so that all module exports appear prior to the `Config` struct
definition.
crates/rginx-http/src/proxy/forward/attempt.rs (1)

1-346: 🛠️ Refactor suggestion | 🟠 Major

生产代码软上限超限,CI 已失败(modularization-gate)。

CI 报告 attempt.rs 共 346 行,超出 300 行软上限。建议把缓存装饰逻辑(mark_cache_status_if_needed 及缓存上下文构造)抽到独立子模块,例如 proxy/forward/cache.rs,可以同时降低本文件复杂度并把第 12–31 行与第 336–346 行集中管理。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/rginx-http/src/proxy/forward/attempt.rs` around lines 1 - 346, The
file is over the 300-line soft limit; extract the cache decoration logic into a
new submodule (e.g., proxy::forward::cache): move the helper fn
mark_cache_status_if_needed and the cache lookup/context construction around the
top of forward_request (the logic that creates cache_lookup, cache_store,
cache_status and the final call at the end) into that module (export a function
like cache::prepare_cache_context(request: &Request<HttpBody>, state:
&SharedState, downstream: &DownstreamRequestContext<'_>, cache_opt: Option<...>)
-> (Option<crate::cache::CacheStore>, Option<crate::cache::CacheStatus>) and
cache::wrap_response_with_status(state: &SharedState, response: HttpResponse,
cache_status: Option<crate::cache::CacheStatus>) -> HttpResponse), update
attempt.rs to call those two functions instead of inlining the logic, adjust
imports/uses (e.g., replace direct mark_cache_status_if_needed and the cache
construction with calls to cache::prepare_cache_context and
cache::wrap_response_with_status) and run cargo build/tests to ensure
visibility/paths and types (SharedState, HttpResponse,
crate::cache::CacheLookup/CacheStatus) are correct.
crates/rginx-http/src/proxy/forward/success.rs (1)

29-57: ⚠️ Potential issue | 🟡 Minor

在 upgrade 路径上仍调用 apply_cache_store,建议显式跳过缓存写入。

WebSocket / HTTP/1.1 upgrade 响应(通常是 101 Switching Protocols)的语义不应进入缓存流程。当前实现依赖 response_is_storable 通过状态码白名单过滤掉 101,但仍会进入 store_cached_responsestore_response,浪费一次锁与状态判断;更重要的是,一旦未来将 101 加入用户可配置 statuses,就会错误地尝试缓存一个已经被劫持成 raw I/O 的连接体。建议在此分支上仅应用 cache_status、丢弃 cache_store

♻️ 建议
-        return apply_cache_store(
-            context.state,
-            context.cache_store,
-            context.cache_status,
-            response,
-        )
-        .await;
+        // Upgraded connections must never be persisted to cache.
+        return apply_cache_store(context.state, None, context.cache_status, response).await;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/rginx-http/src/proxy/forward/success.rs` around lines 29 - 57, This
branch handles upgraded (101/connection-upgrade) responses; instead of calling
apply_cache_store (which may acquire locks and attempt store), return the built
response after only applying the cache_status and skipping any cache_store
logic: in the upgrade branch where (context.downstream_upgrade,
upstream_upgrade) are present, call a helper that applies just the cache_status
(e.g., apply_cache_status(state, cache_status, response)) or, if that helper
doesn't exist, modify apply_cache_store usage to pass a sentinel/no-op for
cache_store and ensure no store_cached_response/store_response path is executed;
update the branch around proxy_upgraded_connection, build_downstream_response
and the apply_cache_store call accordingly so upgrades never attempt to write
cached bodies.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/rginx-app/src/check/render.rs`:
- Around line 57-67: The loop printing cache zone details should be extracted
out of print_route_transport_details into a new function
print_cache_zone_details that takes the cache zone slice (or
&summary.cache_zones) and prints each zone exactly as currently done; update
print_route_transport_details to call print_cache_zone_details (or have
print_check_success call it directly) so responsibilities match names, and
ensure print_check_success invokes print_cache_zone_details after route
transport output to allow future extensions (e.g., health/capacity stats).

In `@crates/rginx-app/src/check/routes.rs`:
- Line 15: The count of cache-enabled routes is duplicated:
RouteTransportCheckDetails::cache_enabled_routes already computes it, but
CheckSummary::cache_enabled_route_count recomputes the same iteration; remove
the duplicate by deleting the cache_enabled_route_count() function/logic and
make CheckSummary use the precomputed value from
route_transport.cache_enabled_routes when constructing the summary (i.e.,
replace any recomputation with a direct reference to
route_transport.cache_enabled_routes so render/output code continues to read the
same field).

In `@crates/rginx-config/src/compile/cache.rs`:
- Around line 50-72: The parsed methods and statuses vectors (constructed from
cache.methods and cache.statuses and stored on RouteCachePolicy.methods) can
contain duplicates (e.g., "GET","get"," GET ") after parsing; after
mapping/parsing but before assigning, deduplicate the collections—either by
collecting into a HashSet then back to a Vec or by normalizing, sorting (or
using a stable dedupe) and calling dedup()—so RouteCachePolicy.methods and the
statuses Vec contain unique entries; ensure you perform this dedupe in the same
block that builds methods and statuses so response_is_storable and
cache_request_bypass still work but without redundant comparisons or duplicate
values in metrics/logs.

In `@crates/rginx-config/src/compile/tests/cache.rs`:
- Around line 122-143: Add assertions to the test that validate the default
fallback for the cache key template and stale_if_error behavior: after obtaining
policy from snapshot.default_vhost.routes[0].cache (via compile_with_base),
assert that policy.key equals DEFAULT_CACHE_KEY (the expected
"{scheme}:{host}:{uri}" template) when key was None, and assert that
policy.stale_if_error (or policy.stale_if_error_secs if that is the field name
used in compile_route_cache) is None when stale_if_error_secs was None;
reference the DEFAULT_CACHE_KEY constant and the policy.stale_if_error field to
locate the correct symbols to assert against.

In `@crates/rginx-config/src/load/tests.rs`:
- Around line 10-27: Update the test function
load_from_str_deserializes_cache_zones_and_route_cache_policy to add assertions
for the missing fields: for the cache zone (config.cache_zones[0]) assert
inactive_secs == Some(600) and default_ttl_secs == Some(60), and for the route
cache (the variable cache from config.locations[0].cache) assert key.as_deref()
== Some("{scheme}:{host}:{uri}"); locate these checks near the existing
assertions that verify name and max_entry_bytes so the test validates
CacheZoneConfig and CacheRouteConfig fully.

In `@crates/rginx-config/src/validate/cache.rs`:
- Around line 50-55: The error message echoes the untrimmed cache.zone which can
mislead users; change the code around the cache_zone_names.contains(zone) check
so the error uses the trimmed value (the same variable used for lookup) instead
of cache.zone, e.g. compute let trimmed = cache.zone.trim(); use
cache_zone_names.contains(trimmed) and when returning Err(Error::Config(...))
include the trimmed value or an explicit note about surrounding whitespace;
update the Error::Config call that currently formats "{route_scope} references
undefined cache zone `{}`" to reference the trimmed name (or mention "contains
leading/trailing whitespace") so lookup and diagnostics are consistent.

In `@crates/rginx-config/src/validate/tests/cache.rs`:
- Around line 57-78: The test function
validate_rejects_unsupported_cache_method_and_invalid_zone_limits mixes two
independent failure scenarios; split it into two separate #[test] functions: one
(e.g., validate_rejects_max_entry_bytes_exceeding_max_size_bytes) that builds
config with CacheZoneConfig { max_entry_bytes: Some(2*1024), max_size_bytes:
Some(1024), ..cache_zone("default") } and asserts validate(&config).expect_err()
contains "max_entry_bytes must not exceed max_size_bytes", and another (e.g.,
validate_rejects_unsupported_cache_method) that builds config with
cache_zone("default"), sets policy = route_cache("default") with policy.methods
= Some(vec!["POST".to_string()]) and asserts validate(&config).expect_err()
contains "cache method `POST` is not supported"; keep using validate(),
cache_zone(), route_cache(), and the same assertion strings.

In `@crates/rginx-http/src/cache/mod.rs`:
- Around line 280-307: The store_response function currently calls
body.collect().await unconditionally for responses lacking a reliable
Content-Length/size_hint, causing OOM for large chunked bodies; update
store_response to avoid full buffering: when response_is_storable cannot prove
the size, either treat the response as non-cacheable (return the response
without collecting) or perform an incremental read of the body (read chunks,
accumulate length, and as soon as accumulated_len >
context.zone.config.max_entry_bytes stop buffering any further bytes and mark as
non-cacheable while continuing to stream the chunks downstream). Locate
store_response and the earlier response_is_storable logic to implement the
chosen approach, ensuring you reference context.zone.config.max_entry_bytes and
response_ttl/parts when deciding to skip caching.
- Around line 463-467: The current logic makes expires_ttl() return
Some(Duration::ZERO) for past Expires headers which then wins over
cache_control_max_age() and prevents fallback to default_ttl; document this
intentional behavior and its operational impact by adding a clear comment in
mod.rs near expires_ttl() and in the routing/cache docs explaining that an
expired Expires header yields TTL=0 (no caching) and can block default_ttl until
the header changes; also reference the existing behavior/test
response_ttl_respects_explicit_zero_or_expired_freshness and suggest a short
note for ops about the risk of stale upstream timestamps.
- Around line 112-135: CacheManager::from_config currently only creates
directories so after restart the in-memory CacheIndex is empty causing
persistent orphaned files and broken size accounting; fix by adding a startup
disk-scan step (either inside from_config or called right after construction)
that for each zone (CacheZoneRuntime) scans its path for matching *.meta.json
and body files, reconstructs entries into CacheIndex and restores
current_size_bytes, deletes orphaned metas/bodies, and triggers eviction if the
reconstructed current_size_bytes exceeds max_size_bytes; ensure this logic is
coordinated with existing lookup and write_cache_entry semantics so writes still
overwrite matching on-disk items and the reconstructed index prevents silent
leaks.
- Around line 348-360: The comparison in cache_request_bypass currently takes a
reference of the right operand (methods.iter().any(|method| method ==
&request.method)), triggering Clippy's op_ref; replace that check with a direct
contains call on the policy.methods Vec (e.g., use
policy.methods.contains(&request.method)) or otherwise compare by value (e.g.,
*method == request.method) inside the iterator; update the condition in the
cache_request_bypass function to use policy.methods.contains(&request.method) to
satisfy Clippy and simplify the code.
- Around line 436-461: In cache_control_max_age, don't let a single malformed
max-age token abort the whole function: replace the use of
value.trim().trim_matches('"').parse::<u64>().ok()? so that parse failures skip
that directive (continue) instead of returning None; locate this logic in
function cache_control_max_age (the loop over headers.get_all(CACHE_CONTROL) and
directive.split(',')) and change the parse to an explicit match/if let that
continues on Err, preserving other directives like s-maxage and allowing
fallback behavior.
- Around line 487-496: The cached_headers function is currently only filtering
CACHE_STATUS_HEADER and therefore persists RFC7230 hop-by-hop headers and
Content-Length; update cached_headers to drop all standard hop-by-hop names
(Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer,
Transfer-Encoding, Upgrade) plus Content-Length, and additionally parse the
Connection header values and remove any header names it lists (case-insensitive)
before collecting. Ensure you still exclude CACHE_STATUS_HEADER and perform
comparisons case-insensitively when filtering header names so replayed responses
won't break framing or connection semantics.
- Around line 361-369: The content-type checks mix case-insensitive equality
with case-sensitive prefix checks, so values like "Application/grpc+proto" are
missed; in the request_is_grpc (the shown block) and response_is_grpc functions,
normalize the mime to a canonical case (e.g., lowercasing the trimmed mime
string) before comparing, then use starts_with("application/grpc+") and
starts_with("application/grpc-web") on that normalized value (or alternatively
use a case-insensitive starts_with helper), or extract a shared helper function
is_grpc_mime(mime: &str) and call it from both request_is_grpc and
response_is_grpc.
- Around line 532-548: write_cache_entry 存在并发写同一 key
时临时文件名冲突(metadata_tmp/body_tmp),会导致 metadata 与 body 对应错位;请将临时文件改为每次写入都带唯一后缀(如
UUID、PID+自增或使用 tempfile::NamedTempFile)而不是直接复用 paths.metadata/.body 派生名,确保在
write_cache_entry 中先写入各自唯一的临时文件(例如 body_tmp =
paths.body.with_extension(format!("body.{}.tmp", uuid))),然后只将各自的临时文件 rename
到最终文件(fs::rename(&own_body_tmp, &paths.body) / fs::rename(&own_metadata_tmp,
&paths.metadata)),并在错误分支清理/删除自身临时文件以避免残留。

In `@crates/rginx-http/src/cache/tests.rs`:
- Line 159: Replace the pattern
lock_index(&zone.index).entries.get(key).is_none() with the idiomatic
contains_key check to satisfy Clippy: use
!lock_index(&zone.index).entries.contains_key(key) instead; update the assertion
in the test that references lock_index, zone.index, entries and key to use
contains_key rather than get(...).is_none().
- Around line 211-221: The test hardcodes PathBuf::from("/tmp/rginx-cache-test")
in the CacheZone config which is platform-unsafe; change to create a temporary
directory with tempfile::tempdir(), use its path (tempdir.path().to_path_buf())
for the CacheZone::path, and keep the TempDir handle (e.g. bind tempdir to a
variable in the test scope) so it is not dropped while the test uses
CacheZoneRuntime (refer to CacheZoneRuntime, CacheZone and the local variable
zone). Ensure imports for tempfile are added and no hardcoded paths remain.

In `@crates/rginx-http/src/proxy/forward/attempt.rs`:
- Around line 23-31: The Hit branch in the match on cache_lookup returns the
cached Response immediately (CacheLookup::Hit(response)), skipping the same
downstream transformations applied on non-cache paths; modify the Hit handling
to pass the cached response through the same processing pipeline (i.e. call
build_downstream_response or at minimum invoke sanitize_response_headers and
wrap the body with IdleTimeoutBody, GrpcDeadlineBody and ActivePeerBody as done
for success paths) so cached responses receive header sanitization, idle timeout
protection, gRPC deadline enforcement and peer-active tracking before being
returned.

In `@crates/rginx-http/src/state/tls_runtime/bindings/tests.rs`:
- Line 101: The added field cache_zones: std::collections::HashMap::new() should
be simplified to use the existing import by replacing
std::collections::HashMap::new() with HashMap::new() to match upstreams:
HashMap::new() and keep style consistent; update the same occurrence mentioned
(also at the other spot) so both cache_zones initializers use HashMap::new() and
no further changes are required since HashMap is already imported at the top of
tests.rs.

In `@crates/rginx-http/src/transition/tests.rs`:
- Line 29: 在测试结构初始化处使用了完全路径 std::collections::HashMap::new(),但文件顶部已通过 use
std::collections::HashMap; 导入了 HashMap;请将字段初始化中的 cache_zones:
std::collections::HashMap::new(), 改为 cache_zones: HashMap::new(), 以与 upstreams:
HashMap::new() 保持一致风格并统一此 PR 中其他测试文件相同写法(定位符:cache_zones, upstreams, HashMap)。

In `@crates/rginx-runtime/src/bootstrap/shutdown/tests.rs`:
- Line 19: The test initializes ConfigSnapshot/Route fields correctly but for
style consistency use the imported HashMap alias: replace explicit
std::collections::HashMap::new() with HashMap::new() for the cache_zones field
(and any other occurrences such as at the other occurrence around the
vhosts/upstreams block) so the test's struct literal for ConfigSnapshot/Route
matches the surrounding style; locate the cache_zones field in the shutdown
tests and update its initializer accordingly.

---

Outside diff comments:
In `@crates/rginx-config/src/model.rs`:
- Around line 30-45: Move the pub re-export for the cache types so it sits with
the other module exports before the Config struct: relocate the line `pub use
cache::{CacheRouteConfig, CacheZoneConfig};` to the block where the other `pub
use` statements for `listener`, `route`, `runtime`, `server`, `tls`, `upstream`,
and `vhost` are declared so that all module exports appear prior to the `Config`
struct definition.

In `@crates/rginx-http/src/handler/dispatch/mod.rs`:
- Around line 47-53: RouteExecutionContext currently clones RouteCachePolicy per
request (via route.cache.clone()), causing heavy heap allocations; change the
type of the cache to use reference-counting instead: make Route.cache (or the
cache field inside RouteExecutionContext) Option<Arc<RouteCachePolicy>> and
update all call sites to clone the Arc (Arc::clone) instead of deep-cloning
RouteCachePolicy; ensure you import std::sync::Arc and update any code that
previously accessed fields or constructed RouteCachePolicy to account for the
Arc (dereference or clone the Arc when needed) and replace route.cache.clone()
with Arc::clone(&route.cache.as_ref().unwrap()) or equivalent safe handling for
the Option.

In `@crates/rginx-http/src/proxy/forward/attempt.rs`:
- Around line 1-346: The file is over the 300-line soft limit; extract the cache
decoration logic into a new submodule (e.g., proxy::forward::cache): move the
helper fn mark_cache_status_if_needed and the cache lookup/context construction
around the top of forward_request (the logic that creates cache_lookup,
cache_store, cache_status and the final call at the end) into that module
(export a function like cache::prepare_cache_context(request:
&Request<HttpBody>, state: &SharedState, downstream:
&DownstreamRequestContext<'_>, cache_opt: Option<...>) ->
(Option<crate::cache::CacheStore>, Option<crate::cache::CacheStatus>) and
cache::wrap_response_with_status(state: &SharedState, response: HttpResponse,
cache_status: Option<crate::cache::CacheStatus>) -> HttpResponse), update
attempt.rs to call those two functions instead of inlining the logic, adjust
imports/uses (e.g., replace direct mark_cache_status_if_needed and the cache
construction with calls to cache::prepare_cache_context and
cache::wrap_response_with_status) and run cargo build/tests to ensure
visibility/paths and types (SharedState, HttpResponse,
crate::cache::CacheLookup/CacheStatus) are correct.

In `@crates/rginx-http/src/proxy/forward/success.rs`:
- Around line 29-57: This branch handles upgraded (101/connection-upgrade)
responses; instead of calling apply_cache_store (which may acquire locks and
attempt store), return the built response after only applying the cache_status
and skipping any cache_store logic: in the upgrade branch where
(context.downstream_upgrade, upstream_upgrade) are present, call a helper that
applies just the cache_status (e.g., apply_cache_status(state, cache_status,
response)) or, if that helper doesn't exist, modify apply_cache_store usage to
pass a sentinel/no-op for cache_store and ensure no
store_cached_response/store_response path is executed; update the branch around
proxy_upgraded_connection, build_downstream_response and the apply_cache_store
call accordingly so upgrades never attempt to write cached bodies.

In `@crates/rginx-http/src/proxy/tests/mod.rs`:
- Around line 197-213: The cache_zones field in snapshot_with_upstreams_map is
constructed with a fully-qualified std::collections::HashMap::new(); change it
to use the already-imported HashMap by replacing that expression with
HashMap::new() so it matches the style used for the upstreams parameter and
other code; update the initializer in function snapshot_with_upstreams_map
(field cache_zones) accordingly.

In `@crates/rginx-http/src/state/lifecycle/reload.rs`:
- Around line 117-128: The update sequence exposes new state.config/clients
before swapping the cache; move the cache swap so the CacheManager is updated
before the new config becomes visible: write prepared.cache into self.cache (the
prepared.cache assignment currently following the inner.write() block) either
before modifying state inside the same self.inner.write().await critical section
or immediately before entering it; then update state.revision, state.config, and
state.clients inside that same write lock, keep the listener acceptors
assignment (*self.listener_tls_acceptors.write().await = merged_acceptors) and
the revisions send (self.revisions.send(next_revision)) as-is after state and
cache are swapped so readers never observe a config that references an
out-of-date CacheManager.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: d55338bb-45c4-42c6-93d6-a9019b83ebba

📥 Commits

Reviewing files that changed from the base of the PR and between c82e189 and e755513.

📒 Files selected for processing (71)
  • CACHE_SUPPORT_PLAN.md
  • crates/rginx-app/src/check/render.rs
  • crates/rginx-app/src/check/routes.rs
  • crates/rginx-app/src/check/summary.rs
  • crates/rginx-config/src/compile/cache.rs
  • crates/rginx-config/src/compile/mod.rs
  • crates/rginx-config/src/compile/route.rs
  • crates/rginx-config/src/compile/tests.rs
  • crates/rginx-config/src/compile/tests/cache.rs
  • crates/rginx-config/src/compile/tests/http3.rs
  • crates/rginx-config/src/compile/tests/listeners.rs
  • crates/rginx-config/src/compile/tests/route.rs
  • crates/rginx-config/src/compile/tests/route/regex.rs
  • crates/rginx-config/src/compile/tests/server_settings.rs
  • crates/rginx-config/src/compile/tests/server_tls.rs
  • crates/rginx-config/src/compile/tests/upstream_defaults.rs
  • crates/rginx-config/src/compile/tests/upstream_fallbacks.rs
  • crates/rginx-config/src/compile/tests/upstream_server_name.rs
  • crates/rginx-config/src/compile/tests/upstream_tls.rs
  • crates/rginx-config/src/compile/tests/upstream_transport.rs
  • crates/rginx-config/src/compile/tests/vhosts.rs
  • crates/rginx-config/src/compile/tests/vhosts/listener_conflicts.rs
  • crates/rginx-config/src/compile/tests/vhosts/nezha.rs
  • crates/rginx-config/src/load/tests.rs
  • crates/rginx-config/src/model.rs
  • crates/rginx-config/src/model/cache.rs
  • crates/rginx-config/src/model/route.rs
  • crates/rginx-config/src/validate.rs
  • crates/rginx-config/src/validate/cache.rs
  • crates/rginx-config/src/validate/route.rs
  • crates/rginx-config/src/validate/tests.rs
  • crates/rginx-config/src/validate/tests/cache.rs
  • crates/rginx-config/src/validate/tests/route.rs
  • crates/rginx-config/src/validate/tests/vhosts.rs
  • crates/rginx-config/src/validate/vhost.rs
  • crates/rginx-core/src/config.rs
  • crates/rginx-core/src/config/cache.rs
  • crates/rginx-core/src/config/route.rs
  • crates/rginx-core/src/config/snapshot.rs
  • crates/rginx-core/src/config/tests.rs
  • crates/rginx-core/src/lib.rs
  • crates/rginx-http/Cargo.toml
  • crates/rginx-http/src/cache/mod.rs
  • crates/rginx-http/src/cache/tests.rs
  • crates/rginx-http/src/handler/dispatch/mod.rs
  • crates/rginx-http/src/handler/dispatch/route.rs
  • crates/rginx-http/src/handler/tests/routing/authorize.rs
  • crates/rginx-http/src/handler/tests/routing/handle.rs
  • crates/rginx-http/src/handler/tests/routing/selection.rs
  • crates/rginx-http/src/handler/tests/support.rs
  • crates/rginx-http/src/lib.rs
  • crates/rginx-http/src/proxy/clients/tests.rs
  • crates/rginx-http/src/proxy/forward/attempt.rs
  • crates/rginx-http/src/proxy/forward/setup.rs
  • crates/rginx-http/src/proxy/forward/streaming.rs
  • crates/rginx-http/src/proxy/forward/success.rs
  • crates/rginx-http/src/proxy/forward/types.rs
  • crates/rginx-http/src/proxy/health/registry/tests.rs
  • crates/rginx-http/src/proxy/tests/cache.rs
  • crates/rginx-http/src/proxy/tests/mod.rs
  • crates/rginx-http/src/router/tests.rs
  • crates/rginx-http/src/state/cache.rs
  • crates/rginx-http/src/state/helpers.rs
  • crates/rginx-http/src/state/lifecycle/reload.rs
  • crates/rginx-http/src/state/mod.rs
  • crates/rginx-http/src/state/tests/support.rs
  • crates/rginx-http/src/state/tls_runtime/bindings/tests.rs
  • crates/rginx-http/src/transition/tests.rs
  • crates/rginx-runtime/src/bootstrap/listeners/tests.rs
  • crates/rginx-runtime/src/bootstrap/shutdown/tests.rs
  • crates/rginx-runtime/src/health/tests.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-04-13T10:53:29.903Z
Learnt from: vansour
Repo: vansour/rginx PR: 43
File: crates/rginx-config/src/validate/server.rs:421-439
Timestamp: 2026-04-13T10:53:29.903Z
Learning: In `crates/rginx-config/src/validate/server.rs` (`validate_http3`), the check for `tls.session_tickets` when `http3.early_data = true` intentionally only rejects `Some(true)` and allows `None`. Requiring `Some(false)` breaks 0-RTT resumption and the `routes_http3_early_data_by_replay_safety` test and the CI `http3` gate in the current rustls/quinn integration. This is a deliberate design decision and should not be flagged as a bug until the runtime/session policy is updated to support that configuration.

Applied to files:

  • crates/rginx-config/src/validate/tests/route.rs
  • crates/rginx-http/src/handler/tests/routing/authorize.rs
  • crates/rginx-http/src/handler/tests/routing/selection.rs
  • crates/rginx-config/src/validate/tests/vhosts.rs
  • crates/rginx-http/src/state/tls_runtime/bindings/tests.rs
  • crates/rginx-config/src/compile/tests/http3.rs
  • crates/rginx-config/src/compile/tests/upstream_defaults.rs
  • crates/rginx-config/src/compile/tests/server_tls.rs
  • crates/rginx-config/src/compile/tests/upstream_server_name.rs
  • crates/rginx-config/src/compile/tests/upstream_tls.rs
  • crates/rginx-config/src/validate/tests/cache.rs
  • crates/rginx-http/src/router/tests.rs
  • crates/rginx-config/src/compile/tests/server_settings.rs
🪛 GitHub Actions: CI
crates/rginx-http/src/cache/mod.rs

[error] 349-349: Clippy error (op_ref): taken reference of right operand. Use request.method directly instead of &request.method. (implied -D clippy::op-ref)

crates/rginx-http/src/cache/tests.rs

[error] 159-159: Clippy error (unnecessary_get_then_check): unnecessary use of get(key).is_none(). Use !contains_key(key) instead. (implied -D clippy::unnecessary-get-then-check)

crates/rginx-http/src/proxy/forward/attempt.rs

[error] 1-1: modularization-gate: new production soft-limit violation. File has 346 lines; soft limit is 300.

🪛 LanguageTool
CACHE_SUPPORT_PLAN.md

[uncategorized] ~13-~13: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...YPASSEXPIRED`。 本文档按阶段规划 rginx 的缓存支持。第一目标是实现用户可配置的 HTTP 反向代理响应缓存。现有内部缓存,例如 DNS 解析...

(wa5)


[uncategorized] ~29-~29: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...tion的请求。 - 默认不存储带Set-Cookie的响应。 - 第一版将Range` 请求视为 bypass。 - 采用“内存 metadata/...

(wa5)

🔇 Additional comments (47)
crates/rginx-core/src/config/cache.rs (1)

54-109: 模板变量语法已正确实现且与文档一致,无需修改。

根据代码验证,模板语法实现与文档、测试、默认配置完全一致:

  • 文档声明(docs/NEZHA_DASHBOARD.md):明确指定 {host}{scheme} 等为模板变量语法,{{}} 用于转义字面量大括号
  • 实现一致性:解析器正确处理 {name} 作为变量、{{ / }} 作为转义
  • 测试覆盖:所有单元测试和配置示例({scheme}:{host}:{uri})均采用此语法
  • 默认配置(compile/cache.rs):DEFAULT_CACHE_KEY = "{scheme}:{host}:{uri}"

不存在 Handlebars 风格 {{...}} 变量的使用或配置,因此无缓存污染风险。

			> Likely an incorrect or invalid review comment.
crates/rginx-http/src/proxy/health/registry/tests.rs (1)

125-125: 与上文同类问题(重复评论)

同样建议复用顶部已导入的 HashMap,避免在每个测试 fixture 中使用 std::collections::HashMap::new() 全限定路径。

crates/rginx-runtime/src/health/tests.rs (1)

55-55: 与前文同类问题(重复评论)

文件顶部已 use std::collections::HashMap;,此处可写为 cache_zones: HashMap::new(), 与同 fixture 中第 76 行 upstreams: HashMap::from(...) 保持一致。

crates/rginx-runtime/src/bootstrap/listeners/tests.rs (1)

39-39: 与前文同类问题(重复评论)

文件已导入 HashMap,可用短路径 HashMap::new(),与第 53 行 upstreams: HashMap::new(), 写法一致。

crates/rginx-http/src/lib.rs (1)

3-3: LGTM

新增内部 cache 模块声明清晰且作用域为 crate 内部,符合 PR 中 cache MVP 的封装目标。

crates/rginx-config/src/compile/tests/http3.rs (1)

12-12: LGTM

为已有 HTTP/3 编译用例补齐新增的 cache_zonesLocationConfig.cache 字段,与全仓 fixture 改动一致,未引入语义变化。

Also applies to: 66-66, 117-117, 171-171

crates/rginx-http/src/handler/tests/routing/selection.rs (1)

140-140: LGTM

两处手写构造的 Route 字面量已补齐新增的 cache: None 字段,与 Route 结构定义一致,对 gRPC 路由选择测试不引入额外副作用。

Also applies to: 186-186

crates/rginx-config/src/compile/tests/route/regex.rs (1)

6-6: 测试夹具补齐 cache_zones 字段,符合新 schema。

两处 Config { ... } 字面量显式初始化 cache_zones: Vec::new(),与本 PR 引入的缓存配置编译路径一致,未影响原有路由匹配/排序断言。

Also applies to: 122-122

crates/rginx-config/src/compile/tests/upstream_defaults.rs (1)

6-6: 与其他编译测试夹具保持一致,LGTM。

显式将 cache_zones 初始化为空 Vec,未触及上游默认值相关的断言逻辑。

Also applies to: 119-119

crates/rginx-http/src/handler/tests/routing/authorize.rs (1)

6-6: Route.cache = None 初始化与新模型一致,LGTM。

授权相关的请求/响应行为与断言均未改变,仅是对必填字段的填充。

Also applies to: 48-48

crates/rginx-config/src/validate/tests/route.rs (1)

116-116: LocationConfig 字面量补齐 cache: None,符合新 schema。

仅影响该 push 的 location 构造,未改变重复路由/gRPC 约束相关断言。

crates/rginx-http/src/router/tests.rs (1)

13-13: 所有 Route 字面量统一补齐 cache: None,LGTM。

路由匹配、gRPC 约束等断言保持不变,仅扩展字段以匹配新模型。

Also applies to: 51-51, 71-71, 99-99, 183-183, 206-206, 238-238, 278-278

crates/rginx-http/src/handler/dispatch/route.rs (1)

48-48: 正确将路由级缓存策略透传至 forward_request

route.cacheOption<RouteCachePolicy>)通过 DownstreamRequestOptions.cache 进入 forward/attempt.rs,与上下游集成(lookup/store、X-Cache 标记)的实现路径一致;RouteExecutionContext 在此处按值消费,无所有权问题。

crates/rginx-http/src/handler/tests/routing/handle.rs (1)

45-46: LGTM!

机械性补全 Route 新增的 cache: Option<RouteCachePolicy> 字段,使该返回路由场景明确为不启用缓存,与本 PR 中其他测试夹具保持一致。

crates/rginx-config/src/compile/tests/upstream_tls.rs (1)

10-10: LGTM!

机械性补齐 Config.cache_zonesLocationConfig.cache 新增字段,保持现有上游 TLS 测试用例缓存关闭,断言未变更。

Also applies to: 80-80, 139-139, 200-200, 245-245, 315-315

crates/rginx-config/src/compile/tests/vhosts.rs (1)

13-13: LGTM!

vhost 编译测试夹具机械补齐 cache_zones: Vec::new(),与新增的顶层缓存区配置字段对齐,无行为变更。

Also applies to: 79-79, 134-134, 186-186, 215-215

crates/rginx-config/src/compile/tests/upstream_server_name.rs (1)

6-6: LGTM!

补齐 cache_zonesLocationConfig.cache 字段,测试断言(server_name_override 的标准化与拒绝逻辑)未变化。

Also applies to: 67-67, 106-106, 174-174, 212-212, 273-273

crates/rginx-config/src/compile/tests/vhosts/listener_conflicts.rs (1)

47-47: LGTM!

机械性补齐 cache_zones,监听冲突拒绝逻辑断言保持不变。

crates/rginx-config/src/validate/tests/vhosts.rs (1)

146-167: LGTM!

LocationConfig 显式补齐 cache: None,与新增的 Option<CacheRouteConfig> 字段一致;该用例验证 vhost tls without tls listener 拒绝行为的断言保持不变。

crates/rginx-config/src/model/route.rs (1)

53-54: LGTM!

新增 cache 字段使用 #[serde(default)],对现有配置文件保持向后兼容;CacheRouteConfig 导入路径正确。

crates/rginx-config/src/compile/route.rs (1)

68-122: LGTM!

cache 字段被正确解构并通过 compile_route_cache(cache)? 编译,错误向上传播,与既有字段处理风格一致。

crates/rginx-config/src/compile/tests/vhosts/nezha.rs (1)

28-61: LGTM!

测试夹具同步补齐了新增的 cache_zonescache 字段,与新 schema 保持一致。

crates/rginx-config/src/compile/tests/upstream_transport.rs (1)

6-276: LGTM!

三处测试用例均按新 schema 显式设置 cache_zones: Vec::new()LocationConfig.cache: None,未触及断言逻辑。

crates/rginx-http/src/state/helpers.rs (1)

8-17: LGTM!

CacheManager 的初始化通过 ? 正确传播 IO/配置错误(包括 from_config 中的 create_dir_all 失败),并随 PreparedState 一同返回,与既有 ProxyClients 的装配模式一致。

crates/rginx-config/src/compile/tests/server_tls.rs (1)

12-234: LGTM!

三个 TLS 编译测试用例均机械性补齐 cache_zonescache 字段,未影响 TLS 断言逻辑。

crates/rginx-config/src/compile/tests/listeners.rs (1)

6-73: LGTM!

测试用例按新 schema 补齐 cache_zonescache 字段,断言部分未变更。

crates/rginx-config/src/compile/tests/upstream_fallbacks.rs (1)

6-6: LGTM:补齐新增缓存字段的测试夹具更新。

仅为对齐新增的 Config.cache_zonesLocationConfig.cache 字段,未影响断言与被测逻辑,符合预期。

Also applies to: 74-74, 128-128, 189-189, 237-237, 298-298

crates/rginx-config/src/compile/tests/route.rs (1)

8-8: LGTM:路由编译测试夹具同步缓存字段。

仅是夹具补齐 cache_zones / cache 字段,被测路由编译路径与断言未变。

Also applies to: 36-36, 70-70, 98-98, 134-134, 171-171, 233-233, 298-298, 335-335

crates/rginx-http/src/handler/tests/support.rs (1)

33-33: LGTM:测试支撑代码与新 cache 字段对齐。

ConfigSnapshot.cache_zones/Route.cache 初始化与新模型匹配,测试夹具仍保持禁用缓存的语义。

Also applies to: 64-64

crates/rginx-config/src/compile/tests.rs (1)

5-11: LGTM:编译测试模块入口完成缓存测试集成。

新模块 cache 与导入扩展、test_location 默认填充 cache: None 一致,能覆盖默认无缓存与显式缓存策略两类用例(详见同目录新增 cache.rs)。

Also applies to: 37-37, 56-56

crates/rginx-http/src/state/tests/support.rs (1)

21-21: LGTM:状态层测试夹具补齐缓存字段。

ConfigSnapshot.cache_zonesRoute.cache 默认值与现有用例语义一致(不启用路由缓存)。

Also applies to: 94-94, 119-119

crates/rginx-config/src/validate/vhost.rs (1)

13-13: LGTM:vhost 校验透传缓存区名集合。

cache_zone_names 仅作为透传参数传入 validate_locations,未改变 vhost 自身的校验语义;与"缓存区仅在全局定义"的 MVP 设计一致。

Also applies to: 55-55

crates/rginx-config/src/compile/tests/server_settings.rs (1)

6-6: LGTM!

测试夹具仅按新增的 cache_zonescache 字段做了机械性补齐,未改动断言或调用流程,与 compile/validate 管线的扩展保持一致。

Also applies to: 51-51, 79-79, 118-118, 146-146, 201-201, 229-229

crates/rginx-config/src/validate.rs (1)

7-7: LGTM!

validate_cache_zones 先于 validate_locations / validate_virtual_hosts 运行,保证路由侧能对未声明 zone 的引用做存在性校验;签名扩展也已在两处调用点同步更新。

Also applies to: 26-29, 38-38

crates/rginx-http/src/state/mod.rs (1)

14-20: LGTM!

使用 Arc<StdRwLock<CacheManager>> 在 reload 时整体替换 manager 是合适的;state/cache.rs 内仅在同步作用域内持有锁并克隆管理器,规避了跨 await 持有 std 锁的隐患。CacheLookup/CacheStatus/CacheStoreContext 的提前导入也支撑了 state::cache 子模块的 super::* 解析。

Also applies to: 38-38, 82-82, 164-164

crates/rginx-http/src/proxy/forward/streaming.rs (1)

9-9: LGTM!

downstream 改为借用形参后,函数体只读访问 request_id 等字段,调用方可在缓存查找/存储路径中复用 DownstreamRequestContext 而不必显式 clone,与 Copy 取消保持一致。

crates/rginx-http/src/proxy/forward/types.rs (1)

3-18: LGTM!

RouteCachePolicyString/Vec 等非 Copy 字段,去掉 Copy 是必然结果;其它转发链路已配套改为按引用传递,整体一致。

crates/rginx-config/src/model/cache.rs (1)

3-28: 新增的 CacheZoneConfigCacheRouteConfig 与仓库内既有配置模型(如 UpstreamConfigLocationConfig 等)保持一致,均不使用 #[serde(deny_unknown_fields)]。这是仓库内的统一模式,而非不一致或缺陷。

			> Likely an incorrect or invalid review comment.
crates/rginx-http/src/proxy/forward/setup.rs (1)

21-21: 已验证,RouteBufferingPolicy 仍实现 Copy 特性,代码无问题。

crates/rginx-core/src/config/route.rs 中,RouteBufferingPolicy 的定义为:

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RouteBufferingPolicy { ... }

第 84 行通过借用结构体按值传入 downstream.options.request_buffering 是安全的。

crates/rginx-http/src/state/cache.rs (1)

4-21: 移除该评论 - CacheManager::clone() 已正确实现了共享状态语义。

CacheManager 使用 #[derive(Clone)] 并持有 Arc<HashMap<String, Arc<CacheZoneRuntime>>> 字段。当克隆 CacheManager 时,内部的 Arc 会被克隆(O(1) 引用计数增长),所有克隆实例都指向同一份底层数据。lookup_cached_responsestore_cached_response 中的「读锁 → 克隆 → 释放锁 → await」模式正确无误:锁释放后,克隆的 CacheManager 仍持有对共享 Arc 的引用,后续的缓存操作(通过 CacheZoneRuntime 内部的 Mutex 进行同步)会正确地更新和读取同一份索引与磁盘状态。不存在状态不一致或静默失效的风险。

			> Likely an incorrect or invalid review comment.
crates/rginx-config/src/compile/mod.rs (1)

47-99: LGTM!

cache_zones 字段被正确地从 Config 解构出来,并通过 cache::compile_cache_zones 编译后注入 ConfigSnapshot。字段顺序与 crates/rginx-core/src/config/snapshot.rsConfigSnapshot 的定义一致,编译流程整合完整。

crates/rginx-config/src/validate/tests.rs (1)

1-200: LGTM!

测试夹具同步更新为新的 Config.cache_zonesLocationConfig.cache 字段,新增 mod cache; 与缓存配置类型的导入,与 PR 中 model/validate 的 schema 变更保持一致。

crates/rginx-http/src/proxy/tests/cache.rs (2)

60-117: LGTM!

miss→hit 测试通过预先入队 [OK, INTERNAL_SERVER_ERROR] 两个状态码,巧妙地实现了对“第二次请求必须命中缓存而不再回源”的反向验证:若缓存未命中,第二次回源将返回 500,断言 StatusCode::OK 会立即暴露问题。x-cache: MISS/HIT 的语义校验也清晰。


119-165: LGTM!

通过附带 Authorization 头部的 GET 请求验证 x-cache: BYPASS 行为,覆盖了缓存策略对认证请求的旁路处理。建议后续 phase 在此基础上补充对 EXPIRED 标记以及带 stale_if_error 的回源失败回退分支的端到端用例(可选)。

crates/rginx-config/src/validate/route.rs (1)

94-118: LGTM!

validate_cache_handler_compatibilityvalidate_route_cache 之前先校验“缓存策略仅可用于 Proxy 处理器”的约束,能在非 Proxy 路由(如 Return)配置缓存时给出明确错误信息;且当 cacheNone 时两者都为 no-op,整体顺序与短路逻辑合理。cache_zone_names 也被正确透传给 super::cache::validate_route_cache

crates/rginx-app/src/check/summary.rs (1)

101-124: LGTM!

check_cache_zone_summaries 显式按 name 排序,弥补了 HashMap 迭代顺序不稳定的问题,输出对快照/check 渲染友好且可复现;cache_enabled_route_count 正确合并了 default_vhostvhosts,未重复统计(default_vhostvhostsConfigSnapshot 中是分离字段)。

CACHE_SUPPORT_PLAN.md (1)

1-232: 文档与实现一致,无需修改。

阶段 0–3 的当前实施状态描述与 validate/cache.rscompile/cache.rscache/mod.rsproxy/forward/{attempt,success}.rs 的代码行为吻合(包括 X-Cache: HIT/MISS/BYPASS/EXPIRED、默认 bypass Authorization/Range、不存储 Set-Cookiestale 不在 MVP 内等)。LanguageTool 提示均为 zh-CN 误报,可忽略。

Comment thread crates/rginx-app/src/check/render.rs
Comment thread crates/rginx-app/src/check/routes.rs
Comment thread crates/rginx-config/src/compile/cache.rs Outdated
Comment thread crates/rginx-config/src/compile/tests/cache.rs
Comment thread crates/rginx-config/src/load/tests.rs
Comment thread crates/rginx-http/src/cache/tests.rs
Comment thread crates/rginx-http/src/proxy/forward/attempt.rs Outdated
Comment thread crates/rginx-http/src/state/tls_runtime/bindings/tests.rs Outdated
Comment thread crates/rginx-http/src/transition/tests.rs Outdated
Comment thread crates/rginx-runtime/src/bootstrap/shutdown/tests.rs Outdated
@vansour vansour force-pushed the feat/cache-support-mvp branch 2 times, most recently from 43d4ee3 to 4300378 Compare April 27, 2026 11:39
@vansour
Copy link
Copy Markdown
Owner Author

vansour commented Apr 27, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 430037842a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/rginx-http/src/cache/mod.rs
Comment thread crates/rginx-http/src/state/lifecycle/reload.rs Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 27, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 22

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/rginx-core/src/config/route.rs (1)

8-34: 🧹 Nitpick | 🔵 Trivial

字段添加合理,可考虑用 Arc 包装以降低克隆成本。

Route 结构体在请求分发路径上会被 .cloned()(见 crates/rginx-http/src/handler/dispatch/mod.rs 第 116 行),RouteCachePolicy 内部包含 Vec<Method>Vec<StatusCode>CacheKeyTemplate,每次请求都会做一次堆分配的深拷贝。若热路径上对克隆成本敏感,可将 cache 字段改为 Option<Arc<RouteCachePolicy>>,使路由策略只读共享、克隆变为引用计数递增。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/rginx-core/src/config/route.rs` around lines 8 - 34, The Route
struct's cache field is currently Option<RouteCachePolicy> causing expensive
deep clones on each request; change it to Option<Arc<RouteCachePolicy>> by
importing std::sync::Arc and updating the type in the Route definition (and any
construction sites) so routes are cheaply cloned via Arc::clone; also update
places that expect owned RouteCachePolicy (e.g., any code in dispatch or tests
that dereferences or mutates cache) to handle &RouteCachePolicy (or
Arc::as_ref()) and ensure RouteCachePolicy remains immutable or is wrapped
appropriately if mutation is needed.
crates/rginx-http/src/proxy/forward/types.rs (1)

3-18: 🧹 Nitpick | 🔵 Trivial

移除 Copy 合理;可选地用 Arc 共享缓存策略以降低克隆开销。

RouteCachePolicy 内含字符串/集合等堆分配字段,去掉 Copy 是必要的。新字段 cache: Option<rginx_core::RouteCachePolicy> 在每次构造 DownstreamRequestOptions 时会随 Clone 一起复制;若策略对象较大或位于热路径(dispatch → forward → cache lookup),可考虑用 Option<Arc<rginx_core::RouteCachePolicy>>,让 dispatch 阶段一次性 Arc::clone 即可贯穿整条转发链。

-    pub cache: Option<rginx_core::RouteCachePolicy>,
+    pub cache: Option<std::sync::Arc<rginx_core::RouteCachePolicy>>,

非阻塞建议,需结合 rginx_core::RouteCachePolicy 的实际字段规模与调用频度评估。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/rginx-http/src/proxy/forward/types.rs` around lines 3 - 18,
DownstreamRequestOptions currently holds cache:
Option<rginx_core::RouteCachePolicy> which forces heavy clones; change that
field to cache: Option<std::sync::Arc<rginx_core::RouteCachePolicy>> and add use
std::sync::Arc so the options struct remains Clone but cheap-to-clone
(Arc::clone) in hot paths; update callers that construct
DownstreamRequestOptions/DownstreamRequestContext to wrap the policy with
Arc::new(...) or pass Arc clones, and where the policy is read use Arc
deref/borrow as needed (keeping derives for Debug/Clone intact and removing any
Copy impls if present).
♻️ Duplicate comments (1)
crates/rginx-http/src/cache/store.rs (1)

41-69: ⚠️ Potential issue | 🔴 Critical

未知大小响应仍会先全量缓冲到内存再判长(OOM 风险)。

store_response 在第 50 行无条件 body.collect().await,再到第 61 行才比较 max_entry_bytes。如果上游使用分块编码或不带 Content-Lengthresponse_is_storable 在外层无法依靠 size_hint().exact() 提前剔除,几 GB 的 chunked body 会被整体拉入内存。建议改为流式累计字节数,一旦超过 context.zone.config.max_entry_bytes 立即丢弃缓冲、把已有 chunk 透传给下游而不再缓存;或者在上层把缺乏精确 size hint 的响应直接判为不可缓存。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/rginx-http/src/cache/store.rs` around lines 41 - 69, store_response
currently calls body.collect().await (in function store_response) before
checking size, which buffers the entire response and can OOM for unknown-length
(chunked) bodies; change the logic to stream the body instead of using
body.collect(): iterate over body chunks, accumulate bytes up to
context.zone.config.max_entry_bytes, and if the running total exceeds
max_entry_bytes drop caching (stop buffering) and immediately return/forward a
Response built from the original parts with the body sent through (i.e., do not
call collect); ensure response_is_storable still gates obvious non-cache cases,
and preserve the existing error handling and TTL logic when buffering completes
successfully.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/rginx-config/src/compile/route.rs`:
- Line 122: 为 cache 的编译错误附加路由上下文:在调用点(route.rs 中的 `cache:
super::cache::compile_route_cache(cache)?`)把当前路由标识(如 `route_id` 或 matcher
字符串)传入,或在调用处用 anyhow/eyre 的 Context 包裹错误(例如 `.context(format!("route {}: failed
to compile cache", route_id))`);同时在 `super::cache::compile_route_cache` 的签名中新增
`route_id: &str` 并在其所有返回错误处把该路由信息包含到错误消息里(例如把 “invalid cache method” 等错误格式化为包含
route 标识的消息),确保最终错误能定位到具体 route。

In `@crates/rginx-config/src/compile/tests/upstream_fallbacks.rs`:
- Line 6: 测试夹具里显式初始化的重复字段(如 cache_zones: Vec::new() 和 LocationConfig.cache:
None)应与新增模型字段保持一致并减少重复;实现或补充 Config 和 LocationConfig 的 Default(或提供一个 helper 构造函数
e.g. new_test_config / new_test_location)并在测试中用 Config::default() /
LocationConfig::default()(或 helper)替换这些重复初始化,以便所有相关测试(例如涉及
cache_zones、LocationConfig.cache 的 fixture)统一维护并自动包含未来新增字段。

In `@crates/rginx-config/src/compile/tests/upstream_server_name.rs`:
- Line 6: Many tests duplicate large Config/LocationConfig literals making
future optional-field additions noisy; add helper constructors (e.g. fn
default_test_config() -> Config and fn default_test_location() ->
LocationConfig) and update tests that currently inline Config/LocationConfig
(references: Config, LocationConfig in the upstream_server_name tests) to call
these helpers and override only the fields needed (or use ..Default::default()),
so new optional fields won't require editing every test; implement helpers in
the tests module or a shared test util and replace the repeated literals at the
call sites mentioned in the comment.

In `@crates/rginx-config/src/load/tests.rs`:
- Around line 18-29: Add assertions to cover the disk cache path and capacity
fields: in the test that inspects config.cache_zones and the deserialized route
cache (using config.cache_zones[0] and the local variable cache from
config.locations[0].cache), assert that cache_zones[0].path equals
"/var/cache/rginx/default" and cache_zones[0].max_size_bytes equals
Some(1048576), and likewise assert that the deserialized cache.path and
cache.max_size_bytes match those values so path and max_size_bytes are
validated.

In `@crates/rginx-config/src/model/cache.rs`:
- Around line 7-14: 在 crates/rginx-config/src/model/cache.rs 中,移除对 Option 字段上多余的
#[serde(default)] 属性:删除 max_size_bytes、inactive_secs、default_ttl_secs 和
max_entry_bytes 四个字段前的 #[serde(default)] 注解(保留字段类型为 Option<u64>
不变),以消除冗余并保持序列化行为一致。
- Around line 3-28: 为防止配置字段名拼写错误被静默忽略,给缓存配置结构添加 serde 的未知字段拒绝:在 CacheZoneConfig
和 CacheRouteConfig 的定义上各加上
#[serde(deny_unknown_fields)](它们是反序列化的入口符号),以便反序列化时遇到多余/拼错字段立即报错;同时检查并在其他顶层配置结构(例如
RuntimeConfig、VirtualHostConfig、UpstreamConfig)按需统一应用相同的
#[serde(deny_unknown_fields)] 策略以保持校验一致性。

In `@crates/rginx-http/Cargo.toml`:
- Line 41: 在 Cargo.toml 中发现 serde_json 在 [dependencies] 与 [dev-dependencies]
重复声明(都使用 workspace = true);请删除 [dev-dependencies] 区块中重复的 serde_json
条目,保留运行时依赖的声明(即在 [dependencies] 中的 serde_json = { workspace = true
}),以消除冗余并避免误导性表明该包仅用于测试。

In `@crates/rginx-http/src/cache/entry.rs`:
- Around line 63-100: read_cached_response currently does fs::read(&paths.body)
which loads the entire body into memory and holds the zone IO lock; change it to
open the file asynchronously with tokio::fs::File and stream its contents via
tokio_util::io::ReaderStream (or an equivalent async byte stream) and attach
that stream as the response body instead of full_body(Bytes::from(body));
specifically replace the fs::read usage of paths.body in read_cached_response
with File::open(&paths.body).await? wrapped in ReaderStream::new(file) and
convert that stream into the HTTP body (e.g., Body::wrap_stream or the project's
full_body streaming API), and ensure you release any zone.io_lock before
starting the streaming read so the lock isn't held for the whole file transfer.
- Around line 33-37: The CachedHeader struct currently serializes value: Vec<u8>
as a JSON numeric array; change its serde serialization to a compact text form
(e.g. base64) by annotating the field with #[serde(with = "base64_serde")] (or a
custom module like hex_serde) and ensure the corresponding serde helper
module/crate is added and imported; apply the same change to the other
occurrences mentioned (the similar header/metadata fields around lines 159-180)
so metadata is persisted as a base64/hex string instead of a byte-array, and
run/adjust any (de)serialization tests to use the new helper.

In `@crates/rginx-http/src/cache/load.rs`:
- Around line 50-58: The loop in scan_prefix_dir (used by load_index_from_disk)
currently uses entry? which aborts the whole zone scan on any io::Error; instead
change the handling of the per-entry result so a failed fs::read_dir entry
(entry?) is logged with tracing::warn! and skipped (continue) rather than
propagated, preserving the rest of the scan; keep the existing logic that calls
metadata_hash(&path), body_hash(&path), cache_paths(&zone.path, &hash) and
remove_cache_files(zone, &hash) for valid entries and only return Err for truly
unrecoverable errors at the directory level.
- Around line 97-102: The conversion from body_metadata.len() to usize can fail
and currently returns None without cleaning files; change the
usize::try_from(body_metadata.len()) call to handle the Err case by calling
remove_cache_files(zone, hash) before returning None, and keep the Ok(path) flow
identical so that when try_from succeeds you continue to compare with
metadata.body_size_bytes as before; update the code around the body_size binding
(the existing variable and the subsequent size check) to use this match/if-let
style error-handling so orphaned cache files are removed on conversion failure.

In `@crates/rginx-http/src/cache/mod.rs`:
- Around line 204-223: The current store_response (method store_response)
returns error.into_response from the Err branch (coming from
store::store_response) which can contain a failing body stream; instead, change
the Err branch to NOT forward error.into_response directly — either construct
and return a safe 5xx response (e.g. 502 Bad Gateway with a plain error body
produced via an error_response_body helper) and then call with_cache_status on
that, or propagate the error to upstream error handling; to implement this you
can extend CacheStoreError to indicate "body already consumed" if needed, but in
this function specifically replace the error.into_response usage with a
synthesized 5xx HttpResponse (or a propagated error) to avoid sending a response
whose body will immediately fail to downstream clients.
- Around line 134-140: The unknown-zone branch that returns
CacheLookup::Bypass(CacheStatus::Bypass) silently hides configuration errors:
update the block that checks let Some(zone) =
self.zones.get(&policy.zone).cloned() to emit a tracing::warn! (including
policy.zone and maybe route id) when the zone key is missing, and guard that
warn with a simple once_cell/HashSet or other frequency limiter so it doesn’t
spam on reloads; additionally adjust cache_request_bypass handling
(cache_request_bypass function and the CacheLookup::Bypass return sites) and the
stats/phases to differentiate "unknown_zone" vs "policy_bypass" counters (i.e.,
add a separate metric or enum variant for unknown_zone rather than lumping both
into a single BYPASS stat).
- Around line 149-172: When an entry is detected expired in the lookup (the
branch that currently returns Err(CacheStatus::Expired)), remove that entry from
index.entries under the same lock (use lock_index(&zone.index) /
index.entries.remove(&key)), decrement the zone.current_size_bytes accordingly,
then drop the index lock and schedule a best-effort async/file-delete task under
the io_lock (or push to the existing io cleanup queue) to remove the on-disk
meta.json/body; finally continue returning a CacheLookup::Miss using
CacheStoreContext::new as before. This ensures expired entries don't keep
counting toward current_size_bytes and their files are cleaned up without
blocking the critical index lock.

In `@crates/rginx-http/src/cache/policy.rs`:
- Around line 28-30: The check in response_is_storable currently treats
"no-cache" the same as "no-store" which is too strict; change the logic so
cache_control_contains(response.headers(), &["no-store", "private"]) is used to
decide non-storability and remove "no-cache" from that rejection list, and
instead surface "no-cache" by marking the stored entry as requiring revalidation
(e.g., set a requires_validation / must_revalidate flag on the cache entry or
response metadata when cache_control_contains(..., &["no-cache"]) is true).
Update response_is_storable and any CacheEntry/ResponseMetadata construction
code to record that flag so lookup/serve logic can enforce validation, or
document as an MVP exception if you intentionally keep the stricter behavior.
- Around line 41-46: 当前对 body size 的两处检查(response.body().size_hint().exact() 和
response.body().size_hint().upper())是冗余的:当 exact() 为 Some(n) 时 upper() 也为
Some(n),请合并为单一检查,删除对 exact() 的单独分支并仅使用 response.body().size_hint().upper()(匹配
Some(upper) if upper <= context.zone.config.max_entry_bytes as u64),保留对 None
的原有行为(返回 false),以改善可读性并保持现有逻辑(见 size_hint().exact(), size_hint().upper(),
context.zone.config.max_entry_bytes)。

In `@crates/rginx-http/src/cache/request.rs`:
- Around line 7-29: The GET/HEAD duplicate check in cache_request_bypass is
redundant because validate_route_cache already ensures policy.methods only
contains GET/HEAD; remove the explicit matches!(request.method, Method::GET |
Method::HEAD) branch so cache_request_bypass relies on
policy.methods.contains(&request.method) to gate allowed methods (update the
function cache_request_bypass accordingly), and keep the remaining header and
content-type checks as-is; optionally replace the string literals used in the
content-type comparison with a shared constant if you want clearer semantics.

In `@crates/rginx-http/src/cache/store.rs`:
- Around line 136-163: eviction_candidates currently can evict the item that was
just written if its size alone exceeds max_size_bytes; change the function
signature of eviction_candidates(index: &mut CacheIndex, max_size_bytes:
Option<usize>) -> Vec<String> to accept an extra parameter for the just-inserted
key (e.g. new_entry_key: Option<&str>) and skip removing any entry whose key
equals that new_entry_key when iterating/sorting entries; then update the caller
update_index_after_store (the code path that calls eviction_candidates during
store) to pass Some(&key) for the entry being stored so the newly inserted item
is not immediately evicted. Ensure behavior is unchanged when None is passed and
preserve the current last_access sorting and size accounting.
- Around line 81-103: The zone-level tokio::sync::Mutex io_lock currently
serializes all cache I/O so lookup hits (see lookup in mod.rs) block behind
store_response's write path (write_cache_entry) and its update_index_after_store
(which does eviction_candidates sorting and fs::remove_file), causing tail
latency; fix by decoupling read vs write: stop taking context.zone.io_lock in
the read path (lookup) and instead rely on atomic file rename + size checks when
reading cache files, keep io_lock only around write_cache_entry and
index/eviction updates (update_index_after_store), or implement a sharded lock
keyed by the cache key hash to reduce contention, and/or move costly
fs::remove_file eviction work out of update_index_after_store into a background
eviction worker (enqueue paths to remove and process them async) so write path
holds io_lock only briefly; modify store_response, write_cache_entry,
update_index_after_store, and remove_index_entry accordingly to implement one of
these approaches.
- Around line 83-103: 当前在 write_cache_entry 返回 Err 时无条件调用
remove_index_entry(&context.zone,
&context.key),会把仍然有效的旧索引条目从内存移除导致磁盘残留和缓存静默失效。修改处理逻辑:让 write_cache_entry
返回能区分失败类型的结果(或在原有错误中携带标识),在错误分支只在确认是覆盖/最终文件被移除的失败(即对应 cleanup_failed_write 的
remove_final=true / “rename 已部分生效” 这种错误类型)时才调用 remove_index_entry;对于其他写入失败只记录
warning 并保留原有 index 条目(不调整 current_size_bytes),以保证旧文件继续可用并由后续淘汰流程清理。确保使用
write_cache_entry、remove_index_entry、cleanup_failed_write、update_index_after_store、context.key/context.zone
这些标识定位并实现该分支判断。

In `@crates/rginx-http/src/state/helpers.rs`:
- Line 8: CacheManager::from_config performs blocking IO (create_dir_all +
load_index_from_disk) each time prepare_state (including hot reload) runs;
update prepare_state to avoid always reloading: detect unchanged cache_zones in
the new config and reuse the existing CacheManager instead of calling
CacheManager::from_config, and only call CacheManager::from_config when the zone
set or paths change (so load_index_from_disk runs only on real changes). Also
consider adding a cleanup/rollback hook after create_dir_all when
prepare_listener_tls_acceptors or ProxyClients::from_config_with_health_notifier
fails (or document that create_dir_all is intentionally left as idempotent), and
mark load_index_from_disk as potentially expensive so future work can move it to
a blocking/task offloader if needed.

In `@crates/rginx-http/src/state/lifecycle/reload.rs`:
- Line 117: The update currently replaces self.cache before acquiring the
inner.write().await lock, creating a brief non-atomic window where readers may
observe new prepared.cache with old state.config/revision (or vice versa); move
the assignment that replaces self.cache (the write to self.cache using
prepared.cache) into the same critical section where you perform
inner.write().await and update state.config/clients/revision (i.e., perform
self.cache = prepared.cache inside the same locked block that updates
state.config = prepared.config.clone() and increments revision), or add a clear
comment stating the transient inconsistency is acceptable; ensure you keep the
poison-lock recovery pattern (unwrap_or_else(|poisoned| poisoned.into_inner()))
when modifying the cache.

---

Outside diff comments:
In `@crates/rginx-core/src/config/route.rs`:
- Around line 8-34: The Route struct's cache field is currently
Option<RouteCachePolicy> causing expensive deep clones on each request; change
it to Option<Arc<RouteCachePolicy>> by importing std::sync::Arc and updating the
type in the Route definition (and any construction sites) so routes are cheaply
cloned via Arc::clone; also update places that expect owned RouteCachePolicy
(e.g., any code in dispatch or tests that dereferences or mutates cache) to
handle &RouteCachePolicy (or Arc::as_ref()) and ensure RouteCachePolicy remains
immutable or is wrapped appropriately if mutation is needed.

In `@crates/rginx-http/src/proxy/forward/types.rs`:
- Around line 3-18: DownstreamRequestOptions currently holds cache:
Option<rginx_core::RouteCachePolicy> which forces heavy clones; change that
field to cache: Option<std::sync::Arc<rginx_core::RouteCachePolicy>> and add use
std::sync::Arc so the options struct remains Clone but cheap-to-clone
(Arc::clone) in hot paths; update callers that construct
DownstreamRequestOptions/DownstreamRequestContext to wrap the policy with
Arc::new(...) or pass Arc clones, and where the policy is read use Arc
deref/borrow as needed (keeping derives for Debug/Clone intact and removing any
Copy impls if present).

---

Duplicate comments:
In `@crates/rginx-http/src/cache/store.rs`:
- Around line 41-69: store_response currently calls body.collect().await (in
function store_response) before checking size, which buffers the entire response
and can OOM for unknown-length (chunked) bodies; change the logic to stream the
body instead of using body.collect(): iterate over body chunks, accumulate bytes
up to context.zone.config.max_entry_bytes, and if the running total exceeds
max_entry_bytes drop caching (stop buffering) and immediately return/forward a
Response built from the original parts with the body sent through (i.e., do not
call collect); ensure response_is_storable still gates obvious non-cache cases,
and preserve the existing error handling and TTL logic when buffering completes
successfully.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 811cd8e4-b145-41dd-8029-068dbfcc071d

📥 Commits

Reviewing files that changed from the base of the PR and between e755513 and 4300378.

📒 Files selected for processing (79)
  • CACHE_SUPPORT_PLAN.md
  • crates/rginx-app/src/check/render.rs
  • crates/rginx-app/src/check/routes.rs
  • crates/rginx-app/src/check/summary.rs
  • crates/rginx-config/src/compile/cache.rs
  • crates/rginx-config/src/compile/mod.rs
  • crates/rginx-config/src/compile/route.rs
  • crates/rginx-config/src/compile/tests.rs
  • crates/rginx-config/src/compile/tests/cache.rs
  • crates/rginx-config/src/compile/tests/http3.rs
  • crates/rginx-config/src/compile/tests/listeners.rs
  • crates/rginx-config/src/compile/tests/route.rs
  • crates/rginx-config/src/compile/tests/route/regex.rs
  • crates/rginx-config/src/compile/tests/server_settings.rs
  • crates/rginx-config/src/compile/tests/server_tls.rs
  • crates/rginx-config/src/compile/tests/upstream_defaults.rs
  • crates/rginx-config/src/compile/tests/upstream_fallbacks.rs
  • crates/rginx-config/src/compile/tests/upstream_server_name.rs
  • crates/rginx-config/src/compile/tests/upstream_tls.rs
  • crates/rginx-config/src/compile/tests/upstream_transport.rs
  • crates/rginx-config/src/compile/tests/vhosts.rs
  • crates/rginx-config/src/compile/tests/vhosts/listener_conflicts.rs
  • crates/rginx-config/src/compile/tests/vhosts/nezha.rs
  • crates/rginx-config/src/load/tests.rs
  • crates/rginx-config/src/model.rs
  • crates/rginx-config/src/model/cache.rs
  • crates/rginx-config/src/model/route.rs
  • crates/rginx-config/src/validate.rs
  • crates/rginx-config/src/validate/cache.rs
  • crates/rginx-config/src/validate/route.rs
  • crates/rginx-config/src/validate/tests.rs
  • crates/rginx-config/src/validate/tests/cache.rs
  • crates/rginx-config/src/validate/tests/route.rs
  • crates/rginx-config/src/validate/tests/vhosts.rs
  • crates/rginx-config/src/validate/vhost.rs
  • crates/rginx-core/src/config.rs
  • crates/rginx-core/src/config/cache.rs
  • crates/rginx-core/src/config/route.rs
  • crates/rginx-core/src/config/snapshot.rs
  • crates/rginx-core/src/config/tests.rs
  • crates/rginx-core/src/lib.rs
  • crates/rginx-http/Cargo.toml
  • crates/rginx-http/src/cache/entry.rs
  • crates/rginx-http/src/cache/load.rs
  • crates/rginx-http/src/cache/mod.rs
  • crates/rginx-http/src/cache/policy.rs
  • crates/rginx-http/src/cache/request.rs
  • crates/rginx-http/src/cache/store.rs
  • crates/rginx-http/src/cache/tests.rs
  • crates/rginx-http/src/handler/dispatch/mod.rs
  • crates/rginx-http/src/handler/dispatch/route.rs
  • crates/rginx-http/src/handler/tests/routing/authorize.rs
  • crates/rginx-http/src/handler/tests/routing/handle.rs
  • crates/rginx-http/src/handler/tests/routing/selection.rs
  • crates/rginx-http/src/handler/tests/support.rs
  • crates/rginx-http/src/lib.rs
  • crates/rginx-http/src/proxy/clients/tests.rs
  • crates/rginx-http/src/proxy/forward/attempt.rs
  • crates/rginx-http/src/proxy/forward/cache.rs
  • crates/rginx-http/src/proxy/forward/failure.rs
  • crates/rginx-http/src/proxy/forward/mod.rs
  • crates/rginx-http/src/proxy/forward/setup.rs
  • crates/rginx-http/src/proxy/forward/streaming.rs
  • crates/rginx-http/src/proxy/forward/success.rs
  • crates/rginx-http/src/proxy/forward/types.rs
  • crates/rginx-http/src/proxy/health/registry/tests.rs
  • crates/rginx-http/src/proxy/tests/cache.rs
  • crates/rginx-http/src/proxy/tests/mod.rs
  • crates/rginx-http/src/router/tests.rs
  • crates/rginx-http/src/state/cache.rs
  • crates/rginx-http/src/state/helpers.rs
  • crates/rginx-http/src/state/lifecycle/reload.rs
  • crates/rginx-http/src/state/mod.rs
  • crates/rginx-http/src/state/tests/support.rs
  • crates/rginx-http/src/state/tls_runtime/bindings/tests.rs
  • crates/rginx-http/src/transition/tests.rs
  • crates/rginx-runtime/src/bootstrap/listeners/tests.rs
  • crates/rginx-runtime/src/bootstrap/shutdown/tests.rs
  • crates/rginx-runtime/src/health/tests.rs
📜 Review details
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-04-13T10:53:29.903Z
Learnt from: vansour
Repo: vansour/rginx PR: 43
File: crates/rginx-config/src/validate/server.rs:421-439
Timestamp: 2026-04-13T10:53:29.903Z
Learning: In `crates/rginx-config/src/validate/server.rs` (`validate_http3`), the check for `tls.session_tickets` when `http3.early_data = true` intentionally only rejects `Some(true)` and allows `None`. Requiring `Some(false)` breaks 0-RTT resumption and the `routes_http3_early_data_by_replay_safety` test and the CI `http3` gate in the current rustls/quinn integration. This is a deliberate design decision and should not be flagged as a bug until the runtime/session policy is updated to support that configuration.

Applied to files:

  • crates/rginx-http/src/handler/tests/routing/authorize.rs
  • crates/rginx-config/src/validate/tests/route.rs
  • crates/rginx-http/src/handler/tests/routing/selection.rs
  • crates/rginx-config/src/compile/tests/http3.rs
  • crates/rginx-config/src/compile/tests/upstream_defaults.rs
  • crates/rginx-config/src/compile/tests/server_tls.rs
  • crates/rginx-config/src/compile/tests/upstream_server_name.rs
  • crates/rginx-config/src/compile/tests/upstream_tls.rs
  • crates/rginx-config/src/compile/tests/cache.rs
  • crates/rginx-http/src/state/tls_runtime/bindings/tests.rs
  • crates/rginx-config/src/compile/tests/server_settings.rs
  • crates/rginx-config/src/load/tests.rs
  • crates/rginx-runtime/src/bootstrap/shutdown/tests.rs
  • crates/rginx-config/src/validate/vhost.rs
  • crates/rginx-http/src/cache/tests.rs
  • crates/rginx-config/src/validate/tests/vhosts.rs
  • crates/rginx-http/src/cache/mod.rs
  • crates/rginx-http/src/router/tests.rs
  • crates/rginx-config/src/validate/tests/cache.rs
🪛 LanguageTool
CACHE_SUPPORT_PLAN.md

[uncategorized] ~13-~13: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...YPASSEXPIRED`。 本文档按阶段规划 rginx 的缓存支持。第一目标是实现用户可配置的 HTTP 反向代理响应缓存。现有内部缓存,例如 DNS 解析...

(wa5)


[uncategorized] ~29-~29: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...tion的请求。 - 默认不存储带Set-Cookie的响应。 - 第一版将Range` 请求视为 bypass。 - 采用“内存 metadata/...

(wa5)

🔇 Additional comments (50)
crates/rginx-config/src/model/route.rs (1)

5-5: 新增字段与既有 LocationConfig 模式一致,LGTM。

cache: Option<CacheRouteConfig> 与同结构中其它可选字段写法一致,缺省时反序列化为 Nonecrates/rginx-app/src/check/routes.rscrates/rginx-config/src/validate/tests.rs 等下游也已正确处理。

Also applies to: 53-54

crates/rginx-config/src/model.rs (1)

3-3: 模块挂载与字段新增 LGTM。

mod cache;pub use cache::{CacheRouteConfig, CacheZoneConfig}; 与现有模块的命名/导出风格一致;cache_zones: Vec<CacheZoneConfig> 使用 #[serde(default)],老配置文件无需改动即可继续加载。

Also applies to: 12-12, 36-37

crates/rginx-config/src/compile/tests/vhosts/listener_conflicts.rs (1)

47-47: LGTM:仅是同步 Config 的新字段。

crates/rginx-runtime/src/bootstrap/listeners/tests.rs (1)

39-39: LGTM:与 rginx_core::ConfigSnapshot 新增字段保持一致。

注意此处使用 HashMap(运行时按 zone name 索引),与配置层的 Vec<CacheZoneConfig> 是不同的数据结构,符合预期。

crates/rginx-http/src/lib.rs (1)

3-3: LGTM:将缓存子系统作为 crate 内部模块挂载。

mod cache; 不对外导出,缓存子系统作为 rginx-http 的实现细节封装,避免污染公共 API 面,符合 MVP 阶段渐进暴露的策略。

crates/rginx-app/src/check/routes.rs (1)

15-15: summary.rs::cache_enabled_route_count 存在重复计数逻辑

RouteTransportCheckDetails::cache_enabled_routesCheckSummary::cache_enabled_route_count 各自独立遍历 default_vhost + vhosts 并判定 route.cache.is_some(),违反 DRY。建议让 CheckSummary 直接复用此处已计算的 route_transport.cache_enabled_routes,删除 summary.rs 中重复的计数函数,保持单一数据源。

Also applies to: 51-53

crates/rginx-http/src/handler/dispatch/route.rs (1)

48-48: LGTM!

将路由级 cache 策略透传到 DownstreamRequestOptions,类型一致(Option<RouteCachePolicy>),由于 route: RouteExecutionContext 按值传入,此处 move 也是安全的。

crates/rginx-config/src/compile/route.rs (1)

122-122: validate 阶段已覆盖 cache.zone 交叉校验

验证通过审查代码发现,validate_route_cache 函数(在 crates/rginx-config/src/validate/cache.rs 第46-54行)已完整实现了对 cache.zonecache_zone_names 的交叉校验。若引用的 zone 不存在,会抛出错误 references undefined cache zone,并配有单元测试验证此行为。无需额外处理。

crates/rginx-http/src/transition/tests.rs (1)

28-29: LGTM!

snapshot() 测试夹具补齐了新增的 cache_zones 字段并使用文件顶部已导入的 HashMap,与 upstreams: HashMap::new() 风格一致;transition planning 测试逻辑未受影响。

crates/rginx-http/src/handler/tests/routing/handle.rs (1)

45-64: LGTM!

Route 字面量补齐了新增的 cache: None 字段,测试断言(status 299、无 LOCATION、body b"299\n")保持不变。

crates/rginx-config/src/validate/tests/vhosts.rs (1)

146-167: LGTM!

LocationConfig 字面量补齐 cache: None,与新增 schema 对齐;vhost tls without listener 拒绝逻辑的断言保持不变。

crates/rginx-http/src/state/tls_runtime/bindings/tests.rs (1)

100-111: LGTM!

两个 ConfigSnapshot 字面量都已补齐 cache_zones: HashMap::new(),并使用文件顶部导入的 HashMaptls_binding_snapshots 调用与断言保持不变。

Also applies to: 139-150

crates/rginx-runtime/src/bootstrap/shutdown/tests.rs (1)

18-19: LGTM!

ConfigSnapshot.cache_zones 与默认路由 Route.cache 均已显式初始化,并复用顶部 use std::collections::HashMap; 的导入;graceful shutdown 行为测试未受影响。

Also applies to: 51-52

crates/rginx-config/src/validate/tests/route.rs (1)

115-137: LGTM!

新增的 LocationConfig 字面量补齐 cache: None,与 schema 同步;不同 gRPC 约束允许重复 exact 路由的断言保持不变。

crates/rginx-runtime/src/health/tests.rs (1)

54-55: LGTM!

ConfigSnapshot 字面量补齐 cache_zones: HashMap::new(),与 PR 中其它测试夹具风格一致;collect_probe_targets 的断言与上游枚举行为未变。

crates/rginx-config/src/compile/tests/upstream_tls.rs (1)

9-10: LGTM!

三个 Config 字面量补齐 cache_zones: Vec::new()(配置层使用 Vec,与编译后 ConfigSnapshot 使用 HashMap 的分层一致),三个 LocationConfig 同步补齐 cache: Nonecompile/compile_with_base 调用及对 proxy.upstreamactive_health_check 的断言保持不变。

Also applies to: 79-80, 138-139, 199-200, 244-245, 314-315

crates/rginx-config/src/compile/tests/vhosts/nezha.rs (1)

28-28: 测试夹具按新 schema 同步,无需评论。

仅是为新增字段补默认值,逻辑保持不变。

Also applies to: 61-61

crates/rginx-http/src/handler/tests/routing/selection.rs (1)

140-140: 测试夹具按新 Route.cache 字段同步,逻辑无变更。

Also applies to: 186-186

crates/rginx-http/src/handler/tests/routing/authorize.rs (1)

6-6: 仅同步新字段 cache: None,无功能改动。

Also applies to: 48-48

crates/rginx-config/src/compile/tests/listeners.rs (1)

6-6: Config.cache_zonesLocationConfig.cache 默认值同步,无逻辑变更。

Also applies to: 73-73

crates/rginx-http/src/router/tests.rs (1)

13-13: 所有路由夹具统一补齐 cache: None,覆盖一致,无需变更。

Also applies to: 51-51, 71-71, 99-99, 183-183, 206-206, 238-238, 278-278

crates/rginx-config/src/compile/tests/vhosts.rs (1)

13-13: 所有 Config 字面量补齐 cache_zones: Vec::new(),与新 schema 对齐。

Also applies to: 79-79, 134-134, 186-186, 215-215

crates/rginx-http/src/proxy/tests/mod.rs (1)

55-55: 新增 cache 子模块及空 cache_zones 字段初始化合理。

ConfigSnapshot::cache_zonesHashMap 初始化,符合 core 中 zones 按名称索引的设计;mod cache; 引入对应的代理缓存测试。

Also applies to: 202-202

crates/rginx-http/src/state/helpers.rs (1)

11-17: PreparedState 结构补充 cache 字段,错误传播路径符合既有约定。

?CacheManager::from_configError::Server 沿用现有错误流,且字段顺序与上下文构建顺序一致。

crates/rginx-http/src/proxy/clients/tests.rs (1)

107-107: LGTM!

测试夹具按 ConfigSnapshot 的新 cache_zones 字段补齐,符合预期。

crates/rginx-core/src/config/snapshot.rs (1)

4-14: LGTM!

cache_zones 字段类型 HashMap<String, Arc<CacheZone>>upstreams 风格一致,引入 CacheZone 的导入也匹配。

crates/rginx-http/src/state/tests/support.rs (1)

21-21: LGTM!

测试夹具按新增的 cache_zones / Route.cache 字段补齐为 HashMap::new() / None,与其他测试辅助保持一致。

Also applies to: 94-94, 119-119

crates/rginx-http/src/handler/dispatch/mod.rs (1)

52-52: LGTM!

RouteExecutionContext.cacheroute.cache.clone() 的传递与下游 build_route_response 链路所需的缓存策略一致。克隆成本相关的优化建议已在 crates/rginx-core/src/config/route.rs 中给出,此处不再重复。

Also applies to: 169-169

crates/rginx-core/src/config.rs (1)

2-15: LGTM!

cache 模块声明与 CacheKeyRenderContextCacheKeyTemplateCacheKeyTemplateErrorCacheZoneRouteCachePolicy 的 re-export 与下游 crate(rginx-configrginx-http)的引用面一致。

crates/rginx-config/src/compile/tests/route.rs (1)

8-8: LGTM!

各测试用例的 Config.cache_zonesLocationConfig.cache 已统一补齐为 Vec::new() / None,与编译流水线新增字段对齐。关于减少字面量重复的建议见 crates/rginx-config/src/compile/tests/upstream_server_name.rs 中评论,不再重复。

Also applies to: 36-36, 70-70, 98-98, 134-134, 171-171, 233-233, 298-298, 335-335

crates/rginx-config/src/compile/mod.rs (1)

57-57: 此问题已在校验层被正确处理,无需修改

validate::cache::validate_cache_zones(第 7-35 行)在校验阶段已明确处理重复检查:

  • 第 11 行:对 zone 名称进行 trim()
  • 第 15-17 行:使用 HashSet::insert() 并检查返回值,重名时返回 Err(Error::Config("duplicate cache zone {name}"))

因此,重名 zone 会在校验阶段被拒绝,永不到达编译阶段。名称仅靠首尾空白区分的两个 zone 也会被识别为重复(均在 trim 后比较)。编译阶段的 HashMap 收集不存在静默覆盖风险。

crates/rginx-app/src/check/render.rs (1)

8-14: 已按上次评审拆出 print_cache_zone_details,职责划分清晰。

print_check_success 中分别调用路由传输与缓存区详情打印,缓存区缺省 max_size_bytes"-" 表示,输出格式与其他 check 行风格一致。

Also applies to: 60-72

crates/rginx-http/src/proxy/forward/mod.rs (1)

7-23: 模块装配清晰,导入与子模块定义匹配。

新增的 cachefailure 子模块以及对应 use 项与上下文片段中实际导出的符号一致(ForwardCacheLookupForwardCacheContextlookup_forward_cachefailed_to_build_request_responseupstream_unavailable_response),无重复或多余引入。

crates/rginx-http/src/proxy/forward/setup.rs (1)

21-21: 改为按引用传参合理。

由于 DownstreamRequestContext 已不再实现 Copy(见 forward/types.rs),调用方需要在 prepare_forward_request 之后继续使用 downstream(如 attempt.rs 第 96/120 行),改成 &DownstreamRequestContext<'_> 是必要且最小化改动。函数体只读访问 optionsrequest_id,与引用语义兼容。

crates/rginx-config/src/validate.rs (1)

26-39: 验证流程接入正确。

validate_cache_zones 先于 route::validate_locationsvhost::validate_virtual_hosts 调用,确保后续按引用使用的 cache_zone_names 已完成去重与字段校验(参考 validate/cache.rs 的实现)。两处下游校验都接收 &cache_zone_names,无所有权问题。

crates/rginx-config/src/validate/tests.rs (1)

1-12: 测试模块装配与夹具更新一致。

CacheRouteConfigCacheZoneConfig 通过本模块导入后,可被新增的 mod cache 子测试通过 use super::*; 复用,模式与现有同级子模块一致。base_config()/sample_vhost() 显式补齐 cache_zonescache: None,避免编译期遗漏字段错误。

Also applies to: 27-27, 88-88, 139-139

crates/rginx-http/src/state/mod.rs (1)

82-82: std::sync::RwLock 的使用是安全的,guard 在 .await 前已释放。

SharedState.cache 使用 Arc<StdRwLock<CacheManager>> 是正确的设计。在 lookup_cached_responsestore_cached_response 中,代码模式为:

let cache = { self.cache.read().unwrap_or_else(...).clone() };
cache.lookup(...).await

块级作用域 { } 确保读锁在 .await 前释放,仅用于获取 CacheManager 的克隆(指针复制),不涉及长时间持锁。在 CacheManager::lookup()store_response() 内部:

  • 对同步数据结构 index 的访问限制在同步代码段,锁立即释放
  • 磁盘 I/O(如 read_cached_response)通过 AsyncMutex 保护,不使用同步锁

无需改用 tokio::sync::RwLock 或重构。代码已正确避免在 .await 期间持有同步锁。

crates/rginx-config/src/validate/tests/cache.rs (1)

57-81: 已按上一次评审建议拆分用例。

max_entry_bytes 超限与不支持的 POST 方法已分别拆为 validate_rejects_max_entry_bytes_exceeding_max_size_bytesvalidate_rejects_unsupported_cache_method,失败定位更清晰。

crates/rginx-config/src/compile/tests/cache.rs (1)

122-145: 已补齐默认回退断言。

第二次编译现在显式断言了 policy.key.as_str() == "{scheme}:{host}:{uri}"policy.stale_if_error == None,完整覆盖 compile_route_cache 的默认值路径,与上一次评审建议一致。

crates/rginx-http/src/proxy/forward/streaming.rs (1)

9-9: 签名改为借用引用,与上层调用一致。

downstream 改为 &DownstreamRequestContext<'_> 后避免了所有权转移,与 attempt.rs 中后续仍需复用 downstream 的流程一致,内部仅做字段读取,语义未变。

crates/rginx-core/src/lib.rs (1)

7-20: 新增缓存相关公共类型的 re-export。

正确暴露了 CacheKeyRenderContextCacheKeyTemplateCacheKeyTemplateErrorCacheZoneRouteCachePolicy,与 rginx-httprginx-config 的使用方式对齐,无遗漏。

crates/rginx-config/src/validate/route.rs (1)

94-118: 缓存与 Handler 兼容性检查接入正确。

validate_cache_handler_compatibility 在调用 validate_route_cache 前先校验 handler 必须为 Proxy,顺序合理(在 zone 名称未定义等错误前先暴露语义错误)。两个 validate_locations 调用点(validate.rsvalidate/vhost.rs)都已传入 cache_zone_names,签名变更的影响面已闭合。

crates/rginx-http/src/proxy/forward/cache.rs (1)

13-36: lookup 适配层逻辑正确。

  • policyNone 时短路返回 Proceed,避免不必要的 lock/clone 读取(参考 state/cache.rs::lookup_cached_response 会克隆共享 cache)。
  • Miss 分支先取 cache_status() 再将 context move 进 store,顺序正确(CacheStatus 派生 Copy)。
  • Bypass 分支不携带 store,与后续 store_response 的“无 store 即跳过写入”流程一致。
crates/rginx-http/src/cache/policy.rs (1)

59-98: TTL 解析逻辑正确,边界处理符合预期。

  • s-maxage 在跨多个 Cache-Control 值或同一值内多个指令时优先于 max-age(在任意一处遇到 s-maxage 立即返回),与 HTTP 缓存语义一致。
  • 过期 Expires 通过 unwrap_or(Duration::ZERO) 显式映射为零 TTL,而非回退到 default_ttl,且注释清晰解释了原因。
  • httpdate::parse_http_date 失败回退为 None,会落回 default_ttl,符合“无法解析则视作无 freshness 元数据”的预期。

如果想消除 Line 73-74、80 重复的 name.trim() 调用可做小幅优化,但目前可读性可接受。

crates/rginx-http/src/proxy/forward/attempt.rs (1)

12-23: 缓存命中分支仍直接返回,绕过下游响应处理。

ForwardCacheLookup::Hit(response) 直接 return response,未经过 build_downstream_response / sanitize_response_headers / IdleTimeoutBody / GrpcDeadlineBody / ActivePeerBody 等处理;与非缓存路径在 finalize_upstream_success 中获得的转换不一致。该问题在先前评审中已经提出,目前代码仍未修复。请确认是否计划在后续提交中处理,或在此 MVP 中给出明确取舍说明(例如缓存响应在 store_response 时已确保头部安全且使用 full_body,所以无需再次包裹)。

crates/rginx-http/src/proxy/forward/success.rs (1)

41-62: 升级响应分支显式丢弃 cache_store 行为合理。

WebSocket 等升级响应(101)本身不应被缓存,第 50 行将 cache_store 显式传入 None、仅保留 cache_status 用于头部标记,与缓存语义一致。常规分支同时透传 cache_storecache_status,由 apply_cache_store/store_cached_response 内部统一负责盖戳,避免双重写入 x-cache

CACHE_SUPPORT_PLAN.md (1)

1-12: 阶段计划文档与代码实现一致。

文档第 8–11 行声明阶段 2/3 已完成 MVP(含磁盘元数据 + 内存索引 + 启动扫描 + X-Cache 输出),与 crates/rginx-http/src/cache/mod.rs:108 中的 load_index_from_disk 启动扫描以及 with_cache_status 写入 HIT/MISS/BYPASS/EXPIRED 的实现保持一致。

crates/rginx-http/src/cache/entry.rs (1)

102-131: 临时文件 per-write 唯一后缀已修复并发覆盖问题。

next_temp_suffix() 使用 CACHE_TMP_COUNTER 原子计数 + process::id() 生成唯一后缀,配合 cleanup_failed_write 在各失败路径只删自己的临时文件,已经消除了之前 metadata_tmp/body_tmp 派生自固定 hash 时的并发交错风险。rename_body 成功而 rename_metadata 失败那一支用 remove_final=true 清理已落盘的不一致状态也是正确的。

crates/rginx-core/src/config/cache.rs (1)

54-130: CacheKeyTemplate::parserender 实现整体稳健。

  • 大括号转义({{ / }})、UTF-8 边界处理(index += ch.len_utf8())、变量未闭合 / 未转义 } / 空变量名 / 未知变量这几个错误分支都已覆盖;
  • 变量名经 trim() 后再匹配,意味着 { scheme } 等价于 {scheme},但保持大小写敏感({Scheme} 会拿到 UnknownVariable)——这是一个"严格模式",建议在 CACHE_SUPPORT_PLAN.md 的 cache key 部分简单说明;
  • 第 116 行 String::with_capacity(self.raw.len() + context.uri.len()) 的容量启发式偏向 uri,对包含多个变量或 host/method 较长的模板会偏小,但仅影响一次 Vec 增长,可忽略。
crates/rginx-config/src/compile/cache.rs (1)

16-43: 此问题已被 validator 处理。

validate_cache_zones()crates/rginx-config/src/validate.rs 中被调用,使用 HashSet 在执行 .trim() 后检测重复的 zone 名,若发现重复则返回错误:"duplicate cache zone {name}"。因此重复的 zone 不会到达 compile 函数的 HashMap,无需额外的手动检测。

Comment thread crates/rginx-config/src/compile/route.rs
Comment thread crates/rginx-config/src/compile/tests/upstream_fallbacks.rs
Comment thread crates/rginx-config/src/compile/tests/upstream_server_name.rs
Comment thread crates/rginx-config/src/load/tests.rs
Comment thread crates/rginx-config/src/model/cache.rs
Comment thread crates/rginx-http/src/cache/store.rs
Comment thread crates/rginx-http/src/cache/store.rs
Comment thread crates/rginx-http/src/cache/store.rs
Comment thread crates/rginx-http/src/state/helpers.rs
Comment thread crates/rginx-http/src/state/lifecycle/reload.rs Outdated
@vansour vansour force-pushed the feat/cache-support-mvp branch from 4300378 to 61501e6 Compare April 27, 2026 12:01
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 61501e6077

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/rginx-http/src/cache/load.rs
@vansour vansour force-pushed the feat/cache-support-mvp branch from 61501e6 to adb9ff2 Compare April 27, 2026 12:15
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@vansour
Copy link
Copy Markdown
Owner Author

vansour commented Apr 27, 2026

Update after adb9ff2:

  • Real CI is green: Format, Modularization, Clippy, Test Fast, Test Slow, and TLS Gate all passed.
  • Addressed the latest actionable cache review items: HEAD-only cache methods are rejected, cache/config/client state now comes from the same active snapshot, expired entries are removed from index and disk on lookup, startup scan skips bad directory entries instead of aborting the whole zone, cache body collection failures now return a clear 502 instead of an erroring original response body, failed cache writes preserve any still-valid old index entry, unknown runtime cache zones warn before bypassing, and default max_entry_bytes is clamped to max_size_bytes when the user only configures max_size_bytes.
  • On the remaining cache-hit direct-return concern: this is intentional for the MVP. Cached responses are stored after build_downstream_response has already produced the downstream response; cache metadata filters hop-by-hop headers and x-cache, records a sane Content-Length, and HIT responses are returned as full bodies (HEAD reads metadata only and returns an empty body). There is no upstream peer/timeout body to re-wrap on a cache HIT, so re-entering the upstream success path would not add the skipped upstream-only wrappers.

CodeRabbit is currently rate-limited on this PR, so its old CHANGES_REQUESTED review has not been refreshed yet.

@vansour vansour dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] April 27, 2026 12:24

Superseded by commits 61501e6 and adb9ff2. Required CI checks are green; remaining CodeRabbit review is stale and currently rate-limited.

@vansour vansour merged commit 819facc into main Apr 27, 2026
7 checks passed
@vansour vansour deleted the feat/cache-support-mvp branch April 27, 2026 12:26
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