2601_94997069头像

Http-errors 跨端三方库仓颉适配全流程

本文记录 http-errors 2.0.1 适配仓颉(Cangjie)的完整过程。适配目标是得到一个可以被仓颉项目直接依赖的跨端三方库,并在本地终端中通过真实的仓颉可执行程序验证 HTTP 错误构造、状态码分发、默认消息、自定义消息、错误暴露策略和错误识别能力。

项目地址: AtomGit/nutpi_cj/http-errors

上游仓库: jshttp/http-errors

开发工具: 华为云码道

一、背景

1.1 为什么适配 http-errors

http-errors 是 Node.js Web 服务中常用的 HTTP 错误构造库,用于把状态码、错误消息和客户端暴露策略统一封装成可检查的错误对象。JavaScript 版本通过动态对象、原型继承和状态码构造器提供 createError(404)、createError.NotFound 等调用方式,而仓颉强调静态类型、显式异常层次和跨平台可移植性。直接把 JavaScript 源码逐行改写成仓颉,难点不在语法转换,而在于把动态错误对象和状态码构造器重新组织成稳定的异常类型。

本项目保留 http-errors 的核心调用方向:使用 createError 按状态码生成错误,使用 NotFoundError、InternalServerError 等具名异常表达常见状态,使用 status、statusCode、message、expose 和 errorName 读取结果,并通过 isHttpError 判断一个值是否属于该异常层次。

1.2 跨端三方库的适配边界

当前项目提供的是仓颉跨端库,不绑定某一个 UI 框架、Web 框架或操作系统。适配边界可以概括为:

边界仓颉实现
包管理cjpm.toml、cjpm build 和静态库输出
基础错误HttpError <: Exception,保存状态码、消息和暴露标志
具名构造器29 个 4xx 类和 12 个 5xx 类
工厂函数createError(status, message:),支持标准、未知和非法状态
状态消息statusMessage(status) 返回标准原因短语和范围回退
错误识别isHttpError(value) 检查仓颉异常层次
交付形态仓颉静态库 libhttp_errors_cangjie.a 和接口文件 http_errors_cangjie.cjo
验证方式test/main.cj 直接链接测试和 example/example.cj 完整终端结果 demo

这里没有 HAP 构建、ArkTS 桥接、网络传输或系统权限。http-errors 本身只负责错误对象,HTTP 客户端、Web 框架和响应发送仍由调用方负责。只要目标平台提供兼容版本的仓颉 SDK,就可以按同一套源码和 cjpm 配置构建并消费这个库。

1.3 适配后的能力

当前仓颉实现对齐 http-errors 的错误构造和读取流程,公开提供以下能力:

  • 基础异常:HttpError,包含 status、statusCode、message、expose 和 errorName;
  • 4xx 具名异常:BadRequestError、UnauthorizedError、NotFoundError、TooManyRequestsError 等;
  • 5xx 具名异常:InternalServerError、BadGatewayError、GatewayTimeoutError 等;
  • 工厂创建:createError 根据状态码自动选择具名异常,未知 4xx/5xx 保留状态码并使用范围默认消息;
  • 原因短语:statusMessage 返回标准 HTTP 状态消息;
  • 类型识别:isHttpError 判断一个值是否属于 HttpError 层次;
  • 结果输出:示例逐项打印具名构造器和工厂函数实际生成的名称、状态码、消息和 expose 值。

二、实现路线图

第 1 阶段:盘点 API       ── 对照 http-errors 的工厂、具名构造器和错误属性
第 2 阶段:建立 cjpm 工程 ── 固定包名、版本、源码目录和静态库输出
第 3 阶段:类型映射       ── 用 Exception、class 和显式属性替换动态 Error 对象
第 4 阶段:实现错误模型   ── 完成状态消息、4xx/5xx 构造器和 createError 分发
第 5 阶段:测试与核对     ── 编译测试入口,执行状态码、消息和类型识别断言
第 6 阶段:终端演示       ── 输出所有库调用产生的真实结果并保留效果图

