topcoat 源码解析
全卷只对着一个版本写:
| crate | 版本 | tag | commit | 日期 |
|---|---|---|---|---|
| topcoat 及工作区里所有发布的子 crate | 0.10.0 | v0.10.0 | 3e7bb43 | 2026-10-03 |
topcoat 是 tokio 组织下的全栈 Web 框架,README 开头用粗体写着「Early-stage and experimental. Expect breaking changes.」本卷只对 0.10.0 负责:tag 之后两周里上游又合了 28 个提交,其中有两处正好改到 02 router 讲的代码(给 hyper 配计时器、限定模块路由的发现范围),讲到时会注明。
它和前几卷的关系有点特别:topcoat 不依赖 axum。服务器直接用 hyper 1 和 hyper-util 的 auto::Builder(hyper 卷),路径匹配直接用 matchit 0.9,tower 只在压缩和「与 tower 互操作」两处出现(tower 卷)。所以读它的路由,等于看另一种在 hyper 之上搭框架的办法,和 axum 卷可以逐处对照。
全卷沿三条线展开:
- 请求主线(服务端 → HTML):
topcoat::start绑端口,accept 循环给每个连接起一个任务;Router先用catch_unwind把整个请求包起来,再给请求组一个按类型存值的上下文Cx,用 matchit 找端点、按方法挑路由,外面永远先过一层 Origin 检查;#[page]渲染出的视图先发首屏,suspense里还没完成的部分以<template>加一小段脚本追加在同一个分块响应里。 - 发现主线(启动时,路由从哪来):没有 build.rs,也不扫文件系统。
#[page]等属性宏展开时用 inventory 在链接期登记自己,并记下module_path!();module_router!()在启动时把这些登记项取出来,按模块路径切段,推出 URL。 - 交互主线(浏览器 → 服务端 → 局部 HTML):
$(...)表达式同时生成服务端求值的 Rust 和写进 HTML 属性的 JavaScript;只读浏览器端状态的表达式就地重算,需要服务端的部分(shard)通过 WebSocket 或 HTTP 请求重新渲染,新 HTML 被 morph 进页面。
flowchart TB intro[00 导读] --> overview[01 全景] overview --> router[02 router] router --> cx[03 上下文] cx --> view[04 view] view --> rt[05 浏览器运行时] router --> assets[06 资产与工具链] router --> sec[07 安全默认值] rt --> integ[08 第三方交互集成] rt --> vs[09 与本站方案对比] vs --> design[10 设计取舍] design --> pit[11 常见坑] pit --> site[12 在本站中] site --> hands[13 动手]
实验环境
文中每个示例都实际跑过一次:Apple M2 Max、macOS、rustc 1.99.0,依赖锁在 topcoat =0.10.0(full feature)、tokio =1.54.1,解析出来的是 hyper 1.12.0、hyper-util 0.1.21、matchit 0.9.2、tower 0.5.3、tower-http 0.7.1。上游自己的 Cargo.lock 用的是 tokio 1.51.1、hyper 1.9.0,版本不同的地方只影响行为细节,不影响本卷讲的 topcoat 代码。服务器都绑在 127.0.0.1,用 curl 或一个打印每次 read 时间的小客户端发请求。
各部分
- 00 导读:锁定的版本、三条主线、阅读路线和前置知识;17 个 crate 的分层,
X/X-grammar/X-macro三件套,门面 crate 的 feature。 - 01 全景:topcoat 想解决什么问题,和同类方案的位置;一个带
suspense的页面从监听到 HTML 的全过程,逐段标注分块响应。 - 02 router:本卷重点。连接怎么接进来、请求怎么分发;路由表和 layer 链;模块路由与链接期发现;请求、响应与「错误即控制流」;tower 互操作、可信代理与压缩。
- 03 上下文:
Cx是一张按类型存值的表,#[memoize],以及官方「函数,而不是中间件」的建议。 - 04 view:
view!展开成什么;组件、同一任务里的并发渲染与流式。 - 05 浏览器运行时:本卷另一个重点。
$(...)一份 Rust 两种执行;signal、shard 与 procedure。 - 06 资产与工具链:
asset!与二进制里的字节标记;tailwind、font、icon 与topcoatCLI。 - 07 安全默认值:Origin 检查、cookie 与 session。
- 08 第三方交互集成:htmx、Datastar、Alpine AJAX 都是读写请求头的薄层,片段与整页共用 URL 时缺的
Vary;顺带mail!。 - 09 与本站方案对比:topcoat 与 Dioxus fullstack,按源码事实和实测逐项比,写各自适合什么。
- 10 设计取舍:把各章的取舍汇总到一起,附 0.10.0 的已知问题表。
- 11 常见坑:用报错原文或现象做标题,每个都先跑出最小复现。
- 12 在本站中:本站没有用 topcoat,这一章是「把本站一页用 topcoat 写会怎样」的对照。
- 13 动手:用 hyper 和 matchit 写一个迷你版的思路。
从「导读」开始。只想会用 topcoat 的读者,读 01、02 的模块路由、04、05、07、11;想读懂实现的,按顺序读。