HTTP 传输与 FastSim Core 分离。不开启该服务时,不会引入 HTTP、序列化、网络或 逐物理帧开销。服务本身不实现仿真逻辑,也不直接导入任何仿真器后端。
从当前仓库安装
请使用 FastSim 所在的同一个 Python 环境:
python -m pip install ./packages/fastsim-plugin-server
fastsim-server --help
使用浏览器工作台的实时相机画面时,请安装可选 WebRTC 依赖:
python -m pip install './packages/fastsim-plugin-server[webrtc]'
远程客户端或 Agent 需要 IK 时安装可选 Kinematics 服务:
python -m pip install './packages/fastsim-plugin-server[kinematics]'
可用下面的命令确认可选 Provider 与软件视频编码器均可正常使用:
python -c "from fastsim_plugin_server.media import select_media_transport; print(select_media_transport('webrtc').active_transport)"
命令必须输出 webrtc。基础安装只有在请求选择媒体传输时才会导入和探测
aiortc 与 PyAV;未启用浏览器视频的部署不会因此增加启动或仿真热路径开销。
--media-mode webrtc 会强制要求该依赖;缺少时会返回准确的安装命令,而不是静默
降级。--media-mode snapshot 表示显式使用 JPEG 帧请求;--media-mode auto 允许带明确
告警的兼容回退。无论采用哪种画面传输,运行状态都继续走持久 WebSocket 会话。
包版本 0.8.4 支持 FastSim >=0.1.0a19,<0.2,并保持
websockets>=12,<17。该有界范围允许 Server 跟随保持公开 Application 与
fastsim-http/1 契约兼容的 Core alpha 更新,同时把下一条 minor 版本线保留为
显式复审边界。详见变更记录。源码树中的版本号不代表
已发布到 PyPI。
启动一个 Run
fastsim-server run.yaml
默认地址为 http://127.0.0.1:8000。进程固定使用一个 Uvicorn worker 和一个
FastSim 应用事件循环,不提供 reload 和多 worker,避免多个进程同时占用同一个
仿真 Run。
未指定 --launch-root 时,这种 positional 形式保持原有的不可替换 Run 语义;显式
添加 launch root 才会把初始 Run 放入可替换的常驻槽位。
场景、坐标系和规划几何信息默认不发布,需要显式开启:
fastsim-server run.yaml --planning-reads
常驻启动服务
独立部署的 Web Client 可以连接一个启动时没有初始 Run 的常驻服务。服务运维者只 开放命名后的配置目录:
fastsim-server \
--launch-root examples=/srv/fastsim/runs \
--browser-origin http://127.0.0.1:8080
GET /api/v1/launch 返回槽位状态、generation、公开 root 别名、允许的格式和准确的
上传字节上限。客户端可从白名单目录启动配置:
curl -sS -X POST http://127.0.0.1:8000/api/v1/run/launch \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: launch-demo-001' \
-d '{
"schema": "fastsim-run-launch/1",
"source": {
"kind": "server_path",
"root": "examples",
"config": "kitchen/run.yaml"
},
"target_state": "running"
}'
也可以上传一份独立的 YAML、JSON 或 TOML UTF-8 文本配置:
jq -n --rawfile content ./run.yaml '{
schema: "fastsim-run-launch/1",
source: {kind: "upload", filename: "run.yaml", content: $content},
target_state: "running"
}' | curl -sS -X POST http://127.0.0.1:8000/api/v1/run/launch \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: launch-upload-001' \
--data-binary @-
Launch 是异步 operation,需要轮询返回的 operation URL。调用
POST /api/v1/run/close 并完成应用与插件清理后,槽位回到 idle,可继续启动下一次
Run。target_state 默认是 running;prepared 会进入并准备 FastSim,但不启动仿真;
open 只编译并挂载延迟应用,不会打开仿真器。
动态 Launch 还可设置 "planning_reads": true,只为本次 Run 启用场景、坐标系和规划
几何服务。省略时继承 Server 的 --planning-reads 默认值;Server 未带该参数时,普通
相机和控制 Run 保持轻量模式。
上传模式只接收一份隔离配置文档,可以使用已安装的组件包,但不会发现上级 Project,
也不支持本地配套文件或上传 lock。动态 server-path 同样不接收 lock;原有的
fastsim-server run.yaml --lock run.lock.yaml 固定启动方式继续保留。
常用启动参数:
| 参数 | 含义 |
|---|---|
--lock run.lock.yaml |
使用已经解析并冻结的 Run lock |
--launch-root ALIAS=/ABS/PATH |
允许从一个命名服务端目录选择配置;可重复指定 |
--output-root /ABS/PATH |
为每个 Run 代次创建独立私有输出目录,并启用产物 API |
--authority exclusive|operator|background|read_only |
选择一种由 Core 定义的控制权限 |
--planning-reads |
要求 FastSim 发布场景、坐标系和几何服务 |
--timeout SECONDS |
设置 FastSim 超时,并在未覆盖时推导 HTTP 控制/查询超时 |
--application-open-timeout SECONDS |
限制 FastSim 应用的延迟启动;默认继承 --timeout,否则为 120 秒 |
--control-timeout SECONDS |
单独设置 HTTP 控制 operation 超时 |
--query-timeout SECONDS |
单独设置所有公开读取的超时 |
--body-read-timeout SECONDS |
限制接收单个请求体的时间;默认 10 秒 |
--max-in-flight-requests N |
限制并发 HTTP 请求数;默认 256,最大 10000 |
--max-buffered-body-bytes N |
限制所有客户端累计缓冲的请求体;默认 32 MiB |
--max-binary-requests N |
限制相机、流体、几何和产物二进制并发;默认 4 |
--host / --port |
设置监听地址;默认 127.0.0.1:8000 |
--trusted-host HOST |
允许一个浏览器访问的精确 API Host;可重复指定;使用通配监听时必须显式配置 |
--browser-origin ORIGIN |
允许一个独立 Web Client 的精确源;可重复指定;默认关闭 |
--media-mode webrtc|snapshot|auto |
强制 WebRTC、显式 JPEG 快照,或允许带告警的兼容回退 |
--quiet-access-log |
关闭重复 HTTP access log,同时保留 FastSim 诊断信息 |
第一个负责启动 Run 的生命周期或控制请求会延迟打开 FastSim;只读数据接口绝不会
触发启动。即使 FastSim 本身未显式指定 --timeout,这一步仍有独立硬截止时间。启动器同时会给
FastSim facade 传入一个有限且不大于应用打开期限的 timeout,使 Core 内部清理与
HTTP 外层取消边界一致。超时会返回 504 application_open_timeout,健康检查和
operation 历史仍可访问,后续请求也可以重新尝试启动。实际截止时间会出现在
discovery 和 capabilities 文档中。
启动后先查看接口发现:
curl -sS http://127.0.0.1:8000/api/v1 | jq
- OpenAPI:
GET /api/v1/openapi.json - 交互式接口文档:
GET /api/v1/docs - 静态能力声明:
GET /api/v1/capabilities
请求模型
生命周期和控制写入都是异步操作。提交成功返回 202 Accepted、operation ID 和
Location 响应头。调用方应轮询 operation,直到 state 变为 succeeded、
failed 或 cancelled。
场景命令不同:它们直接通过公开 FastSimApplication.scene facade 结算,并返回
Core 的 fastsim-scene-command-result/1。Server 不会另建命令队列、拖拽求解器或
幂等缓存。
BASE=http://127.0.0.1:8000
START_ID=$(curl -sS -X POST "$BASE/api/v1/run/start" | jq -r .operation_id)
curl -sS "$BASE/api/v1/operations/$START_ID" | jq
Run launch 和物理控制接口必须带 Idempotency-Key。同一个 key 和同一请求会返回
原 operation;同一个 key 配合不同请求会返回 409 Conflict。其他生命周期接口也
支持该请求头,但保持可选。
使用可替换槽位时,launch 之后的每个生命周期写入和控制写入还必须携带
X-FastSim-Slot-Generation,其值取自 GET /api/v1/launch 或 /api/v1/run 返回的
generation。服务会在槽位 mutation lock 内、接触 FastSim 之前检查该前置条件。旧客户端
若仍指向 Run 1,而另一客户端已经启动 Run 2,会收到 409 stale_slot_generation,
不会误关或误控新 Run。传统不可替换的 positional 或嵌入式 Server 不要求该请求头。
CONTROL_ID=$(curl -sS -X POST "$BASE/api/v1/control/joint-paths" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: droid-arm-demo-001' \
-H 'X-FastSim-Slot-Generation: 1' \
-d '{
"actor": "robots.droid",
"group": "arm",
"path": [[0.0, -0.4], [0.2, -0.2]],
"dt": 0.0166666667,
"preempt": false,
"timeout_s": 30.0
}' | jq -r .operation_id)
curl -sS "$BASE/api/v1/operations/$CONTROL_ID" | jq
已经接纳的任务属于 Run,不属于某一条 TCP 连接。客户端断开不会取消仿真任务。
需要取消时调用 DELETE /api/v1/operations/{operation_id}。
完整接口表
发现与健康检查
| 方法与路径 | 返回内容 |
|---|---|
GET /health/live |
HTTP 进程是否存活;绝不会因此打开仿真器 |
GET /health/ready |
HTTP 是否接收新任务及 application_opened;不会因此打开仿真器 |
GET /api/v1 |
API 版本、链接、硬限制和二进制媒体类型 |
GET /api/v1/capabilities |
结合 authority 与启动参数的能力声明 |
GET /api/v1/metrics/process |
按需采样当前 Server 进程的 CPU、RSS 和可选 NVML 显存 |
GET /api/v1/launch |
Run 槽位状态、generation、来源目录、格式和上传上限 |
GET /api/v1/openapi.json |
受鉴权保护的 OpenAPI 文档 |
GET /api/v1/docs |
受鉴权保护的交互式接口文档 |
按需进程指标
GET /api/v1/metrics/process 返回当前 Server PID 的一份
fastsim-process-metrics/1 文档;可替换 Run 槽位处于 idle 时也可以读取:
{
"schema": "fastsim-process-metrics/1",
"sampled_at": "2026-08-27T00:00:00Z",
"pid": 12345,
"cpu_percent": 17.5,
"rss_bytes": 536870912,
"gpu_memory_bytes": null,
"gpu_memory_available": false,
"gpu_memory_unavailable_reason": "pynvml_not_installed",
"gpu_device_count": null
}
cpu_percent 是相邻按需采样之间,当前 Server 进程消耗的 CPU 时间除以墙钟时间;
首个样本为 0.0,多线程进程可能超过 100%。rss_bytes 是当前进程驻留内存。
GPU 显存是 NVML 在所有设备上归因到当前 PID 的总和;同一设备在 compute/graphics
视图中的重复记录不会重复计数。可选 pynvml/NVML 缺失或无法完成进程查询时,
GPU 显存为 null,gpu_memory_unavailable_reason 给出稳定原因码。Server 绝不会
启动 nvidia-smi。
实时通道 metrics 通过 fastsim-realtime/1 发布同一文档,默认 1 Hz,可请求的
max_hz 不得超过 5。采样严格按需:既没有读取端点、也没有活动 metrics 订阅时,
Server 不创建指标任务、不读取进程文件系统计数器,也不导入或初始化 NVML;最后一个
订阅关闭后,其 sampler 会被取消。
实际控制帧实时流
Run 级实时通道 control_applied 暴露后端实际接纳的控制帧。该通道严格按需工作:
仅当至少存在一个 WebSocket 订阅者时,Server 才打开一个共享的 Core
subscribe_applied_frames handle,参数固定为 capacity=64、
max_item_bytes=1048576、overload=drop_oldest。最后一个订阅关闭、slot generation
改变、launch 失败或 Run 被 terminal stop/close 时,Core handle 与 sampler 都会释放。
worker 每次采样最多投影一个按序且有大小上限的可靠批次,频率不超过 30 Hz;超出部分
留在有界 Core 队列中,其 drop_oldest 丢失账本会作为显式 gap 发布。Server transport
不为实际控制帧保留历史:重连 resume 会收到带 history_unavailable 的 stream.reset,
而不会触发可能达到数 MiB 的可靠 replay 洪峰。
stream.snapshot 内的 value 具有以下稳定形状:
{
"schema": "fastsim-control-applied-batch/1",
"items": [
{
"schema": "fastsim-control-audit/1",
"kind": "applied_frame",
"sequence": 7,
"record_sequence": 11,
"generation": 2,
"sim_tick": 17,
"sim_time_s": 0.2833333333333333,
"received_monotonic_ns": 123456,
"estimated_size_bytes": 512,
"payload": {"schema_version": "fastsim-applied-control-frame/1", "apply_tick": 17}
}
],
"first_source_sequence": 7,
"last_source_sequence": 7,
"item_count": 1
}
上例为简洁省略了字段;payload 实际是完整的 canonical
applied_frame_to_dict mapping。Core 过载造成的丢失不会被隐藏:kind: "gap"
的 item 会保留 reason、source/record sequence 范围、tick 范围、item_count 与
byte_count。WebSocket envelope 顶层 generation 是用于隔离的 Server Run slot
generation;每个 item 的 generation 则是独立的 Core runtime generation,reset 时
可以变化,也不要求与 slot generation 相等。
动态 launch 期间已接纳的 Run 级订阅会跨越暂时的 cold/launch 边界,以有界频率重试; 一次性的 scene scenario/catalog 读取只有在成功采样后才停止。resume replay 会按 generation 过滤,上一 Run 的历史不会再压制新 Run 中内容相同的 scene 快照。
Run 与 Scenario
| 方法与路径 | 返回内容 |
|---|---|
GET /api/v1/run |
无副作用的 Run 元数据;仅在稳定 Run 中补充 provider 身份信息 |
GET /api/v1/run/snapshot |
公开 FastSim 应用快照 |
GET /api/v1/run/artifacts |
列出指定槽位代次已提交、在白名单内的输出 |
GET /api/v1/run/artifacts/{path} |
完整下载一个输出,或读取单段 byte Range |
POST /api/v1/run/launch |
从白名单服务端配置或上传的独立配置启动 Run |
POST /api/v1/run/prepare |
准备 Run |
POST /api/v1/run/start |
启动仿真 |
POST /api/v1/run/pause |
暂停仿真 |
POST /api/v1/run/resume |
恢复仿真 |
POST /api/v1/run/step |
前进指定且有上限的物理步数 |
POST /api/v1/run/reset |
重置 Run |
POST /api/v1/run/stop |
停止 runtime 和插件任务 |
POST /api/v1/run/close |
终止 Run 并释放应用资源 |
GET /api/v1/scenario |
Scenario 摘要、digest 和可用分区 |
GET /api/v1/scenario/source |
冻结后的 Scenario 源配置 |
GET /api/v1/scenario/scene |
冻结后的 scene 分区 |
GET /api/v1/scenario/behavior |
可选 behavior 分区;不存在时返回 404 |
GET /api/v1/scenario/evaluation |
可选 evaluation 分区;不存在时返回 404 |
Run 产物默认关闭。先创建一个由运维者持有的目录,再通过
--output-root /绝对/输出/目录 启动 Server。每个被接纳的 launch generation 都会获得
不同的私有子目录;FastSim 可信的 run.output@1 policy 会把 Recorder FSR 写入这里。
其他生产者应通过原子 rename 提交最终文件名,并把临时文件保持为隐藏状态。Server
不会安装 watcher 或仿真 callback,只有显式调用列表接口时才扫描当前私有代次目录。
两个产物接口始终要求携带 GET /api/v1/launch 返回的精确
X-FastSim-Slot-Generation;terminal close 后槽位已回到 idle 时也一样。下一次 launch
会立即隔离上一代。白名单包含 FSR、MP4/WebM、JSON/JSONL、CSV、PNG/JPEG、文本、
日志和 Markdown。空文件、隐藏文件、临时文件、未知格式、符号链接、FIFO 及其他特殊
文件均不会暴露。
GENERATION=1
curl -sS "$BASE/api/v1/run/artifacts" \
-H "X-FastSim-Slot-Generation: $GENERATION" | jq
curl -sS "$BASE/api/v1/run/artifacts/episode.fsr" \
-H "X-FastSim-Slot-Generation: $GENERATION" \
-H 'Range: bytes=0-16777215' \
-o episode.part
列表和下载均受 discovery 公布的上限约束;路径在已固定的目录 descriptor 下解析,
全程不跟随符号链接。应先 terminal run/close,再把插件输出视为最终结果:close 会
排空控制,让 Recorder 等插件完成 finalize;随后该代次仍保持可读,直到启动新 Run。
Server 不会删除已完成的代次目录;运维者需要自行负责输出根目录的保留、归档与清理。
run/close 会先取消并排空正在执行的控制 operation,再执行 FastSim 插件和应用
清理。在常驻启动模式下,槽位随后回到 idle;不可替换的嵌入式应用则为保持兼容
而停留在 closed。HTTP operation 注册表
继续保留,客户端仍可查询 close operation。使用同一个幂等 key 重复 close 会返回
原 operation;Run 已关闭后再结束服务进程也不会重复清理或报错。
同一时刻只接纳一个生命周期转换。转换期间的新 lifecycle/control 请求立即返回
409 lifecycle_transition_in_progress。每一种生命周期转换都会先关闭新的数据面
读取入口,并在有界时间内等待已接纳的读取退出,再操作仿真器。reset、stop、
close 还会取消并彻底排空控制 operation,包括底层 release;活动控制期间仍可
使用 pause、resume 和 step。
原始数据面读取仅在生命周期稳定处于 prepared、running 或 paused 时准入。
应用快照、插件状态与事件、状态/控制查询、相机/流体帧和规划读取,在冷启动、状态
转换、stopped、closed 或 failed 时统一快速返回
503 data_plane_unavailable。被拒绝的读取不会隐式打开 FastSim,也不会排入其
ApplicationEventLoop。健康检查、operation 历史、/api/v1/run 元数据、能力声明
和不可变 Scenario 接口在上述状态与转换期间仍保持可读。
常驻槽位为空时,/api/v1/run 返回 present: false,依赖 Run 的读取和写入会立即
返回 409 run_not_configured。
使用 manage_application=False 嵌入 Python 时,调用方持有已经进入的应用,因此
稳定的 open 状态允许数据面读取,但不会被伪装成 running;转换、排空、终态和
失败态门禁仍然有效。
插件、状态与控制
| 方法与路径 | 返回内容 |
|---|---|
GET /api/v1/plugins/status |
有上限的公开插件宿主快照 |
GET /api/v1/plugins/{instance}/status |
指定插件实例的公开状态和有上限的宿主上下文 |
GET /api/v1/plugins/events |
插件事件分页;支持 sequence、instance、topic 筛选 |
GET /api/v1/state |
同一时刻的一致 World/Observation 状态 |
GET /api/v1/control/targets |
查询 actor、group、axis、command space 和 controller |
GET /api/v1/control/state |
当前公开控制状态;可设置 timeout_s |
POST /api/v1/control/joint-paths |
提交关节位置轨迹 |
POST /api/v1/control/tracks |
提交通用物理 Track,支持多资源 chunk |
GET /api/v1/operations |
operation 分页列表,可按 state、kind 筛选 |
GET /api/v1/operations/{operation_id} |
查询状态、进度和终态结果 |
DELETE /api/v1/operations/{operation_id} |
对可取消操作发起取消请求 |
场景、坐标系与几何
以下接口依赖对应的 FastSim 公开服务。需要规划信息时,应使用
--planning-reads 启动;场景命令使用 Core command service,不要求开启规划读取。
| 方法与路径 | 返回内容 |
|---|---|
GET /api/v1/scene/catalog |
可筛选的静态实体、link 和 articulation 目录 |
GET /api/v1/scene/state |
可筛选的动态场景状态 |
POST /api/v1/scene/entities/{entity_id}/pose |
设置一个已有实体的世界位姿 |
POST /api/v1/scene/entities/{entity_id}/attachments |
将刚体或铰接体指定 link 固定到另一物理端点,并返回 attachment_id |
POST /api/v1/scene/entities/{entity_id}/attachments/{attachment_id}/detach |
解除固定,不重置 child 铰接状态 |
POST /api/v1/scene/entities/{entity_id}/drags |
开始一次拖拽并返回 drag_id |
POST /api/v1/scene/entities/{entity_id}/drags/{drag_id}/updates |
把拖拽中的实体移动到世界位姿 |
POST /api/v1/scene/entities/{entity_id}/drags/{drag_id}/end |
正常结束拖拽 |
POST /api/v1/scene/entities/{entity_id}/drags/{drag_id}/cancel |
取消拖拽 |
GET /api/v1/frames/catalog |
可筛选的坐标系目录 |
GET /api/v1/frames/transform?source=...&target=... |
一次公开坐标变换 |
GET /api/v1/geometry/capabilities |
几何表示类型和服务限制 |
GET /api/v1/geometry/catalog |
可筛选的不可变几何目录;JSON 不内嵌大块数据 |
GET /api/v1/geometry/transforms |
可筛选的动态几何位姿 |
GET /api/v1/geometry/resources/{geometry_id} |
完整或指定范围的不可变几何二进制 |
运动学
| 方法与路径 | 返回内容 |
|---|---|
GET /api/v1/kinematics/descriptor |
当前 Solver 身份、支持的模型/关节/碰撞模式与算法 |
POST /api/v1/kinematics/ik |
从已提交的当前关节状态求解末端位姿,但不会移动机器人 |
入门请求只需机器人、Base/Tip Frame、目标 xyz_m 与 quat_xyzw;可选 Group、
容差、超时、碰撞模式、Seed 和解的数量。FastSim 负责当前状态捕获、模型选择、
Generation 围栏与 Provider 执行,HTTP 层只负责验证和传输公开结果。随附 Portable
Solver 是局部 DLS,不宣称提供碰撞感知全局规划。
目录接口支持 offset 和 limit;坐标变换还可指定不可变 generation。
场景命令只操作本次 Run 已经加载的实体,不负责生成资产、求解 IK 或增加语义。 最短的位姿命令如下:
curl -sS -X POST "$BASE/api/v1/scene/entities/objects.cube/pose" \
-H 'Content-Type: application/json' \
-d '{"xyz_m":[0.35,0.0,0.55]}' | jq
朝向默认是单位四元数。generation 与 command_id 都可以省略:省略时由 Core 在
提交时绑定或生成;高级调用方可以显式提供,从而获得 generation 栅栏和精确重试
幂等性。HTTP Idempotency-Key 只是 command_id 的另一种写法;两者同时存在时
必须相同。可替换 Server 还需要独立的 X-FastSim-Slot-Generation 请求头。Core
返回 rejected 仍然是 HTTP 200 的正常结算,并带有 status: "rejected";服务
边界失败则保留 Core 错误码以及 command、entity、generation 详情。
筛选字段使用重复 query 参数,并取各条件的交集:
curl -G "$BASE/api/v1/scene/state" \
--data-urlencode 'entity_id=robots.droid' \
--data-urlencode 'entity_id=objects.cube' \
--data-urlencode 'enabled=true'
curl -G "$BASE/api/v1/geometry/catalog" \
--data-urlencode 'purpose=collision' \
--data-urlencode 'motion_class=static' \
--data-urlencode 'representation=triangle_mesh'
geometry ID 可以包含 /,resource 路由会捕获完整 ID。无论读取成功还是失败,
服务都会关闭 FastSim 的作用域 resource lease,并且绝不返回 provider locator、
lease token 或本地文件路径。优先使用标准单段 Range: bytes=... 请求头,也可使用
兼容的 offset 和 length 参数读取最大 64 MiB;两种方式不能混用:
curl -sS "$BASE/api/v1/geometry/resources/robots.droid/base/collision?offset=0&length=65536" \
-o geometry.bin
curl -sS -H 'Range: bytes=0-65535' \
"$BASE/api/v1/geometry/resources/robots.droid/base/collision" -o geometry.bin
相机、粒子流体与柔性体
| 方法与路径 | 媒体类型 | 数据布局 |
|---|---|---|
GET /api/v1/cameras/{entity_id}/rgb |
application/vnd.fastsim.rgb24 |
行优先紧凑 uint8 RGB24 |
GET /api/v1/cameras/{entity_id}/png |
image/png |
无损、适合浏览器直接显示的 RGB PNG |
GET /api/v1/fluids/{entity_id}/particles |
application/vnd.fastsim.particle-fluid-f64 |
小端 float64 位置 [N,3],随后为速度 [N,3] |
GET /api/v1/deformables/{entity_id}/nodes |
application/vnd.fastsim.deformable-f64 |
小端 float64 节点位置 [N,3],随后为节点速度 [N,3] |
Run ID、实体 ID、generation、仿真 tick/time、shape、dtype 和 encoding 通过
X-FastSim-* 响应头返回。
PNG 只在请求时编码,并在线程池中完成,不阻塞 ASGI 事件循环。原始 RGB 路径不做
图像编码。
curl -sS "$BASE/api/v1/cameras/sensors.front/png?timeout_s=5" -o front.png
curl -sS "$BASE/api/v1/fluids/fluids.water/particles?timeout_s=5" -o water.f64
curl -sS "$BASE/api/v1/deformables/deformables.cloth/nodes?timeout_s=5" -o cloth.f64
远程监听安全
默认只监听 loopback。监听非本机地址时,必须同时启用 TLS,并使用一个长度为
32~4096 的 RFC 6750 b64token。token 主体只接受 ASCII 字母、数字和 -._~+/,
可选的 = padding 只能位于末尾;Unicode 和控制字符会被拒绝。token 文件必须是仅当前用户可读的普通文件;
TLS 私钥也必须是非符号链接的普通文件,权限为 0600 或更严格:
chmod 600 ./fastsim-server.token
chmod 600 ./server.key
fastsim-server run.yaml --planning-reads \
--host 192.0.2.10 \
--browser-origin https://console.example \
--token-file ./fastsim-server.token \
--tls-cert-file ./server.crt \
--tls-key-file ./server.key
每次请求都要发送 Authorization: Bearer ...。健康检查、OpenAPI 和交互式文档也
经过相同鉴权。token 不能以内联参数、cookie 或 Run 配置的方式提供。
默认关闭浏览器跨域访问。独立托管的静态 Web Client 需要显式配置一个或多个精确 来源:
fastsim-server run.yaml \
--browser-origin http://127.0.0.1:4173 \
--browser-origin https://console.example
每个值只能包含 http 或 https、主机和可选端口。通配符、null、userinfo、路径、
query 和 fragment 都会被拒绝;省略默认端口和显式写出默认端口语义相同。非 loopback
前端必须使用 HTTPS,整个 allowlist 最多 64 项。浏览器用
Authorization: Bearer ... 传 token,并使用 credentials: "omit";服务不会启用
cookie credentials。
合法的 OPTIONS 预检会在 bearer 鉴权、请求体接收和 FastAPI/应用分发之前完成。
预检只接受真实 API 路由的方法,以及有上限的 Accept、Authorization、
Content-Type、Idempotency-Key、Range、X-FastSim-Slot-Generation 和
X-Request-Id 请求头。实际浏览器请求
仍要经过 bearer 鉴权和全部常规 API 限制。允许的响应会暴露 Location、
Retry-After、range 元数据、X-Request-Id 和完整的可移植 X-FastSim-* 元数据,
并且永远不会返回 Access-Control-Allow-Credentials。
Origin、Host 和 Fetch Metadata 会在延迟打开应用或相机编码前校验。同源浏览器无需
allowlist,但必须提供唯一可信 Host 和唯一 Site/Mode,不能携带 cookie;API fetch 可用
cors/same-origin,GET 页面导航可用 navigate,Site 必须为 same-origin(或
none)。独立部署且已允许的前端
必须报告 same-site 或 cross-site,并使用 CORS fetch mode。重复安全头、Fetch
Metadata 与 Origin 不一致、跨站资源探测缺少 Origin、Host 不匹配都会被拒绝。不发送
Origin 和浏览器 Fetch Metadata 的 SDK、命令行客户端保持兼容。
浏览器访问的 API authority 必须与明确配置的 --host/--port 一致,或者
与某个精确的 --trusted-host 及配置的 --port 一致。0.0.0.0 等通配
监听地址本身永远不是可信公开地址。监听所有网卡时,必须显式声明每个
浏览器访问 Host,例如:
fastsim-server run.yaml \
--host 0.0.0.0 --port 8443 \
--trusted-host 127.0.0.1 \
--browser-origin http://127.0.0.1:8090 \
--token-file ./fastsim-server.token \
--tls-cert-file ./server.crt \
--tls-key-file ./server.key
--trusted-host 只接受 DNS 名、IPv4 或 IPv6 地址,不接受 scheme、端口、
通配地址或路径。输入会按精确值规范化、去重,最多 64 项。HTTP 和实时
WebSocket 使用同一套 Host 信任策略。服务不会根据反向代理请求头推断公开地址。
Launch root 的信任边界
server-path 请求只能选择公开 root 别名和严格的相对 POSIX 路径,客户端不能提交可被
接受的主机绝对路径。空片段、.、..、盘符前缀、反斜杠、符号链接、非普通文件和
越出 root 的路径都会被拒绝。服务通过 root 目录文件描述符逐段执行 no-follow 打开,
并在编译期间固定源目录,因此替换路径不能把读取重定向到 root 之外。
--launch-root 授权的是可信 FastSim Project 树中的配置选择,并不是对完整资产图的
sandbox。配置仍可使用 Project 的 registry 和 trusted_roots、已安装组件包,以及
带 digest 的 HTTPS 资源。因此 launch-root 目录及其 Project 元数据必须由服务运维者
控制,不能允许不受信任用户写入。
数据边界与错误
- 请求体默认最大 4 MiB,并在 FastAPI 解析前检查;GET 和 DELETE 禁止携带请求体。
- 可用的上传内容上限由
GET /api/v1/launch返回。该值小于请求体上限,因为准入会 同时计算 JSON 外层和最坏转义开销。请求体上限过小时,上传会关闭,但 server-path 仍可使用。 - 请求体接收、公开查询、geometry lease 操作和控制分别有独立 deadline;查询超时
返回
504 query_timeout。 - FastSim 延迟启动默认独立限制为 120 秒;超时会取消打开尝试,并返回
504 application_open_timeout。 - 累计请求体缓冲和二进制响应并发各自有硬上限;饱和时立即返回
429。 - 可移植 JSON 总大小最大 16 MiB,单个 UTF-8 字符串最大 1 MiB。
- 每个筛选字段最多 256 个值。
- operation 每页最多 200 条;保留历史有容量上限并会过期。
- 几何范围读取和粒子流体 payload 最大 64 MiB。
- 全局 HTTP 并发有硬上限;饱和时返回
429和request_capacity_exhausted。 - 应用构建运行在 FastSim 进程中,其中可能包含后端不可中断的编译器。进程退出时, Server 会等待已经启动的 factory 返回,关闭被放弃的应用,再释放上传临时目录。 这可以避免所有权泄漏;但有缺陷且永不返回的第三方 factory 可能拖延 SIGTERM, 部署时应使用带最终强制结束期限的外部进程守护器。
所有错误均使用 fastsim-http-error/1,包含稳定错误码、有界消息、retryable 标记
和 request ID。后端 traceback、失败原文、路径、插件私有配置、binding 与 provider
诊断不会返回。HTTP 只允许查询单个插件状态,不单独启停插件;插件生命周期仍由
FastSimApplication 统一管理,以保证依赖顺序和清理过程确定。
嵌入 Python 应用
import fastsim
from fastsim_plugin_server import ServerSettings, create_http_app
simulation = fastsim.app("run.yaml", planning_reads=True)
http_application = create_http_app(
simulation,
settings=ServerSettings(
planning_reads_enabled=True,
application_open_timeout_s=120.0,
browser_origins=("http://127.0.0.1:4173",),
),
)
HTTP 层只支持上文定义的有界单文档启动,不提供任意文件系统访问、多文件资产上传、 任意 Python 执行、后端原生句柄、pick/place 等语义动作,也不提供无上限的持续订阅。 这些能力应由可信 Run 目录、FastSim 插件或独立可信控制面负责。