每个阶段都有可检查的产物:工程阶段得到 cjpm.toml,实现阶段得到 .cjpm-target/http_errors_cangjie,验证阶段得到 ALL HTTP-ERRORS CANGJIE TESTS PASS,最终示例则输出 41 个具名构造器和所有工厂路径的实际结果。

三、逐步实现过程

第 1 阶段:盘点原库 API 和仓颉工程边界

1.1 识别动态 API

http-errors 的 JavaScript API 接受数字状态码、字符串消息、已有 Error 和动态属性对象,并根据状态码动态选择构造器。仓颉没有与 JavaScript 原型对象完全等价的动态错误容器,因此本适配先筛选可以稳定表达的能力:状态码使用 Int64,消息使用 String,错误类型使用 Exception 子类,公开属性使用明确的只读成员。

原库可以把任意已有 Error 重新装饰为 HTTP 错误;仓颉适配不修改任意异常的运行时原型,而是要求调用方使用 HttpError 或其具名子类。这样错误类型在编译期可见,调用方也能直接读取状态码和暴露策略。

1.2 固定仓颉项目结构

以下目录树按照当前 http-errors 项目的仓颉适配文件整理,只展示仓颉相关的文件;原始 JavaScript 源码、脚本和测试文件保持原位置:

http-errors/
├── cjpm.toml
├── cjpm.lock
├── README.Cangjie.md
├── README.Cangjie_CN.md
├── src/
│   └── http_errors.cj
├── test/
│   └── main.cj
└── example/
    ├── cjpm.toml
    ├── cjpm.lock
    └── example.cj

源码集中在 src/http_errors.cj,按基础异常、状态消息、具名异常和工厂分发顺序组织。测试入口位于 test/main.cj,示例工程位于 example/。示例使用 http_errors_cangjie = { path = ".." } 引用本地包,且 src-dir = "." 时将 target-dir 放在 example/ 外部的 ../.cjpm-target/http_errors_example,避免构建输出被再次扫描为源码。

第 2 阶段:创建 cjpm 工程

根目录 cjpm.toml 使用静态库输出类型,并将构建目录放在源码目录之外:

[package]
  cjc-version = "1.1.3"
  name = "http_errors_cangjie"
  version = "2.0.1"
  output-type = "static"
  src-dir = "src"
  target-dir = ".cjpm-target/http_errors_cangjie"

示例工程使用本地路径依赖,并把构建目录放在 example/ 外部:

[package]
  cjc-version = "1.1.3"
  name = "http_errors_example"
  version = "1.0.0"
  output-type = "executable"
  src-dir = "."
  target-dir = "../.cjpm-target/http_errors_example"

[dependencies]
  http_errors_cangjie = { path = ".." }

包版本沿用上游 http-errors 2.0.1,包名使用合法的仓颉标识符 http_errors_cangjie。构建成功后,最重要的两个库产物是:

.cjpm-target/http_errors_cangjie/release/http_errors_cangjie/libhttp_errors_cangjie.a
.cjpm-target/http_errors_cangjie/release/http_errors_cangjie/http_errors_cangjie.cjo

其中 .a 用于链接,.cjo 提供编译器需要的接口信息。根包的 target-dir 不在 src/ 扫描范围内,示例包的 target-dir 也不在 src-dir = "." 的目录范围内,因此运行 cjpm run 不会出现把构建输出目录当作源码扫描目录的警告。验证完成后清理 .cjpm-target,不提交构建产物。

第 3 阶段:把 JavaScript 动态错误映射为仓颉类型

3.1 用 HttpError 表示错误属性

JavaScript 错误对象的状态、消息和暴露标志在仓颉中变成显式的异常成员:

let error = HttpError(499, "custom base error",
    errorName: "HttpError", expose: true)
println(error.status)
println(error.statusCode)
println(error.message)
println(error.expose)

status 和 statusCode 始终保存同一个状态码。message 复用仓颉 Exception 的消息属性,errorName 保存与 JavaScript 构造器名称对应的可读名称,expose 表示调用方是否可以把消息返回给客户端。

