Rerun Hub 数据桶 CORS 配置详解:让 Web Viewer 直读预签名 URL 的原理与实操
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
当你把 Rerun Hub 部署用于多模态机器人数据的可视化与查询时,查询结果中的 chunk 数据并不经过服务端中转,而是以预签名 URL 的形式直接指向对象存储桶(S3/GCS/Azure 等)。桌面 Viewer 与 SDK 这类原生客户端天然可以直接使用这些 URL;但 Web Viewer 运行在浏览器中,受同源策略限制,除非桶通过 CORS 显式授权,否则浏览器会拦截对桶的直接读取。本文基于 Rerun 仓库文档 Bucket CORS 展开,完整给出可直接复制的 CORS 配置、应用与验证命令,并结合 chunk_fetcher.rs 的源码,解释 Viewer 究竟发送哪些请求、校验哪些响应头,以及配置缺失时数据如何回退到经 Rerun Hub 中转的路径。
为什么 Web Viewer 依赖桶级 CORS:直读与回退两条数据路径
Rerun 的 DataFrame 查询管线(位于 re_datafusion crate)采用"直接 URL + gRPC"混合拉取策略。服务端在查询响应中为每个 chunk 填充direct_url列(预签名的https://URL),客户端据此发起带Range头的 HTTP GET,直接命中对象存储;没有直读 URL 的行则回退到FetchChunksgRPC 代理。见 io_loop.rs 中的批次拆分逻辑:split_batch_by_direct_url将批次拆成直读与 gRPC 两半,逐波并发拉取。
- 原生客户端(桌面 Viewer、Python/Rust SDK)没有同源限制,预签名 URL 总是可用,直读路径始终生效。
- Web Viewer运行在浏览器里。浏览器的同源策略会阻止跨域读取桶对象,除非桶的 CORS 配置授权了 Viewer 所在的站点。
- 没有 CORS 配置时 Web Viewer 依然可用:它回退到通过 Rerun Hub 中转读取。代价是数据多一跳,且受 Rerun Hub 自身施加的限制约束(例如流式消息的单项大小上限)。
换句话说,CORS 配置不是"要不要"的问题,而是决定 Web Viewer 走"直读桶"还是"经服务端多一跳"的问题。
Viewer 到底发送什么、读取什么:来自源码的请求契约
在配置 CORS 之前,先要理解浏览器会替你发哪些请求。文档给出了 Viewer 的请求契约,这与 chunk_fetcher.rs 中的实现一一对应:
带
Range头的 rangedGET;当数据集带有ETag时还会附带If-Match头。源码中 fetch_merged_range_bytes 构造请求:let mut http_request = http_client .get(url) .header("Range", format!("bytes={range_start}-{range_end}")); // If-Match header to detect manifest drift at the source. if let Some(etag) = expected_etag.and_then(ETag::as_if_match) { http_request = http_request.header(reqwest::header::IF_MATCH, etag); }If-Match用于漂移检测:manifest 注册对象时记录了ETag,若源对象在注册之后被覆盖/替换,存储端会返回412 Precondition Failed,客户端将其归类为SourceChanged不可重试错误(见 DirectFetchError::source_changed 与PRECONDITION_FAILED分支 L996-L998)。Range与If-Match都不在 CORS 安全列表里,因此浏览器在每次真实读取前都会先发一个OPTIONS预检请求(结果按MaxAgeSeconds缓存)。这正是AllowedHeaders必须包含这两个头的原因。Viewer 会校验
Content-Range,并从响应中读取ETag与Last-Modified。源码中 FetchedRange 显式捕获这两个头:returned_etag用于解码失败时的归因比对(实际 ETag 与预期不符即判定数据漂移),Last-Modified随错误日志一并记录,帮助定位"源对象已被改动"这类问题。若桶未暴露这些头,Viewer 无法完成校验与漂移检测——因此ExposeHeaders必须包含它们,否则跨域时浏览器根本不会把响应头交给页面脚本。
配置示例:cors.json 与应用命令
典型部署形态:Web Viewer 部署在https://<stack>.cloud.rerun.io,数据存放在客户自有的 S3 桶中。桶需要一条允许该来源读取的 CORS 规则。完整配置如下(直接继承自仓库文档,可原样复制):
cors.json:
{ "CORSRules": [ { "AllowedOrigins": ["https://<customer>.cloud.rerun.io"], "AllowedMethods": ["GET", "HEAD"], "AllowedHeaders": ["Range", "If-Match"], "ExposeHeaders": ["Content-Range", "ETag", "Last-Modified", "Accept-Ranges"], "MaxAgeSeconds": 3600 } ] }各字段与 Viewer 行为的对应关系:
| 字段 | 取值 | 为什么需要 |
|---|---|---|
AllowedOrigins | Viewer 站点域名 | 浏览器只放行与响应Access-Control-Allow-Origin匹配的站点;必须是你部署的 Web Viewer 域名 |
AllowedMethods | GET,HEAD | Viewer 只发起 ranged GET 读取 |
AllowedHeaders | Range,If-Match | 两个请求头均非 CORS 安全头,不授权就会预检失败 |
ExposeHeaders | Content-Range,ETag,Last-Modified,Accept-Ranges | 跨域时浏览器默认隐藏这些头,不暴露则 Viewer 无法校验响应范围、无法读取 ETag/Last-Modified,会拒绝响应并回退 |
MaxAgeSeconds | 3600 | 预检结果缓存 1 小时,降低 OPTIONS 请求频率 |
应用配置:
aws s3api put-bucket-cors --bucket <bucket> --cors-configuration file://cors.json通配符与其他对象存储
- S3 允许每个 origin 使用一个
*通配符(例如https://*.cloud.rerun.io),但文档明确建议优先使用显式 origin——授权面最小,避免把权限意外开放给同域下未部署的站点。 - 其他对象存储使用同一套语义、各自的格式:GCS 将"允许的头"与"暴露的头"合并进同一个
responseHeader列表;Azure Blob Storage 则在存储账户层面配置 CORS。
直读路径的源码细节:合并、并发与重试
理解 CORS 配置后,再看直读路径如何消费这些请求,有助于判断配置失效时的症状。从 fetch_batch_via_direct_urls 的结构看,拉取分为五步:
- 按 URL 分组:把同一源对象上的多个 chunk 字节区间收集起来,并记录该对象的
expected_etag与registration_time(用于漂移检测)。 - 区间合并:merge_ranges_for_url 把相邻区间合并成更少的 HTTP Range 请求,合并间隔取平均 chunk 大小的 25%(calculate_optimal_gap_size),单个合并区间上限为 16 MB(MAX_MERGED_RANGE_SIZE)。也就是说 CORS 授权的一个 Range 请求可能覆盖多个 chunk。
- 自适应并发:calculate_adaptive_concurrency 按区间平均大小与总量动态调整并发数(130/90/30 档位,再受内存压力上限 25/8 约束),小区间高并发、大区间低并发。
- 并发拉取 + 重试:每个合并请求独立执行,瞬时错误最多重试 10 次(
DIRECT_FETCH_MAX_RETRIES),退避参数为 base 100ms、上限 3s、全抖动,与 gRPC 重试设置一致。400/401/403/405判定为不可重试(status_retryable)——注意:CORS 配置错误导致预检失败时,浏览器侧表现为请求被拦截,客户端视角往往是连接/解码类失败,排查时要优先确认OPTIONS是否通过。 - 按原始行序重组chunk,保证与查询批次顺序一致。
解码出的每个 chunk 是 protobuf 编码的ArrowMsg(decode_chunk_from_bytes),即 RRD 中的 Arrow 数据批次。
回退路径:无直读 URL 或强制 gRPC 时
以下情况数据会走FetchChunksgRPC 通道(fetch_batch_group_via_grpc),服务端代读并流式返回 chunk:
- 批次中没有任何非空
direct_url行(batch_has_any_direct_urls 判定),全部走 gRPC; - 原生客户端可设置环境变量
RERUN_CHUNK_STRATEGY=grpc强制 gRPC 路径(force_grpc),此时服务端也会跳过直读 URL 的生成。Wasm(Web Viewer)下没有环境变量,force_grpc恒为false,Web Viewer 完全依赖桶的 CORS 直读,失败即回退经 Hub 中转。
对 Web Viewer 而言,这意味着:桶 CORS 配置质量直接决定了 Web 端查询的数据路径与吞吐上限——直读时数据流量不经过 Hub 的转发带宽;回退时则受 Hub 流式大小限制约束。
验证:无需凭证的预检探测
文档提供了一条不依赖 AWS 凭证的OPTIONS探测命令(直接继承自仓库文档,可原样执行):
curl -i -X OPTIONS "https://<bucket>.s3.<region>.amazonaws.com/any-key" \ -H "Origin: https://<stack>.cloud.rerun.io" \ -H "Access-Control-Request-Method: GET" \ -H "Access-Control-Request-Headers: range,if-match"判读响应:
200且Access-Control-Allow-Origin回显了你传入的 origin→ 预检通过,直读可用。建议同时确认响应中存在Access-Control-Expose-Headers(或等效覆盖)包含Content-Range、ETag、Last-Modified,否则 Viewer 能连通却读不到校验所需头,仍会拒绝响应并回退。403且附带CORSResponse: CORS is not enabled for this bucket→ 桶上没有 CORS 配置,按上文put-bucket-cors补齐后重测。
小结与检查清单
结合文档与源码,一条可执行的落地检查清单:
- 桶上存在 CORS 规则,
AllowedOrigins为 Web Viewer 的显式域名(避免裸通配符); AllowedHeaders含Range与If-Match(对应 fetch_merged_range_bytes 发出的两个非安全头);ExposeHeaders覆盖Content-Range、ETag、Last-Modified、Accept-Ranges(对应 Viewer 的响应校验与漂移检测,FetchedRange);- 用
curl预检探测确认200+ origin 回显; - GCS/Azure 场景按各自格式落地同一套语义(GCS 的
responseHeader合并头列表、Azure 账户级 CORS)。
配置完成后,Web Viewer 的查询数据将不再经 Rerun Hub 多一跳,而是由浏览器直接以 ranged GET 读取桶内对象;当直读不可用时,io_loop.rs 的混合调度会透明地回退到 gRPC 路径,功能不受影响,只是多了一层中转与限流约束。
相关代码与文档入口:
- 配置文档:docs/content/hub/bucket-cors.md
- 直读拉取实现:crates/store_app/re_datafusion/src/chunk_fetcher.rs
- 直读/gRPC 混合调度:crates/store_app/re_datafusion/src/dataframe_query_provider/io_loop.rs
- gRPC 强制开关:crates/store_app/re_datafusion/src/dataframe_query_common.rs
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考