中间件
ACE Framework 的中间件采用洋葱模型,每个中间件包裹下一层,请求由外向内穿透,响应由内向外回程。这与 Koa.js 的设计完全一致,却以仓颉的静态类型和编译期宏实现。
洋葱模型
请求进入
│
▼
┌─────────────────────────────┐ order=1(最外层)
│ Logger │
│ ┌───────────────────────┐ │
│ │ CORS │ │ order=10
│ │ ┌─────────────────┐ │ │
│ │ │ JWT Auth │ │ │ order=20
│ │ │ ┌───────────┐ │ │ │
│ │ │ │ Router │ │ │ │ order=99(最内层)
│ │ │ └───────────┘ │ │ │
│ │ │ next() 返回 ↑ │ │ │
│ │ └─────────────────┘ │ │
│ └───────────────────────┘ │
└─────────────────────────────┘
│
▼
响应回程(逆序执行 next() 之后的代码)每个中间件调用 next() 将控制权交给内层,next() 返回后继续执行回程逻辑(如追加响应头、记录耗时)。
@Middleware 宏
@Middleware[order] 装饰一个无参数、返回 Middleware 的函数,容器自动收集并按 order 从小到大(最外层)排列:
package com.example.middleware
import ace.framework.*
import ace.web.*
@Middleware[1]
func requestLogger(): Middleware {
return { ctx, next in
let start = currentTimeMillis()
await next()
let elapsed = currentTimeMillis() - start
println("[${ctx.method}] ${ctx.path} ${ctx.status} ${elapsed}ms")
}
}@Middleware 参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
order | Int64 | 执行顺序,数值越小越靠外层;默认值为 100 |
order 约定
建议内置基础设施类中间件使用 1–20,业务鉴权类使用 20–50,业务逻辑类使用 50–90,路由层保持 99 以上。
内置中间件
ACE 提供一组开箱即用的中间件函数,通过 App().use(...) 手动挂载或配合 @Middleware 宏自动注册。
bodyParser()
解析 application/x-www-form-urlencoded 和 application/json 请求体,结果写入 ctx.body:
app.use(bodyParser())gzip()
对响应体自动 gzip 压缩(当客户端 Accept-Encoding 含 gzip 时生效):
app.use(gzip())timeoutHandler(ms)
超过指定毫秒未完成则返回 503 Service Unavailable:
app.use(timeoutHandler(5000)) // 5 秒超时jwtAuth(secret)
验证 Authorization: Bearer <token>,校验通过后将 payload 写入 ctx.state["jwt"]:
app.use(jwtAuth("my-secret-key"))
// 在 Handler 中读取
@Get["/profile"]
func profile(@Inject ctx: Context): String {
let payload = ctx.state["jwt"] as JwtPayload
return payload.sub
}serveStatic(dir, prefix)
挂载本地静态文件目录:
app.use(serveStatic("./public", "/static"))
// 访问 /static/logo.png → 读取 ./public/logo.pngcors()
添加跨域响应头,支持预检请求(OPTIONS):
app.use(cors()) // 允许所有来源
app.use(cors(origins: ["https://example.com"])) // 白名单自定义中间件示例
以下示例展示一个完整的请求日志中间件,记录方法、路径、状态码和耗时:
package com.example.middleware
import ace.framework.*
import ace.web.*
import std.time.*
@Middleware[5]
func accessLog(): Middleware {
return { ctx, next in
let start = DateTime.now()
var err: ?Exception = None
try {
await next()
} catch (e: Exception) {
err = e
}
let ms = (DateTime.now() - start).toMilliseconds()
let level = if (ctx.status >= 500) { "ERROR" } else { "INFO" }
println("[${level}] ${ctx.method} ${ctx.path} ${ctx.status} ${ms}ms")
if (let e <- err) {
throw e // 重新抛出,交由 @Catch 处理
}
}
}手动注册(无宏时)
不使用 @Middleware 宏时,可直接调用 App.use():
import ace.web.*
import ace.http.*
func main() {
let app = App()
app.use(timeoutHandler(3000))
app.use(cors())
app.use(bodyParser())
app.use(router.routes())
listen(app, "0.0.0.0", 8080)
}手动注册时中间件的执行顺序与 use() 调用顺序一致(先 use 的在最外层)。
中间件 vs @Catch 过滤器
中间件和 @Catch 过滤器都能处理异常,但职责不同:
| 特性 | 中间件 | @Catch 过滤器 |
|---|---|---|
| 注册位置 | @Middleware[order] 或 app.use() | @Catch[ExceptionType] 标注在类上 |
| 作用范围 | 全局或路由组 | 按异常类型精确匹配 |
| 执行时机 | 请求全程(前后都可介入) | 仅当指定异常被抛出时 |
| 适合场景 | 日志、鉴权、超时、CORS | 业务异常映射为 HTTP 响应 |
// @Catch 示例:将 NotFoundException 映射为 404
@Catch[NotFoundException]
class NotFoundFilter <: ExceptionFilter<NotFoundException> {
func catch(e: NotFoundException, ctx: Context): Unit {
ctx.status = 404
ctx.json({ "error": e.message })
}
}中间件中的异常处理
中间件捕获异常后若不重新抛出,@Catch 过滤器将不会触发。建议日志中间件仅记录后重新 throw,让过滤器负责最终的响应格式。