3.2 用具名异常替换动态构造器

JavaScript 版本会根据状态码动态选择 NotFound、InternalServerError 等构造器。仓颉适配把这些构造器实现为公开的 HttpError 子类:

let notFound = NotFoundError()
let timeout = GatewayTimeoutError()

println("${notFound.status}: ${notFound.message}")
println("${timeout.status}: ${timeout.message}")

4xx 子类默认 expose == true,5xx 子类默认 expose == false。每个具名类都有默认原因短语,也接受 message: 命名参数覆盖默认消息。

3.3 用 createError 保留工厂调用方向

调用方可以使用状态码工厂,不需要手动选择异常类:

let error = createError(404, message: "This video does not exist")
println("${error.errorName}: ${error.status} ${error.message}")

标准状态码返回对应的具名异常。未知 4xx 和 5xx 状态会保留数字状态码,并分别使用 Bad Request 和 Internal Server Error 作为回退消息;不属于 4xx/5xx 的状态会归一化为 500。

第 4 阶段:实现源码

4.1 文件 src/http_errors.cj

源码按错误生命周期组织:先由 statusMessage 提供状态原因短语,再由 4xx 和 5xx 具名类固定状态、名称和 expose,最后由 createError 按状态码分发到对应构造器。未知状态进入范围回退,isHttpError 使用仓颉运行时类型判断检查异常层次。

这种组织方式没有引入外部依赖,所有状态码和原因短语都在静态库中完成。调用方不需要初始化全局注册表,也不需要通过字符串查找异常构造器。

4.2 状态码与默认消息

statusMessage 覆盖上游 README 列出的 400 至 511 标准错误状态,包括 Im a Teapot、URI Too Long、Unavailable For Legal Reasons 和 Network Authentication Required。对未知的 4xx 和 5xx,函数分别返回范围默认消息;对 2xx、3xx 等非错误状态返回 HTTP Error。

4.3 错误识别和能力边界

isHttpError 只识别 HttpError 及其子类,不会把普通字符串、数字或其他仓颉异常误判为 HTTP 错误:

let error = createError(404)
println(isHttpError(error))
println(isHttpError("not an HTTP error"))

JavaScript 版本对外部 Error 的 duck typing 和任意属性扩展依赖动态对象模型,仓颉版本不伪造这部分行为,而是通过类型安全的异常层次明确适配边界。

第 5 阶段:建立自动化测试

test/main.cj 是一个直接链接仓颉静态库的可执行测试入口,覆盖以下行为:

测试组示例
默认构造createError(404)、createError(500)
自定义消息createError(500, message:)
状态属性status、statusCode 同步
暴露策略4xx 为 true,5xx 为 false
未知状态499 的状态保留和 4xx 回退消息
类型识别isHttpError 接受 HTTP 异常并拒绝普通值

断言失败会抛出异常并以非零状态退出;全部通过时输出固定标记,便于脚本或 CI 判断:

ALL HTTP-ERRORS CANGJIE TESTS PASS

完整的具名构造器和工厂输出由 example/example.cj 负责。示例会逐一调用全部 41 个具名异常,并打印库实际生成的名称、状态码、消息和 expose 值,而不是只打印预先写死的演示文本。

第 6 阶段:核对示例工程

示例工程位于 example/,通过本地路径依赖引用根包。example/example.cj 真实创建全部具名异常和工厂错误,输出自定义消息、未知状态、非法状态、statusMessage 和 isHttpError 结果。任一结果与预期不一致都会抛出 IllegalArgumentException,因此终端输出既是功能结果,也是可执行验收。

四、完整验证步骤

以下命令均以仓库根目录 http-errors/ 为当前目录。先确认本机已经安装仓颉 SDK:

echo "$CANGJIE_HOME"
cjc --version
cjpm --version

然后检查并构建静态库:

cjpm check
cjpm build

编译并运行测试入口:

