Skip to content

自定义中间件与异常过滤器

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.stateHashMap<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 标注的顶层函数在启动时自动生效。

基于 Apache-2.0 许可证发布