HTTP 传输与 FastSim Core 分离。不开启该服务时,不会引入 HTTP、序列化、网络或 逐物理帧开销。服务本身不实现仿真逻辑,也不直接导入任何仿真器后端。

从当前仓库安装

请使用 FastSim 所在的同一个 Python 环境:

bash
python -m pip install ./packages/fastsim-plugin-server
fastsim-server --help

使用浏览器工作台的实时相机画面时,请安装可选 WebRTC 依赖:

bash
python -m pip install './packages/fastsim-plugin-server[webrtc]'

远程客户端或 Agent 需要 IK 时安装可选 Kinematics 服务:

bash
python -m pip install './packages/fastsim-plugin-server[kinematics]'

可用下面的命令确认可选 Provider 与软件视频编码器均可正常使用:

bash
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

bash
fastsim-server run.yaml

默认地址为 http://127.0.0.1:8000。进程固定使用一个 Uvicorn worker 和一个 FastSim 应用事件循环,不提供 reload 和多 worker,避免多个进程同时占用同一个 仿真 Run。 未指定 --launch-root 时,这种 positional 形式保持原有的不可替换 Run 语义;显式 添加 launch root 才会把初始 Run 放入可替换的常驻槽位。

场景、坐标系和规划几何信息默认不发布,需要显式开启:

bash
fastsim-server run.yaml --planning-reads

常驻启动服务

独立部署的 Web Client 可以连接一个启动时没有初始 Run 的常驻服务。服务运维者只 开放命名后的配置目录:

bash
fastsim-server \
  --launch-root examples=/srv/fastsim/runs \
  --browser-origin http://127.0.0.1:8080

GET /api/v1/launch 返回槽位状态、generation、公开 root 别名、允许的格式和准确的 上传字节上限。客户端可从白名单目录启动配置:

bash
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 文本配置:

bash
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 默认是 runningprepared 会进入并准备 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 文档中。

启动后先查看接口发现:

bash
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 变为 succeededfailedcancelled

场景命令不同:它们直接通过公开 FastSimApplication.scene facade 结算,并返回 Core 的 fastsim-scene-command-result/1。Server 不会另建命令队列、拖拽求解器或 幂等缓存。

bash
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 不要求该请求头。

bash
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 时也可以读取:

json
{
  "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 显存为 nullgpu_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=64max_item_bytes=1048576overload=drop_oldest。最后一个订阅关闭、slot generation 改变、launch 失败或 Run 被 terminal stop/close 时,Core handle 与 sampler 都会释放。 worker 每次采样最多投影一个按序且有大小上限的可靠批次,频率不超过 30 Hz;超出部分 留在有界 Core 队列中,其 drop_oldest 丢失账本会作为显式 gap 发布。Server transport 不为实际控制帧保留历史:重连 resume 会收到带 history_unavailablestream.reset, 而不会触发可能达到数 MiB 的可靠 replay 洪峰。

stream.snapshot 内的 value 具有以下稳定形状:

json
{
  "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_countbyte_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 及其他特殊 文件均不会暴露。

bash
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。每一种生命周期转换都会先关闭新的数据面 读取入口,并在有界时间内等待已接纳的读取退出,再操作仿真器。resetstopclose 还会取消并彻底排空控制 operation,包括底层 release;活动控制期间仍可 使用 pauseresumestep

原始数据面读取仅在生命周期稳定处于 preparedrunningpaused 时准入。 应用快照、插件状态与事件、状态/控制查询、相机/流体帧和规划读取,在冷启动、状态 转换、stoppedclosedfailed 时统一快速返回 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 分页列表,可按 statekind 筛选
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_mquat_xyzw;可选 Group、 容差、超时、碰撞模式、Seed 和解的数量。FastSim 负责当前状态捕获、模型选择、 Generation 围栏与 Provider 执行,HTTP 层只负责验证和传输公开结果。随附 Portable Solver 是局部 DLS,不宣称提供碰撞感知全局规划。

目录接口支持 offsetlimit;坐标变换还可指定不可变 generation

场景命令只操作本次 Run 已经加载的实体,不负责生成资产、求解 IK 或增加语义。 最短的位姿命令如下:

bash
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

朝向默认是单位四元数。generationcommand_id 都可以省略:省略时由 Core 在 提交时绑定或生成;高级调用方可以显式提供,从而获得 generation 栅栏和精确重试 幂等性。HTTP Idempotency-Key 只是 command_id 的另一种写法;两者同时存在时 必须相同。可替换 Server 还需要独立的 X-FastSim-Slot-Generation 请求头。Core 返回 rejected 仍然是 HTTP 200 的正常结算,并带有 status: "rejected";服务 边界失败则保留 Core 错误码以及 command、entity、generation 详情。

筛选字段使用重复 query 参数,并取各条件的交集:

bash
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=... 请求头,也可使用 兼容的 offsetlength 参数读取最大 64 MiB;两种方式不能混用:

bash
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 路径不做 图像编码。

bash
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 或更严格:

bash
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 需要显式配置一个或多个精确 来源:

bash
fastsim-server run.yaml \
  --browser-origin http://127.0.0.1:4173 \
  --browser-origin https://console.example

每个值只能包含 httphttps、主机和可选端口。通配符、null、userinfo、路径、 query 和 fragment 都会被拒绝;省略默认端口和显式写出默认端口语义相同。非 loopback 前端必须使用 HTTPS,整个 allowlist 最多 64 项。浏览器用 Authorization: Bearer ... 传 token,并使用 credentials: "omit";服务不会启用 cookie credentials。

合法的 OPTIONS 预检会在 bearer 鉴权、请求体接收和 FastAPI/应用分发之前完成。 预检只接受真实 API 路由的方法,以及有上限的 AcceptAuthorizationContent-TypeIdempotency-KeyRangeX-FastSim-Slot-GenerationX-Request-Id 请求头。实际浏览器请求 仍要经过 bearer 鉴权和全部常规 API 限制。允许的响应会暴露 LocationRetry-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-sitecross-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,例如:

bash
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 并发有硬上限;饱和时返回 429request_capacity_exhausted
  • 应用构建运行在 FastSim 进程中,其中可能包含后端不可中断的编译器。进程退出时, Server 会等待已经启动的 factory 返回,关闭被放弃的应用,再释放上传临时目录。 这可以避免所有权泄漏;但有缺陷且永不返回的第三方 factory 可能拖延 SIGTERM, 部署时应使用带最终强制结束期限的外部进程守护器。

所有错误均使用 fastsim-http-error/1,包含稳定错误码、有界消息、retryable 标记 和 request ID。后端 traceback、失败原文、路径、插件私有配置、binding 与 provider 诊断不会返回。HTTP 只允许查询单个插件状态,不单独启停插件;插件生命周期仍由 FastSimApplication 统一管理,以保证依赖顺序和清理过程确定。

嵌入 Python 应用

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 插件或独立可信控制面负责。