跳转到内容
AsterDrive Developer Docs开发者

内部存储协议(Follower)

这组接口是主节点和 follower 节点之间的内部对象存储协议,不是给浏览器前端或第三方普通客户端用的公开 API。

这页描述的是 follower 侧实际执行对象读写的 /api/v1/internal/storage/*。primary 侧另外提供独立的 binding 控制面 /api/v1/internal/remote-node-control/*,以及让不能被 primary 直连的 follower 主动连回来的 reverse tunnel 传输入口 /api/v1/internal/remote-tunnel/*

以下路径都相对于:

/api/v1/internal/storage

并且只会在 follower 节点注册。

远端节点协议分成三层,别混在一起看:

  • /api/v1/internal/storage/* 只在 follower 注册,是实际对象读写、绑定同步、远程存储目标管理的协议。
  • /api/v1/internal/remote-node-control/* 只在 primary 注册,是 follower 主动拉取 binding desired state 的独立控制面。
  • /api/v1/internal/remote-tunnel/* 只在 primary 注册,是 reverse tunnel 的对象请求传输入口,不承担 binding 状态收敛。

direct 模式下,primary 直接请求 follower 的 /api/v1/internal/storage/*reverse_tunnel 模式下,primary 把同样的内部存储请求登记到 tunnel registry,follower 主动向 primary 轮询或建立 WebSocket 连接取走请求,再在本地调用内部存储处理逻辑并回传响应。

auto 由 primary 按当前 base_url 解析成明确的 resolved_transport:非空 base_url 使用 direct,空 base_url 使用 reverse_tunnel。follower 会为所有 binding 周期性请求独立控制面,包括 disabled 和解析为 direct 的 binding;因此 transport 切换不依赖切换前或切换后的对象数据路径。

binding 状态按 revision 收敛:

  1. primary 持有 nameis_enabledresolved_transport 和单调递增的 desired_revision;只有这些 follower 可观察状态变化时才递增 revision。
  2. follower 通过 GET /api/v1/internal/remote-node-control/binding-state?applied_revision=N 拉取 primary 的 desired state,并以 primary 返回值为权威持久化本地 binding。
  3. follower 刷新运行时 binding registry,再启动、停止或替换 reverse tunnel worker;只有 registry 和 worker topology 都完成后,才把本地 applied_revision 标记为当前 desired_revision
  4. 下一轮 pull 携带新的 applied_revision,作为对 primary 的隐式 ACK。primary 只接受不高于当前 desired revision 的 ACK;即使 follower 本地 revision 更高,primary 当前状态仍会覆盖本地状态并重新收敛。

当前 binding 控制面入口:

方法路径说明
GET/api/v1/internal/remote-node-control/binding-statefollower 拉取 desired state,并通过 applied_revision query 回报已应用 revision

primary 侧 reverse tunnel 当前入口:

方法路径说明
POST/api/v1/internal/remote-tunnel/pollfollower 长轮询待处理请求
POST/api/v1/internal/remote-tunnel/completefollower 回传轮询请求的处理结果
GET/api/v1/internal/remote-tunnel/connectfollower 建立 WebSocket 流式 tunnel

binding 控制面和 reverse tunnel 接口都使用远端节点签名鉴权,不是浏览器或第三方客户端 API。binding 控制面允许 direct、reverse tunnel 和 disabled 节点访问;reverse tunnel 数据面还会校验节点已启用且当前 resolved_transportreverse_tunnel,因此解析为 direct 的节点即使签名有效,也不能访问 pollcompleteconnect

这次 binding 控制面扩展没有改变 tunnel frame wire format:WebSocket frame version 仍为 1。它也没有抬高 internal storage 协议版本,当前仍是 v5、最低兼容 v4;滚动升级通过 optional capability 和 JSON 字段默认值处理。

当前有两种访问方式:

  • 主节点签名请求
    • x-aster-access-key
    • x-aster-timestamp
    • x-aster-nonce
    • x-aster-signature
  • 预签名 query
    • aster_access_key
    • aster_expires
    • aster_signature

常规控制面接口都要求签名头;对象 GET / PUT 会按场景支持预签名 URL。

方法路径说明
GET/capabilities读取 follower 声明的协议能力
GET/capacity读取 follower 当前远端存储目标的容量观测状态
PUT/binding向未声明 binding control pull capability 的 legacy follower 推送绑定信息
GET/targets列出当前绑定可用的远程存储目标
POST/targets创建远程存储目标
PATCH/targets/{target_key}更新远程存储目标
DELETE/targets/{target_key}删除远程存储目标
POST/compose把多个 part 对象拼成目标对象
GET/objects按前缀列举对象 key
GET/objects/{tail}/metadata读取对象元信息
PUT/objects/{tail}上传对象内容
GET/objects/{tail}读取对象内容
HEAD/objects/{tail}探测对象是否存在并返回头信息
DELETE/objects/{tail}删除对象

0.4.0 已移除旧 /ingress-profiles/ingress-profiles/{target_key} 兼容路径;primary 与 follower 必须统一使用 /targets

返回仍然走统一 JSON 包装,典型字段包括:

  • protocol_version
  • min_supported_protocol_version
  • server_version
  • features
  • browser_cors
  • limits
  • supports_list
  • supports_range_read
  • supports_stream_upload
  • supports_capacity

当前协议版本是 v5,最低兼容版本是 v4,所以当前节点声明的本地支持区间是 v4-v5v4 / v5v2 / v3 不再 wire-compatible:内部存储 JSON 包装里的顶层 code 已经从旧数字码改成稳定字符串 ApiErrorCode。跨过这个边界时,先同时升级 primary 和 follower,再绑定 remote 策略。

当前 follower 在 features.binding_state_pull 显式声明支持独立 binding control pull。该 capability 缺省为 false,所以 primary 可以区分滚动升级边界:

  • capability 为 true:primary 不再主动 push binding state,完全由 follower pull 收敛。
  • capability 缺失或为 false:primary 保留 legacy PUT /binding push。
  • 新 follower 对旧 primary 请求 binding-state 得到 404 时,保留本地 legacy push 状态并继续运行。

v5 在能力响应中增加了远程存储目标 driver 能力。Rust 模型字段名是 remote_storage_target,但为了兼容 v4 / v5 节点,wire JSON 仍序列化为 managed_ingress,同时接受 remote_storage_target 作为反序列化 alias。不要根据 wire 字段的旧名字把它重新解释成另一套产品模型。

兼容规则是显式且有边界的:

  • v4 follower 没有声明这组能力时,primary 会按旧协议语义把 Local 和 S3 视为可用 driver。
  • v5 follower 必须显式声明远程存储目标能力;能力缺失或禁用时,不再套用 v4 的隐式 Local / S3 fallback。
  • primary 只展示 follower 声明且当前版本已注册 descriptor 的 driver;未知的未来 driver id 会被保留为协议数据,但不会自动变成可配置项。

主节点在加载远端策略或刷新绑定时会做能力协商:

  • protocol_version / min_supported_protocol_version 必须和本地支持区间有交集,当前本地区间是 v4-v5
  • 基础远端策略要求 object_getobject_headobject_putobject_deletemetadatarange_getaccept_ranges_headerlistcompose
  • 如果远端策略启用浏览器预签名下载,browser_cors 必须声明允许 range 请求头,并暴露 Accept-RangesContent-RangeContent-Length
  • 如果远端策略启用浏览器预签名上传,browser_cors 必须声明允许 content-type 请求头,并暴露 ETag

当前 follower 返回的 browser_cors.allowed_headers 至少包含 content-typerangebrowser_cors.exposed_headers 会覆盖 GET/PUT 预签名所需的缓存、Range、长度、类型和 ETag 响应头。

返回 follower 当前远端存储目标 driver 的 StorageCapacityInfo

{
"code": "success",
"msg": "",
"data": {
"capacity": {
"status": "supported",
"total_bytes": 1099511627776,
"available_bytes": 549755813888,
"used_bytes": 549755813888,
"source": "local_filesystem",
"observed_at": "2026-05-28T12:00:00Z"
}
}
}

实现约定:

  • follower 直接调用当前 target driver 的 capacity_info()
  • local target 通常返回真实文件系统容量
  • S3 target 明确返回 StorageErrorKind::Unsupported,primary 侧会把它转换成用户可见的 unsupported 容量状态
  • 这个接口只用于管理端容量观测和迁移 preflight,不在上传 / 下载热路径里调用

这条接口只服务于没有声明 features.binding_state_pull 的 legacy follower。新 primary 会用它把 binding desired state push 到旧 follower,请求体字段包括:

  • name
  • is_enabled
  • resolved_transportdirectreverse_tunnel;字段缺省时按 reverse_tunnel 处理
  • desired_revision:primary desired state revision;字段缺省时按 1 处理

这条接口只更新绑定元信息,不直接搬运对象数据。对象命名空间来自 follower 本地保存的 master binding,不由这条请求体传入。

legacy push 只在当前或切换前确实存在可用数据路径时尝试;没有可用路径时跳过,支持新控制面的 follower 仍会自行 pull 收敛。兼容 push 的删除条件是最低支持 follower 版本都显式声明 binding_state_pull

这组接口用于 primary 管理 follower 侧的远程存储目标,控制后续对象写入实际落到 follower 本地还是 follower 管理的 S3。当前请求 / 响应 DTO 使用 target_key 字段名。

创建本地目标的请求体形态:

{
"driver_type": "local",
"name": "local-default",
"base_path": "data/storage",
"is_default": true
}

创建 S3 目标的请求体形态:

{
"driver_type": "s3",
"name": "edge-s3",
"endpoint": "https://s3.example.com",
"bucket": "aster-edge",
"access_key": "AKIA...",
"secret_key": "...",
"base_path": "objects/",
"is_default": false
}

更新接口使用扁平字段,支持修改 namedriver_type、连接参数、base_pathis_default。当前 target driver 只有 locals3;实际可选项还会受到 follower 的 remote_storage_target 能力声明约束。这些控制面接口只接受主节点签名头,不使用预签名 query。

这条接口用于把多个上传 part 合成为最终对象,请求体包括:

  • target_key
  • part_keys
  • expected_size

成功后返回 bytes_written。实现上会在拼接成功后清理被消费的 part 对象。

写入一个对象。请求必须带 Content-Length,follower 会按 ingress 策略检查对象大小上限。

返回原始对象字节流,不走 JSON 包装。

可选 query:

  • offset
  • length
  • response-cache-control
  • response-content-disposition
  • response-content-type

也就是说,这条接口既支持整对象读取,也支持范围读取和响应头覆写。范围读取也可以通过标准 Range: bytes=... 请求头触发;返回部分内容时使用 206 Partial Content

返回对象是否存在以及基础响应头,常用于轻量探测。

返回统一 JSON 包装,data 里当前主要有:

  • size
  • content_type

删除对象,成功时返回空的统一成功响应。

支持以下 query:

  • prefix:只返回匹配前缀的对象 key。
  • cursor:从相对位置继续列举;通常使用上一页返回的 next_cursor
  • limit:请求页大小,必须大于 0;服务端会把它钳制到内部页大小上限。

新客户端应始终发送 limit。响应形态如下:

{
"code": "success",
"msg": "",
"data": {
"items": ["files/part-001", "files/part-002"],
"next_cursor": 2
}
}

只有后面仍有数据时才会返回 next_cursor。不传 limit 时,follower 保留旧客户端使用的无分页响应,并一次返回全部匹配项。

当前返回体里的 items 是 follower 绑定命名空间下的相对 key,不会把 provider 内部前缀原样暴露回去。

下面这些情况,不要再去普通 files / upload / shares 路由里瞎找:

  • 主节点写远端存储节点失败
  • 受管 follower 拼 part 失败
  • 远端节点健康正常,但对象列举 / 读取 / 删除异常
  • 远端节点 enrollment 成功后,后续对象同步行为不对