Skip to content

feat(volo-http): track matched route patterns - #675

Open
Joshuahoky wants to merge 3 commits into
mainfrom
feat/http-matched-path
Open

Joshuahoky wants to merge 3 commits into
mainfrom
feat/http-matched-path

Conversation

@Joshuahoky

Copy link
Copy Markdown
Contributor

Motivation

Currently, requests use the raw URL path for logging and metrics, which can lead to high cardinality when paths contain dynamic parameters.

For example, requests to /users/123 and /users/456 should be associated with the same route pattern, /users/{id}.

Solution

This change adds MatchedPath to identify requests by their registered route patterns instead.

@codecov

codecov Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.26316% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.42%. Comparing base (58cfc6b) to head (bd99009).

Files with missing lines Patch % Lines
volo-http/src/server/route/router.rs 95.26% 9 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #675      +/-   ##
==========================================
+ Coverage   61.68%   62.42%   +0.74%     
==========================================
  Files         167      167              
  Lines       22604    22786     +182     
==========================================
+ Hits        13943    14225     +282     
+ Misses       8661     8561     -100     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Joshuahoky
Joshuahoky force-pushed the feat/http-matched-path branch from 79edccd to a9cdcac Compare October 9, 2026 08:47
@shenyj3

shenyj3 commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

Benchmark in my env:

  每个场景预热后执行 10,000 次请求,平均结果如下,箭头表示“改动前 → 改动后”:

   场景                     分配次数/请求    realloc 次数/请求    累计申请字节/请求    字节增量
  ━━━━━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━
   静态路由(1/256 条)             2 → 3                0 → 0          1384 → 1424       +40 B
  ───────────────────────  ───────────────  ───────────────────  ───────────────────  ──────────
   参数路由(短/长模式)            5 → 6                0 → 0          1748 → 1788       +40 B
  ───────────────────────  ───────────────  ───────────────────  ───────────────────  ──────────
   1 层嵌套                       12 → 14                1 → 2          3886 → 3950       +64 B
  ───────────────────────  ───────────────  ───────────────────  ───────────────────  ──────────
   3 层嵌套                       26 → 30                3 → 5          8198 → 8306      +108 B
  ───────────────────────  ───────────────  ───────────────────  ───────────────────  ──────────
   6 层嵌套                       47 → 55               6 → 11        14756 → 15068      +312 B
  ───────────────────────  ───────────────  ───────────────────  ───────────────────  ──────────
   3 层长前缀嵌套                 26 → 32                3 → 6          8405 → 8979      +574 B
  ───────────────────────  ───────────────  ───────────────────  ───────────────────  ──────────
   顶层 404                         2 → 2                0 → 0            840 → 840           0
  ───────────────────────  ───────────────  ───────────────────  ───────────────────  ──────────
   3 层嵌套后 404                 24 → 27                3 → 4          7601 → 7673       +72 B

It looks OK.

shenyj3
shenyj3 previously approved these changes Oct 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. 两套并行表:Router 同时维护 routes: HashMap<RouteId, Endpoint> 与 patterns: HashMap<RouteId, FastStr>,同一 uri 分三处存(matchit 树、Matcher.matches、Router.patterns),每个 route/nest/merge 都要把 self.patterns.insert(...) 跟在 self.matcher.insert(...) 旁边手写一遍,三处易失步。
  2. 运行时字符串拼接:因为 nest 是运行时委托(StripPrefix 层),MatchedPath::record 里用 outer.0.to_string() + pattern 逐层拼前缀——每层一次分配、整串重拷,k 层嵌套为 O(k) 次分配、O(k²) 字节拷贝,且只在多级嵌套时才明显。
  3. 第二次 hash:call 里先 routes.get 拿 handler,再 patterns.get 拿样式——对同一条请求做两次 HashMap 查询。

改动(均在 volo-http/src/server/route/router.rs)

  • patterns: HashMap<RouteId, FastStr> 字段删除,合并进 routes:
    routes: HashMap<RouteId, (Endpoint<B, E>, FastStr)>,handler 与 pattern 打包一处。
  • route():routes.insert(id, (Endpoint::MethodRouter(...), FastStr::new(uri))),单次插入。
  • nest():入参改 mut router,构建期把嵌套前缀(去尾部 /)预拼进内层每个 pattern(如 /api/{version}/items/{id}),再 nest_route。
  • nest_route():nest 自身 route 的 pattern 即为前缀;routes.insert(id, (Endpoint::Service(...), FastStr::new(prefix.trim_end_matches('/'))))。
  • merge() / layer() / new():同步去掉 patterns 字段及相关逻辑(merge 直接搬 tuple routes,删掉原 patterns.drain 循环)。
  • call() 热路径由:
    “routes.get 拿 handler + patterns.get 拿样式 + record 运行时拼接”
    降为:
    “一次 self.routes.get(matched.value) 解出 (route, pattern) + 一行 cx.extensions_mut().insert(MatchedPath(pattern.clone()))”。
  • 删除 MatchedPath::record helper(to_string() + pattern 的拼接函数),as_str / into_inner / Deref / AsRef 保留。

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants