CYBERSWAT / 开发部自由练习 · v0.4.1

U08 · 第二次请求为什么没有到达上游

v0.4 · 先修:手册 12、15、17、22–23;工作台。选题参考:Caching Proxy。仅连接自己建立的本地模拟服务。

做两个本地服务:上游每收到一次请求,计数加一;缓存服务暂存上游结果。连续请求相同路径时,观察第二次是否真的没有抵达上游。这里练习缓存与 HTTP,不提供访问限制绕过功能。

动手路线:每一步都有文件和结果

先完成 统一启动与恢复手册 的首次准备。以下命令均在自己的练习根目录执行;原文中的 api/web/desktop 概念对应本节列出的现成文件,不需要猜测如何搭空项目。

  1. 打开 u08/starter/model.ts,按 cacheKey → readCache → writeCache 的顺序逐个实现。下面第1节起解释规则;每完成一个函数就重跑样例/测试。
  2. 执行以下三条,分别核对样例、行为和类型。未完成的 TODO 应失败,全部实现后测试应为 fail 0:
node tools/try.mjs u08
node tools/check.mjs u08
npm run check
  1. 打开 runtime/src/u08.ts、tools/target.mjs,找到对自己规则函数的调用。先用一句话记录输入从哪里来、结果写到哪里;读不懂的语法回到本节先修对应章节。

  2. 另开终端运行 node tools/lab.mjs target 并保持打开。构建并启动自己的作品:

node tools/build-app.mjs u08
node tools/lab.mjs start u08
  1. 实际操作:启动模拟目标后,同一查询连续请求看到 MISS→HIT;等待超过1秒再请求为MISS;带身份/Cookie及上游Set-Cookie时BYPASS。

在第二个终端运行 node tools/probe.mjs u08。脚本会添加合成数据并报告每组检查;不清空自己的数据。

  1. 解释与再做一次:完整查询参数为什么属于缓存键?响应错误为什么不能保存成成功缓存? 在自己的 LEARNING.md 先写答案,再选本节末尾的变式,列一个新失败例子后修改。保持原用例通过。仅跑通配套界面、没有完成规则与解释,记录为“参考演示已观察”,不记录为自己完成作品。

卡在规则时按“函数输入→分支条件→返回值→参考实现”逐级看提示;卡在运行时按 故障表 定位。若想单独观察参考成品,在构建命令后加 --reference;页面会明确标出参考演示。观察完再去掉该参数重建,继续自己的 starter。

1. 先让时间可控制

打开 u08/starter/model.ts。Entry 有 value 与 expiresAt;now 和 ttl 都是毫秒。不要在核心内部直接读当前时间,测试要能把时钟放在过期前一毫秒和正好过期时。

完成 writeCache 与 readCache:ttl 必须为正有限数,now 必须有限;只有 now 小于 expiresAt 才命中,等于就失效。找不到返回 null,空字符串则可以是合法缓存值,不能用真假判断混淆。

2. 定义哪些请求能共用结果

完成 cacheKey。本实验只缓存不带 Authorization 的 GET;键包含完整路径与查询字符串。POST 和带身份请求返回 null,表示不进入共享缓存。Cookie 路径在服务层同样绕过,并禁止缓存有 Set-Cookie 的响应。

运行 node tools/check.mjs u08,再自己给空字符串值补一个测试。考虑 /items?q=1 与 /items?q=2:路径相同并不意味着内容相同。

3. 做一个可观察的上游

运行配套 node tools/lab.mjs target,绑定本机3401。返回 JSON 包含 count、path、mode;POST /mode?value=down 切为503,up恢复,slow延迟1500毫秒。验收脚本自动切换,目标响应里的count是实际请求计数。

上游退出用 Ctrl+C,计数重置是本阶段明确边界。不要连接外站来验证,因为它们的限流、内容变化和可用性会混入你的结论。

4. 建缓存服务并保留响应证据

配套 runtime/src/u08.ts 由 Nest 应用加载,默认绑定本机3400;上游地址固定在配置中为本机 3401,不能由请求参数任意指定目标。请求流程:检查可缓存性→读缓存→未命中时 fetch 上游→读取状态/正文→按条件保存→返回。只缓存 200 且无身份相关头的 /data 结果,失败不缓存。

返回自定义 X-Lab-Cache: HIT/MISS/BYPASS 说明实际路径。首个请求 MISS、第二个 HIT,正文 count 相同,上游日志只新增一次。先给 ttl 1000 毫秒,过期后再请求应为 MISS 且 count 增加。真实 HTTP 测试可等待,核心时间边界测试不需要 sleep。

5. 处理失败、清空和生命周期

fetch 设置超时并捕获错误,超时/断开返回你约定的 502/504;不要将错误正文保存为正常结果。POST /api/cache/clear 只在本机实验使用,执行后下一次请求必须 MISS。停止上游,证明缓存未命中时会失败,而不是永远显示过期成功。

本练习的缓存位于内存,重启缓存服务会清空,这是 README 中的明确行为。不要把它称为完整 HTTP 缓存实现;Vary、条件请求等尚未覆盖。

6. 自检与变式

检查首次/第二次、查询差异、正好过期、清空、POST、带 Authorization/Cookie、上游503、断开和超时。保存两个终端的脱敏日志,能指出哪个请求真正到了上游。

独立变式:设置最多 3 个缓存条目,明确淘汰规则并测试第 4 项进入时谁离开。给异常请求补用例后仍能通过旧测试。暂停时停止两个服务;下一次先上游后缓存。后续 U10 会继续使用本地故障目标。