自定义中间件与异常过滤器
ACE Framework 提供两种运行时扩展钩子:
- 洋葱中间件(
@Middleware):包裹请求/响应生命周期,可读写Context、调用next() - 异常过滤器(
@Catch):捕获特定类型的异常,自定义错误响应
两者概念不同,请勿混淆——中间件处理正常流程,过滤器只在抛出异常时介入。
@Middleware 宏
@Middleware[order] 把返回 Middleware 的无参函数自注册进洋葱中间件链。
cangjie
import ace_web.*
import ace_framework_macros.*
@Middleware[150]
public func requestSignatureVerifier(): Middleware {
return {ctx, next =>
let sig = ctx.header("X-Signature") ?? ""
if (!verifySignature(sig, ctx.rawBody() ?? "")) {
throw HttpError(401, "invalid request signature")
}
next()
}
}宏展开等价于:
cangjie
public func requestSignatureVerifier(): Middleware { ... }
let __ace_mw_requestSignatureVerifier = registerMiddleware(150, requestSignatureVerifier)顶层 let 在程序启动时自动执行,无需手动调用。
Order 参考值
| 范围 | 建议用途 |
|---|---|
| 0–50 | 框架内置(不建议占用) |
| 100–199 | 安全/认证(JWT、CSRF、限流) |
| 200–499 | 业务中间件(追踪、审计日志) |
| 500+ | 兜底处理 |
与 ComponentContext.useMiddleware 的区别
@Middleware[order] | ComponentContext.useMiddleware | |
|---|---|---|
| 注册时机 | 编译期(顶层 let) | 组件 setup 期(运行期) |
| 适合场景 | 单文件中间件、简单插件 | 依赖配置参数的中间件(如动态限流阈值) |
| 需要 Component | 否 | 是 |
编写中间件
中间件签名:(ctx: Context, next: () -> Unit) -> Unit。
前置/后置处理
cangjie
@Middleware[200]
public func auditLog(): Middleware {
return {ctx, next =>
let start = System.currentTimeMillis()
next() // 调用下游
let elapsed = System.currentTimeMillis() - start
println("${ctx.method()} ${ctx.path()} ${ctx.status()} ${elapsed}ms")
}
}短路(不调 next)
cangjie
@Middleware[120]
public func maintenanceMode(): Middleware {
return {ctx, next =>
if (isMaintenanceMode()) {
ctx.status(503)
ctx.json("{\"error\":\"service unavailable\"}")
// 不调 next(),请求在此终止
} else {
next()
}
}
}读写 ctx.state
ctx.state 是 HashMap<String, Any>,用于在中间件间传递数据:
cangjie
@Middleware[110]
public func tenantResolver(): Middleware {
return {ctx, next =>
let tenantId = ctx.header("X-Tenant-Id") ?? "default"
ctx.state["tenantId"] = tenantId
next()
}
}
// 下游中间件或控制器中读取
let tenantId = ctx.state.getOrDefault("tenantId", "default")@Catch 异常过滤器
@Catch 装饰一个签名为 (ctx: Context, e: SomeException) -> Unit 的函数,按异常类型精确拦截:
cangjie
import ace_web.*
import ace_framework_macros.*
@Catch
public func handleNotFound(ctx: Context, e: ResourceNotFoundError): Unit {
ctx.status(404)
ctx.json("{\"error\":\"" + e.message + "\",\"code\":\"NOT_FOUND\"}")
}宏展开后自动注册进 dispatchException 的过滤链。框架调用 App.onError(dispatchException),异常到来时:
抛出异常
↓
依次尝试已注册的过滤器(按注册顺序)
↓ 匹配
过滤器写响应,链条终止
↓ 无匹配
默认 jsonErrorHandler(HttpError → 对应状态码 JSON;其余 → 500)自定义异常类
配合 @Exception 宏简化异常类定义:
cangjie
@Exception public class ResourceNotFoundError {}
@Exception public class ValidationError {}
@Exception public class PermissionDeniedError {}等价于:
cangjie
public class ResourceNotFoundError <: Exception {
public init(message: String) { super(message) }
}多过滤器示例
cangjie
@Catch
public func handleValidation(ctx: Context, e: ValidationError): Unit {
ctx.status(400)
ctx.json("{\"error\":\"" + e.message + "\",\"code\":\"VALIDATION_ERROR\"}")
}
@Catch
public func handlePermission(ctx: Context, e: PermissionDeniedError): Unit {
ctx.status(403)
ctx.json("{\"error\":\"forbidden\",\"code\":\"PERMISSION_DENIED\"}")
}
@Catch
public func handleUnexpected(ctx: Context, e: Exception): Unit {
ctx.status(500)
ctx.json("{\"error\":\"internal server error\"}")
}顺序
过滤器按注册顺序(即编译期顶层 let 执行顺序)依次尝试,首个匹配即止。应将更具体的类型(子类)放在更通用类型(如 Exception)之前。
完整示例:API Key 鉴权中间件
cangjie
package myapp.security
import ace_web.*
import ace_framework_macros.*
// 自定义异常
@Exception public class ApiKeyError {}
// 中间件
@Middleware[100]
public func apiKeyAuth(): Middleware {
return {ctx, next =>
let key = ctx.header("X-Api-Key") ?? ""
if (!ApiKeyStore.isValid(key)) {
throw ApiKeyError("invalid or missing API key")
}
ctx.state["apiKey"] = key
next()
}
}
// 异常过滤器
@Catch
public func handleApiKeyError(ctx: Context, e: ApiKeyError): Unit {
ctx.status(401)
ctx.json("{\"error\":\"" + e.message + "\",\"code\":\"API_KEY_INVALID\"}")
}不需要任何手动注册,@Middleware[100] 和 @Catch 标注的顶层函数在启动时自动生效。