Skip to content

内容协商

内容协商(Content Negotiation)是 HTTP 的核心机制——客户端通过 Accept 请求头声明它能处理的媒体类型,服务端选择最合适的编码格式返回。ACE 提供了一套轻量的编解码注册表,零反射、编译期静态分派。

响应编码器

默认行为

框架内置 application/json 编码器,所有 @Get/@Post 等路由的返回值默认序列化为 JSON。

cangjie
@Get["/users/{id}"]
func getUser(@PathParam id: String): User {
    return userService.findById(id)
    // 默认返回 Content-Type: application/json
}

注册自定义编码器

使用 registerBodyEncoder 向全局注册表添加新的媒体类型编码器:

cangjie
import ace_framework_runtime.*

// 注册 CSV 编码器
registerBodyEncoder("text/csv") { value: Any ->
    match (value) {
        case rows: Array<Array<String>> =>
            rows.map { row -> row.joinToString(",") }.joinToString("\n")
        case _ => value.toString()
    }
}
cangjie
// 注册 MessagePack 编码器(需自行引入 msgpack 库)
registerBodyEncoder("application/msgpack") { value: Any ->
    MsgPack.encode(value)
}

注册完成后,当客户端请求头包含对应类型时自动生效:

bash
curl -H "Accept: text/csv" http://localhost:8080/api/export/users

手动调用编码器

在中间件或特殊场景中可直接调用 encodeBody

cangjie
import ace_framework_runtime.*

let mw: Middleware = { ctx, next ->
    await next()
    let accept = ctx.header("Accept") ?? "application/json"
    let encoded = encodeBody(accept, ctx.state["result"])
    ctx.body(encoded)
}

API 参考

函数签名说明
registerBodyEncoder(contentType: String, encoder: (Any) -> String) -> Unit注册响应编码器
encodeBody(acceptHeader: String, value: Any) -> String按 Accept 头选择编码器并序列化

Accept 头匹配规则

encodeBodyq 权重从高到低遍历 Accept 头的媒体类型列表,找到第一个已注册的编码器即返回。若全部未命中,退回 application/json


请求参数类型转换器

路由参数(@PathParam/@Query)原始值均为字符串,框架内置 Int64Float64Bool 等基本类型的转换器。复杂类型需手动注册。

注册自定义转换器

cangjie
import ace_framework_runtime.*

// 注册 UUID 类型转换器
registerConverter("UUID") { raw: String ->
    UUID.fromString(raw)  // 格式不合法时抛出 BadRequestException
}

// 注册枚举转换器
registerConverter("UserStatus") { raw: String ->
    match (raw.toUpperCase()) {
        case "ACTIVE"   => UserStatus.Active
        case "INACTIVE" => UserStatus.Inactive
        case _          => throw BadRequestException("无效的用户状态: ${raw}")
    }
}

注册后,@PathParam@Query 绑定时自动调用:

cangjie
@Get["/users"]
func listUsers(@Query["status"] status: UserStatus): Array<User> {
    return userService.findByStatus(status)
}

手动调用转换器

cangjie
let raw = ctx.query("limit") ?? "20"
let limit = convertParam("Int64", raw) as Int64

API 参考

函数签名说明
registerConverter(typeName: String, converter: (String) -> Any) -> Unit注册类型转换器
convertParam(typeName: String, raw: String) -> Any将字符串转换为目标类型

类型名称约定

typeName 需与仓颉类型名称精确一致(大小写敏感)。建议在应用启动时集中注册,避免在请求路径中调用 registerConverter(非线程安全)。

转换失败处理

转换器内部应在格式非法时抛出 BadRequestException,框架会将其映射为 HTTP 400 响应,并在 body 中附上错误信息。切勿静默吞掉异常后返回零值,这会导致业务逻辑静默错误。

基于 Apache-2.0 许可证发布