news 2026/9/16 21:29:48

Rerun Hub 数据桶 CORS 配置详解:让 Web Viewer 直读预签名 URL 的原理与实操

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rerun Hub 数据桶 CORS 配置详解:让 Web Viewer 直读预签名 URL 的原理与实操

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 中的实现一一对应:

  1. 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)。

  2. RangeIf-Match都不在 CORS 安全列表里,因此浏览器在每次真实读取前都会先发一个OPTIONS预检请求(结果按MaxAgeSeconds缓存)。这正是AllowedHeaders必须包含这两个头的原因。

  3. Viewer 会校验Content-Range,并从响应中读取ETagLast-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 行为的对应关系:

字段取值为什么需要
AllowedOriginsViewer 站点域名浏览器只放行与响应Access-Control-Allow-Origin匹配的站点;必须是你部署的 Web Viewer 域名
AllowedMethodsGET,HEADViewer 只发起 ranged GET 读取
AllowedHeadersRange,If-Match两个请求头均非 CORS 安全头,不授权就会预检失败
ExposeHeadersContent-Range,ETag,Last-Modified,Accept-Ranges跨域时浏览器默认隐藏这些头,不暴露则 Viewer 无法校验响应范围、无法读取 ETag/Last-Modified,会拒绝响应并回退
MaxAgeSeconds3600预检结果缓存 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 的结构看,拉取分为五步:

  1. 按 URL 分组:把同一源对象上的多个 chunk 字节区间收集起来,并记录该对象的expected_etagregistration_time(用于漂移检测)。
  2. 区间合并:merge_ranges_for_url 把相邻区间合并成更少的 HTTP Range 请求,合并间隔取平均 chunk 大小的 25%(calculate_optimal_gap_size),单个合并区间上限为 16 MB(MAX_MERGED_RANGE_SIZE)。也就是说 CORS 授权的一个 Range 请求可能覆盖多个 chunk。
  3. 自适应并发:calculate_adaptive_concurrency 按区间平均大小与总量动态调整并发数(130/90/30 档位,再受内存压力上限 25/8 约束),小区间高并发、大区间低并发。
  4. 并发拉取 + 重试:每个合并请求独立执行,瞬时错误最多重试 10 次(DIRECT_FETCH_MAX_RETRIES),退避参数为 base 100ms、上限 3s、全抖动,与 gRPC 重试设置一致。400/401/403/405判定为不可重试(status_retryable)——注意:CORS 配置错误导致预检失败时,浏览器侧表现为请求被拦截,客户端视角往往是连接/解码类失败,排查时要优先确认OPTIONS是否通过。
  5. 按原始行序重组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"

判读响应:

  • 200Access-Control-Allow-Origin回显了你传入的 origin→ 预检通过,直读可用。建议同时确认响应中存在Access-Control-Expose-Headers(或等效覆盖)包含Content-RangeETagLast-Modified,否则 Viewer 能连通却读不到校验所需头,仍会拒绝响应并回退。
  • 403且附带CORSResponse: CORS is not enabled for this bucket→ 桶上没有 CORS 配置,按上文put-bucket-cors补齐后重测。

小结与检查清单

结合文档与源码,一条可执行的落地检查清单:

  1. 桶上存在 CORS 规则,AllowedOrigins为 Web Viewer 的显式域名(避免裸通配符);
  2. AllowedHeadersRangeIf-Match(对应 fetch_merged_range_bytes 发出的两个非安全头);
  3. ExposeHeaders覆盖Content-RangeETagLast-ModifiedAccept-Ranges(对应 Viewer 的响应校验与漂移检测,FetchedRange);
  4. curl预检探测确认200+ origin 回显;
  5. 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 21:29:46

wigolo搜索流水线深度剖析:18引擎并行扇出、RRF融合与ML重排

wigolo搜索流水线深度剖析&#xff1a;18引擎并行扇出、RRF融合与ML重排 【免费下载链接】wigolo The go-to web for your AI coding agent — local-first search, fetch, crawl & research over MCP. No API keys, no cloud, $0/query. Public beta. 项目地址: https:/…

作者头像 李华
网站建设 2026/9/16 21:28:12

BFS算法解析:LeetCode 994腐烂的橘子问题

1. 问题背景与题目解析今天想和大家分享一道经典的广度优先搜索&#xff08;BFS&#xff09;算法题——LeetCode 994题"腐烂的橘子"。这道题看似简单&#xff0c;但蕴含着很多值得深入思考的算法细节&#xff0c;也是面试中的高频题目。题目描述是这样的&#xff1a;…

作者头像 李华
网站建设 2026/9/16 21:27:32

AD9653与FPGA高速连接设计:JESD204B接口、时钟树与PCB信号完整性

1. 项目概述&#xff1a;这不是接根线就完事的“简单连接”AD9653采集模块怎样连接FPGA底板&#xff1f;——看到这个标题&#xff0c;我第一反应不是去翻数据手册&#xff0c;而是先问自己&#xff1a;你手里的FPGA底板&#xff0c;是实验室里那块带JTAG下载口、几个LED和按键…

作者头像 李华
网站建设 2026/9/16 21:23:10

2026年有线耳机选购指南:从单元到参数,避开这些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 21:22:13

DGX Spark实战:从大模型微调到边缘推理的完整指南

第一次把 DGX Spark 放到办公桌上的时候&#xff0c;我盯着这个比 Mac mini 大不了多少的机箱看了半天。说明书上写着 1 PFLOP&#xff08;FP4&#xff09;AI 算力、128GB 统一内存&#xff0c;NVIDIA 管它叫“个人 AI 超级计算机”。我习惯性打开终端敲下nvidia-smi&#xff0…

作者头像 李华
网站建设 2026/9/16 21:21:38

NVIDIA DGX Spark实战指南:从驱动到大模型微调推理

NVIDIA DGX Spark 是我今年上手之后&#xff0c;实际用下来最意外的一台设备。体积比 Mac mini 大不了多少&#xff0c;却能同时承接大模型训练、微调、部署和边缘推理整条链路&#xff0c;而且开发者体验比传统 GPU 服务器顺畅太多。这篇指南我不打算只念官方参数&#xff0c;…

作者头像 李华