Tea 中间件机制深度解析:如何快速为仓颉 Web 框架打造请求拦截链
【免费下载链接】tea仓颉语言轻量级的、函数式的、高效的HTTP Web后端框架项目地址: https://gitcode.com/Cangjie-SIG/tea
🍵Tea是一个用仓颉(Cangjie)语言编写的轻量级、函数式、高效的 HTTP Web 后端框架。它的中间件(Middleware)机制让"请求拦截链"变得极其简单——你只需几行代码,就能在任意路由前插入鉴权、日志、CORS、限流等逻辑,而完全不必污染业务 Handler。本文带你从原理到实战,一次看懂并亲手打造属于自己的请求拦截链。
核心关键词:Tea 中间件、仓颉 Web 框架请求拦截链、use 注册中间件、CORS 中间件、共享参数 set/get、钩子函数 Hooks
一、什么是 Tea 中间件?先看懂"洋葱模型"
🧅 中间件的本质,是一层包裹在请求与响应外围的处理函数。一个请求进来后,会依次"剥开"多个中间件;当它最终到达业务 Handler 后又"逐层回包"——这就是经典的洋葱模型(Onion Model)。
在 Tea 里,中间件和 Handler 是同一种东西:
public type HandlerFunc = (TeaContext) -> Unit // 接收上下文,返回 Unit public open class Handler { ... } // 仿函数,可携带元信息 metaData- 每个中间件接收一个
TeaContext(下文简称c),代表当前请求的完整上下文。 - 调用
c.next()→ 放行,进入下一个中间件 / Handler。 - 不调用
c.next()→ 拦截链在此中断,请求不会继续往下走。
💡 一句话记忆:
next()是油门,不调就是刹车。这是所有拦截能力(鉴权拒绝、限流、错误兜底)的来源。
中间件的入口定义在 src/core.cj,Handler作为仿函数还能携带metaData(一个HashMap),方便你给每个处理函数"打标签"。
二、如何注册中间件:use 方法的 3 种写法
Tea 用use方法注册中间件(实现在 src/tea.cj)。按作用范围,它有 3 种常见用法:
| 写法 | 作用范围 | 典型场景 |
|---|---|---|
app.use { ... } | 全部路由、全部方法 | 全局日志、统一 CORS |
app.use("/api", { ... }) | 仅匹配路径前缀 | /api接口鉴权 |
group.use { ... } | 仅路由组内部 | 版本分组/v1、/v2独立策略 |
1. 全局中间件(最简单)
let app = Tea.default() // 为 app 下全部路由注册:任意请求都会先经过它 app.use { c => println(">>> 收到请求 ${c.getMethod()}") c.next() // 必须调用,否则会中断 }2. 按路径前缀注册
// 只有 /admin 开头的路由才走这段中间件 app.use("/admin") { c => // 校验管理员身份,失败则不调用 c.next() 即可拦截 c.next() }3. 路由组(Group)中间件
通过 src/group.cj 的use,给整个版本组挂一套独立策略,是组织多版本 API 的最佳实践:
let v1 = app.group("/api/v1") { c => c.set("version", "v1") // 给整组打标签 c.next() } v1.get("/list") { c => /* ... */ }✅ 小提示:
use注册的是"通配方法"路由,意味着它对 GET/POST/PUT 等所有 HTTP 方法都生效,无需为每个方法单独注册。
三、请求拦截链是如何执行的?next() 逐层拆解 🚀
很多人只记住了c.next(),却不知道它背后是两级推进。我们把执行引擎拆开看,你就彻底懂了。
第一级:TeaContext.next()(中间件内部推进)
源码见 src/context.cj:
public func next(): Unit { this.indexHandler ++ // 指针 +1 if (this.indexHandler < this.route.handlers.size) { this.route.handlersthis.indexHandler // 执行“下一个” return } this.app.next(this) // 本路由用完 → 交给 Tea }- 当前路由上挂着一串
handlers(中间件 + 主 Handler 排成一队)。 - 每调一次
next(),indexHandler前进一格,执行队列中的下一个函数。
第二级:Tea.next()(路由之间推进)
当一个路由的 handlers 全部用完,会调用 src/tea.cj 的next():
func next(ctx: TeaContext): Unit { // 1. 按 method + 前缀树取出候选路由列表 // 2. 逐个尝试 matchPath 匹配 // 3. 命中后:ctx.indexHandler = 0,执行 handlers[0] // 4. 都没命中:抛出 404 }为什么中间件一定在 Handler 之前?
答案在注册逻辑 src/tea.cj:
let handlers = ArrayList<Handler>() if (middleware.size > 0) { handlers.add(all: middleware) // ① 先放中间件 } if (handler.isSome()) { handlers.add(handler.getOrThrow()) // ② 再放主 Handler }📌中间件永远排在主 Handler 前面——这正是"拦截"二字的由来:它们先于业务逻辑执行,也先有机会"截停"请求。
四、在中间件之间传递数据:set 与 get 共享参数 🔗
同一条拦截链上的所有函数共享同一个TeaContext。想把"上游算出来的东西"交给"下游",用set/get即可(实现在 src/context.cj,底层是一个ConcurrentHashMap)。
app.use { c => c.set("userId", "u-10086") // 上游:写入共享参数 c.next() } app.get("/test") { c => let uid = c.get("userId") // 下游:读取 c.sendString("hello ${uid}") // => "hello u-10086" }典型用途:认证中间件把登录用户信息set进上下文,后续所有 Handler 直接get取用,避免重复解析 Token。
🧠 注意:
set的 value 是Any类型,取出后通常需要as转型(如book as String)。
五、实战:用内置 CORS 中间件拦截跨域请求 🛡️
Tea 自带一个非常完整的CORS 跨域中间件,位于 src/middleware/cors/cors.cj。它是"中间件能力"的最佳样板——一个函数,搞定预检请求(OPTIONS)、来源白名单、凭证控制。
它拦截了什么?
- 读取请求头
Origin,比对白名单allowOrigins(支持*与://*.通配子域)。 - 遇到
OPTIONS预检 → 直接返回204 No Content,不调用c.next(),把跨域请求"就地拦截"。 - 普通请求 → 写入
Access-Control-Allow-*系列响应头后c.next()放行。
核心拦截片段(src/middleware/cors/cors.cj):
// OPTIONS 预检且无方法头 → 直接放行给后续 if (c.getMethod() == MethodOptions && c.getRequestHeader(HeaderAccessControlRequestMethod) == "") { c.vary(HeaderOrigin) return c.next() } // ... 校验来源、设置响应头 ... return c.sendStatus(StatusNoContent) // 拦截:不进入业务怎么用?只需 3 行
import tea.middleware.cors.cors let app = Tea.default() app.use(cors(CorsConfig(allowOrigins: ["https://example.com"]))) app.get("/") { c => c.sendString("ok") } app.run(8080)✅ 这让你零成本获得生产级跨域拦截——来源校验、预检响应、
Credentials互斥校验(*与凭证不可并存会直接抛错)全部内置。
六、进阶:中间件 vs 钩子函数 Hooks 的区别 ⚙️
Tea 除了"请求期"的中间件,还提供 6 类生命周期钩子(Hooks),定义在 src/hooks.cj。它们的作用时机完全不同:
| 维度 | 中间件(use / next) | 钩子函数(Hooks) |
|---|---|---|
| 触发时机 | 每个 HTTP 请求 | 框架生命周期节点 |
| 典型用途 | 鉴权、日志、CORS、限流 | 路由/分组注册监听、启动、关闭 |
| 能否拦截请求 | ✅ 能(不调 next 即拦截) | ❌ 否(只做旁路通知) |
6 类钩子一览:
OnRoute/OnName—— 添加 / 命名路由之后OnGroup/OnGroupName—— 添加 / 命名分组之后OnListen—— 启动监听之前OnShutdown—— 调用shutDown()时
app.hooks.registerOnListen { println("即将启动,可在此做预热 / 校验") }🎯 选型口诀:要拦截请求 → 用中间件;要监听框架事件 → 用 Hooks。
七、错误兜底:中间件抛异常怎么办?🩹
拦截链中任何函数抛出异常都不会让服务崩掉——Tea 在请求入口处统一捕获,并交给错误处理器(src/tea.cj、src/tea.cj):
try { next(ctx) } catch (e: Exception) { // 有错误没被处理 → 调用 errorHandler if (!ctx.app.handlerError(ctx, e)) { throw e // 处理器返回 false 才继续抛 } }- 默认
defaultErrorHandler会读取Error中的状态码,回写5xx响应体并返回true(表示已兜底)。 - 你可通过
TeaConfig.errorHandler自定义,比如统一返回 JSON 错误结构、打日志、埋点。
🧩 配合中间件,你可以在最外层写一个"异常日志中间件",再交给
errorHandler统一格式化响应,形成"记录 → 兜底"的完整闭环。
八、快速上手清单(5 步打造拦截链)✅
- 构造应用:
let app = Tea.default()(src/tea.cj)。 - 挂全局中间件:
app.use { c => ... ; c.next() }做统一日志。 - 按前缀加固:
app.use("/admin") { ... }做鉴权拦截。 - 组内共享:用
c.set / c.get在拦截链中传递用户态。 - 接入 CORS:
app.use(cors(CorsConfig(...)))一键跨域。
📖 更多 API 细节(路由参数、配置项、常量)可查阅 README.md;完整示例见 src/test/tea_test.cj 与 src/test/cors_test.cj;架构设计说明见 doc/design.md。
写在最后
Tea 的中间件机制,把"洋葱模型"做得极其克制:一个next()贯穿两级推进,一个set/get完成数据流转,一个use覆盖全局 / 前缀 / 分组三种作用域。理解这三点后,你就能为仓颉 Web 框架搭建出既灵活又安全、层层可控的请求拦截链。🚀
【免费下载链接】tea仓颉语言轻量级的、函数式的、高效的HTTP Web后端框架项目地址: https://gitcode.com/Cangjie-SIG/tea
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考