Huawei Cloud OBS native protocol client for MoonBit
Dependencies
moon add mldong/moon-obs/// `sign_v4` 会 raise(时间戳形状、空密钥这两类本地就能判死的输入),文档块里把它兜住:
/// 这一块演示的是"本地算得出"的那一半,兜住不等于演示失败路径。
fn sign(
access_key : String,
secret_key : String,
region : String,
timestamp : String,
req : @obsauth.RequestToSign,
) -> @obsauth.Signature {
try {
@obsauth.sign_v4(access_key, secret_key, region, timestamp, req)
} catch {
err => abort("最小用法只演示本地可算的一档,签名件不该在这里红:" + err.text())
}
}
test "寻址、编码与签名串全在本地算得出,抄这一块不打网络" {
// 基址按区域派生;虚主机那一档由桶名打头,Config::host_of 是唯一出口
assert_eq(
@obsconfig.native_endpoint_host("cn-north-4"),
"obs.cn-north-4.myhuaweicloud.com",
)
// 对象键编码:斜杠保留、空格 %20、加号 %2B——签名侧与请求行用同一条规则
assert_eq(
@obsauth.uri_encode("articles/03 state+machine.png", keep_slash=true),
"articles/03%20state%2Bmachine.png",
)
// 查询串:键、值各自编码,再按编码后的串整体排序,空值写 `acl=`(等号不能省)
assert_eq(
@obsauth.canonical_query_string([("prefix", "a b"), ("acl", "")]),
"acl=&prefix=a%20b",
)
// 头值归一(首尾去掉、中间连续空白压成一个空格)+头名字典序分号拼
assert_eq(@obsauth.normalize_header_value(" padded value "), "padded value")
assert_eq(@obsauth.signed_header_list(["x-amz-date", "host"]), "host;x-amz-date")
// scope 的服务名写死 `s3`,算法串与 AWS 逐字相同——这两格错了不会报错,只会一直 403
assert_eq(
@obsauth.scope_of("20261016", "cn-north-4"),
"20261016/cn-north-4/s3/aws4_request",
)
assert_eq(@obsauth.algorithm, "AWS4-HMAC-SHA256")
assert_eq(@obsauth.unsigned_payload, "UNSIGNED-PAYLOAD")
// 头名前缀:原生端点上 V4 这一族只认 x-amz-*,所以默认档就是 Amz
assert_eq(@obsauth.HeaderPrefix::Amz.date_header(), "x-amz-date")
// 用 AWS 官方 SigV4 测试凭据签一发(公开测试值,不是任何真密钥)。
// Authorization 的拼法是"逗号后不带空格"——AWS SDK 发的是带空格那一形,两家服务端都收,
// 但这里钉的是 OBS 官方 SDK 那串逐字形状。
let signed = sign(
"AKIDEXAMPLE",
"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
"us-east-1",
"20261016T010203Z",
@obsauth.RequestToSign::new(
"GET",
"/examplebucket/plain.txt",
headers=[
("host", "examplebucket.obs.us-east-1.myhuaweicloud.com"),
("x-amz-content-sha256", @obsauth.unsigned_payload),
("x-amz-date", "20261016T010203Z"),
],
payload_hash=@obsauth.unsigned_payload,
),
)
assert_eq(signed.signed_headers, "host;x-amz-content-sha256;x-amz-date")
let want_head = "AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20261016/us-east-1/s3/aws4_request,SignedHeaders=host;x-amz-content-sha256;x-amz-date,Signature="
assert_true(signed.authorization.has_prefix(want_head))
// 密钥本体不许出现在签出来的任何一段里
assert_eq(signed.string_to_sign.find("wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"), None)
}| 这一发 | 重发资格 | 为什么 |
|---|---|---|
| GET/HEAD/DELETE | 有 | 落点由 (桶, 键, 查询串) 定死,重放的是同一发已签名字节 |
| PUT(写对象/写分片/复制/?acl) | 有 | 同上,键幂等:服务端重复执行与执行一次同果 |
| POST ?uploads(建分片任务) | 没有 | 重发会多出一个 uploadId,前一个没人 abort 就永远漏在桶里 |
| POST ?uploadId=(合并分片) | 没有 | 重发可能双份合并,或与第一发竞态 |
| 认不出的 POST | 没有 | 默认按非幂等待,不替服务端猜"大概没事" |
| 带 Stream 体的请求 | 没有 | 流式体倒不回来,重发可能把体发歪 |
| 拿到任何状态码(4xx/5xx 都算) | 没有 | 协议层的红必须原样交给调用方,不许被"再发一次"藏起来 |
[moon-obs] resending exactly once: GET /<bucket>/<key> -- response headers had not arrived (...); the resend is safe because ...
[moon-obs] NOT resending: POST /<bucket>/<key> -- POST ?uploads mints a new uploadId, ...平台能力读数(原生面收不收 list-type=2、分片最小段尺寸、ACL 取值面与 grant 拼法、 预签名免凭据可否取回等)都由仓内的真桶实测档裁决,判据与打印口径见 obs/live_test.mbt 与 obs/retry_live_test.mbt;本文件不抄那些数字。
| 能力 | 本 SDK 的件 | 状态 |
|---|---|---|
| 对象读写/删除/元数据 | put_object get_object delete_object stat_object | 已实现 |
| 列举/分片五步/预签名/对象 ACL | list_objects、分片五件、presigned_*、put/get_object_acl | 已实现 |
| 批量删除/对象标签/条件请求 | delete_objects、标签三件、if_match~ 等 | 已实现 |
| 存储类/自动 MD5 | storage_class~、compute_md5~ | 已实现 |
| 追加写/生命周期 | append_object、get/put/delete_bucket_lifecycle | 已实现(生命周期读) |
| 归档解冻 | restore_object | 部分实现(仅离线件) |
| 桶基础 | create_bucket delete_bucket bucket_exists | 部分实现(建/删未跑真桶) |
| 桶配置读 | ACL/版本/CORS/标签/日志/网站/加密/生命周期/策略 | 已实现 |
| 桶配置写 | 上述各族的 put_* / delete_* | 部分实现(只有离线件) |
| 重命名/截断/改写 | rename_object truncate_object modify_object | 部分实现(POSIX 桶专属,普通桶 405) |
| STS/自动分片/进度回调 | 无对应件 | 待开发 |
Install
Download zipHuawei Cloud OBS native protocol client for MoonBit
Dependencies