cjc test/main.cj \
  --import-path .cjpm-target/http_errors_cangjie/release/http_errors_cangjie \
  --library-path .cjpm-target/http_errors_cangjie/release/http_errors_cangjie \
  --library http_errors_cangjie \
  --static-libs --static-std \
  -o /tmp/http-errors-cangjie-test
DYLD_LIBRARY_PATH="$CANGJIE_HOME/runtime/lib/darwin_aarch64_cjnative" \
  /tmp/http-errors-cangjie-test

测试程序会输出:

ALL HTTP-ERRORS CANGJIE TESTS PASS

再构建并运行独立的终端示例工程:

cd example
cjpm check
cjpm build
cjpm run

示例不会访问网络,也不依赖预先写死的结果。它直接调用仓颉库并输出类似结果:

NotFoundError() => name=NotFoundError, status=404, statusCode=404, message="Not Found", expose=true
createError(500) => name=InternalServerError, status=500, statusCode=500, message="Internal Server Error", expose=false
createError(404, custom message) => name=NotFoundError, status=404, statusCode=404, message="The requested video does not exist", expose=true
createError(499) => name=HttpError, status=499, statusCode=499, message="Bad Request", expose=true
createError(200) => name=HttpError, status=500, statusCode=500, message="Internal Server Error", expose=false
statusMessage(200) => HTTP Error
isHttpError(createError(404)) => true
isHttpError(non-error string) => false
http-errors Cangjie demo passed
Generated results for 41 named constructors and all factory paths

示例使用 target-dir = "../.cjpm-target/http_errors_example",因此运行 cjpm run 时不会出现把 target 目录当作源码扫描目录的警告。验证结束后执行 find .cjpm-target -type f -delete 并删除空目录,确认 .cjpm-target、target 等构建目录未加入 Git。

五、运行效果

下面的效果图来自 example/example.cj 的实际终端运行,包含具名构造器、createError 工厂、状态消息、类型识别和最终完成标记。

在这里插入图片描述

这张图可以作为文章或项目说明中的验收证据:终端同时展示了 4xx 和 5xx 的具名错误结果、状态码工厂结果、自定义消息、未知状态回退和 isHttpError 判断。输出由仓颉示例程序调用静态库生成,不是模拟页面或手工拼接的文本。

六、关键决策说明

6.1 为什么使用显式异常层次

仓颉适配的价值在于让调用方在编译期知道错误的数据形状。若所有错误都退化成 HashMap<String, Any>,虽然表面上接近 JavaScript,却会失去异常继承、状态属性和消息类型检查。因此项目使用 HttpError 作为公开基类,使用具名子类表达标准状态码,使用 Int64、String 和 Bool 固定属性类型。

6.2 为什么保留 http-errors 的方法和字段方向

保留 createError、HttpError、NotFoundError、InternalServerError、status、statusCode、message 和 expose 等名称,可以降低迁移成本。熟悉 Node.js http-errors 的开发者只需要适应仓颉的参数标签和异常捕获,不必重新记忆一套完全不同的 HTTP 错误 API。对于无法直接表达的动态属性装饰,适配通过明确文档边界保持行为可解释。

6.3 为什么把测试和示例分开

测试程序负责断言,适合自动化执行;示例工程负责展示,适合在终端中观察每个库调用生成的实际结果和截图。两者都使用同一个 http_errors_cangjie 仓颉静态库,因此效果图不是模拟输出,而是库的真实运行结果。

七、兼容边界和遗留问题

JavaScript http-errors 与仓颉的运行时模型不同,以下限制需要在接入时提前了解:

  1. 当前版本通过 HttpError 异常层次表达 HTTP 错误,不修改任意外部 Exception 的原型,也不把普通对象动态装饰成 HTTP 错误。
  2. createError 支持状态码和消息参数;JavaScript 版本中用于附加任意属性的动态 properties 对象没有直接映射,调用方应在自己的业务结构体中保存额外上下文。
  3. 状态码使用 Int64,标准状态提供具名异常;未知 4xx/5xx 会保留数值并使用范围回退消息,其他状态会归一化为 500。
  4. statusMessage 覆盖当前实现中的标准错误状态;它不负责解析自定义 HTTP 状态文本,也不连接网络或发送响应。
  5. isHttpError 使用仓颉类型判断,只保证识别本包的 HttpError 及其子类,不实现 JavaScript duck typing 的外部 Error 检查。
  6. message 和 errorName 使用字符串,expose 使用布尔值;调用方仍需根据业务安全策略决定是否把错误消息发送给客户端。

这些不是构建错误。只要 cjpm build 成功、测试程序输出通过标记、示例输出所有库调用结果并显示 http-errors Cangjie demo passed,就说明当前版本已经完成本地编译和行为验收。

八、接入方式

在另一个仓颉项目中,可以将本仓库作为本地依赖或发布包使用。最直接的本地验证方式是先在本仓库根目录生成 .cjpm-target/http_errors_cangjie,再把相同的 --import-path、--library-path 和 --library 参数传给调用方的 cjc 命令。调用代码只需要导入:

import http_errors_cangjie.*

main(): Int64 {
    let error = createError(404, message: "This video does not exist")
    println("${error.errorName}: ${error.status} ${error.message}")
    return 0
}

如果使用 cjpm 管理依赖,则将仓颉包加入项目依赖配置;示例工程采用 http_errors_cangjie = { path = ".." },发布到远程仓库后也可以替换为带版本的 AtomGit 依赖。业务工程负责捕获 HttpError、读取 status 和 message,并根据 expose 决定响应内容。跨端项目可以复用相同的错误构造和识别 API,具体的 HTTP 响应发送、日志和 UI 展示由宿主平台负责。

九、总结

本次适配完成了 http-errors 到仓颉静态异常模型的迁移:源码使用 HttpError 保存 HTTP 错误属性,使用 41 个具名异常覆盖标准 4xx/5xx 状态,使用 createError 统一分发状态码,使用 statusMessage 提供原因短语,使用 isHttpError 提供类型识别。

验证链路包括 cjpm check、cjpm build、测试入口独立编译运行和本地依赖示例工程运行。最终效果图来自真实的仓颉可执行程序,能够直观看到三方库在具名构造器、状态码工厂、默认消息、自定义消息、错误暴露策略和非法状态回退场景中的输出。对于无法一比一映射 JavaScript 动态对象装饰和任意 properties 的部分,项目通过静态签名和文档边界保持行为可解释,方便后续继续扩展。

适配过程 FAQ

以下问题来自仓颉工程创建、cjpm 依赖、与 HarmonyOS 设备联调的实际流程。

Q1:cjpm 依赖解析失败或拉取超时?

  • 检查 cjpm.toml 中的依赖坐标与版本号;
  • 配置可用的仓颉包仓库镜像或代理;
  • 删除 target、cjpm.lock 后重新执行 cjpm update / cjpm build。

Q2:编译报找不到仓颉 SDK 或 cjc 版本不匹配?

  • 设置 CANGJIE_HOME 指向仓颉 SDK 根目录;
  • 确认 cjc --version、cjpm --version 与工程要求的版本一致;
  • 清理构建缓存后重新生成。

Q3:与 HarmonyOS 设备联调时安装失败?

  • 用 hdc list targets 确认设备在线;
  • 确认 HAP 已签名,bundleName 与签名 profile 一致;
  • 设备需加入调试 profile 的 deviceIds。

Q4:动态库或 FFI 符号找不到?

  • 检查导出符号名称、ABI 与打包路径;
  • 确认 .so 已随 HAP 一起打包到 libs/<abi>/;
  • 用 llvm-readelf -d 检查依赖库是否齐全。

Q5:测试用例在设备上失败,但本机通过?

  • 对比设备与主机的架构、SDK 版本和环境变量;
  • 用 hdc hilog 抓取运行日志;
  • 优先复现最小用例,确认是否为平台差异或时序问题。

十、参考资料

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2601_94997069/article/details/169057126

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--