在 A2UI 客户端中嵌入 Web Frame 游戏:Pong Web Server 的搭建与桥接机制全解析
2026/9/15 14:23:00 网站建设 项目流程

在 A2UI 客户端中嵌入 Web Frame 游戏:Pong Web Server 的搭建与桥接机制全解析

【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui

本文围绕 A2UI 仓库中的 Pong Web Server 示例(samples/community/web/pong/README.md)展开,深入讲解如何用一个轻量 Python HTTP 服务器,把基于 Canvas 的 Pong 游戏以标准 Web 应用形式托管,并通过pong_web_frame_bridge.js桥接脚本安全地嵌入 A2UI 客户端的 Web Frame 组件。读完本文,你将掌握该服务器的完整启动流程、动态页面组装原理、CORS 配置细节,以及 Web Frame 与宿主之间基于MessagePort的通信协议与安全要点。

这个包是做什么用的?

samples/community/web/pong/目录下存放的是一个基于标准库http.server实现的 Python HTTP 服务器(pong_server.py),它的作用有两层:

  1. 作为静态文件服务器,把本地目录下的资源直接提供给浏览器;
  2. 作为动态装配器,在请求特定路径时,把共享的 Pong 页面模板、游戏引擎脚本和本地桥接脚本拼接成一份完整的 Web Frame HTML 页面。

该服务器的直接目的,是托管一个准备以 iframe(即 A2UI 的 Web Frame 组件)形式嵌入 A2UI 客户端的 Pong 游戏

这里需要特别区分两种 Pong 形态,它们是理解本示例的关键:

形态通信方式说明
MCP App 版 Pong通过 A2UI Agent 协议 / MCP(window.postMessage+ JSON-RPC)由代理端经 MCP Server 下发,属于 samples/community/agent/adk/mcp_app_proxy 示例体系
Web Frame 版 Pong(本文)标准 HTTP Web 应用 + 桥接脚本回连 Web Frame 环境由本文的 Pong Web Server 提供,页面通过pong_web_frame_bridge.js与宿主环境建立连接

换句话说,本服务器不直接参与 A2UI 的 Agent 协议解析,而是把一个普通的网页游戏WebAppFrameUrl/WebAppFrameSrcdoc两种方式暴露给宿主客户端,让 A2UI 客户端只需一个 URL(或一段 srcdoc 内容)即可完成内嵌。

目录结构与关键文件

samples/community/web/pong/ ├── README.md # 使用说明(本文主题文档) ├── __main__.py # 支持 `uv run .` 的入口 ├── pong_server.py # HTTP 服务器核心实现 ├── pong_web_frame_bridge.js# Web Frame 桥接层(本地注入脚本) ├── pyproject.toml # 项目元数据与命令入口声明 └── uv.lock # uv 依赖锁文件

各文件职责:

  • pong_server.py:核心服务器,继承http.server.SimpleHTTPRequestHandler,拦截两个专用路径并动态组装页面,其余路径走默认静态文件逻辑。
  • pong_web_frame_bridge.js:Web Frame 通信层,负责与宿主(A2UI 客户端)握手、收发动作、同步数据模型与函数调用结果。
  • main.py:调用pong_server.main(),使uv run .可以直接启动。
  • pyproject.toml:声明项目名为a2ui-pong-server、要求 Python>=3.10,并提供pong-server = "pong_server:main"控制台脚本入口,因此安装后也可直接执行pong-server命令。

服务器动态组装所需的共享素材并不在本目录,而是位于 samples/community/agent/adk/mcp_app_proxy(即原文档所述samples/agent/adk/mcp_app_proxy/目录在实际仓库中的路径),包含:

  • pong_base.html:Neon Pong 页面模板,内含<canvas id="pong">、开始/暂停按钮、样式表以及两处占位注释// {{BRIDGE_SCRIPT}}// {{ENGINE_SCRIPT}}
  • pong_engine.js:游戏引擎与渲染逻辑,管理球、球拍、碰撞物理与 Canvas 渲染循环,并对外暴露startGametogglePauseisPausedresizehideOverlayresetBall等供桥接层调用的全局能力。

从源码结构看,这种"模板 + 引擎共享、桥接层按宿主形态替换"的设计,让同一款 Pong 游戏既能以 MCP App 形态运行,也能以 Web Frame 形态运行,桥接层是唯一的差异点。

快速启动:三行命令跑起 Web Frame 版 Pong

启动服务器只需uv,无需手动安装依赖。在原目录下执行:

uv run .

也可以从仓库根目录直接执行:

uv run samples/community/web/pong

启动成功后终端会输出:

Serving at port 8081

此时服务器在本机8081端口持续运行,直到在终端按Ctrl+C停止。

启动后,A2UI 客户端可按如下地址接入:

  • http://localhost:8081/pong_app_web_frame.html—— 供WebAppFrameUrl使用(URL 模式,页面标题徽标显示 "🌐 Embedded Web App (URL)");
  • http://localhost:8081/pong_app_web_frame_srcdoc.html—— 供WebAppFrameSrcdoc使用,该页面内容会由 Agent 远程抓取后作为 srcdoc 注入 iframe(显示 "📦 Embedded Web App (Srcdoc)")。

值得注意的是,pyproject.toml 还声明了pong-server控制台脚本,因此若将该包安装进环境,也可以直接以pong-server命令启动服务器,效果等价。

启动入口的源码细节

main.py 的逻辑极简:导入pong_server.mainsys.exit(main())。而 pong_server.py 的main()设置了socketserver.TCPServer.allow_reuse_address = True(允许端口快速复用,便于开发调试时反复重启),随后绑定("", PORT)(监听所有网卡接口)、打印Serving at port 8081并进入serve_forever()事件循环。默认端口常量定义在文件顶部:PORT = 8081

服务器核心逻辑:请求如何被动态组装

pong_server.py的关键在于对SimpleHTTPRequestHandler.do_GET的重写(pong_server.py),其处理流程如下:

  1. 路径识别:用urllib.parse.urlparse解析请求路径,若命中PONG_APP_PATHS(即/pong_app_web_frame.html/pong_app_web_frame_srcdoc.html两个常量),进入动态组装分支。
  2. 响应头:返回200,声明Content-type: text/html,并显式附加 CORS 头Access-Control-Allow-Origin: http://localhost:4200localhost:4200是 Angular 开发服务器的默认端口,即示例客户端常跑的地址)。
  3. 读取三份素材
    • samples/community/agent/adk/mcp_app_proxy/读取共享的pong_base.html
    • 从本目录读取本地桥接脚本pong_web_frame_bridge.js
    • 从共享目录读取游戏引擎pong_engine.js
  4. 占位符替换:把pong_base.html中的// {{BRIDGE_SCRIPT}}替换为桥接脚本内容、// {{ENGINE_SCRIPT}}替换为引擎脚本内容,从而完成"模板 + 引擎 + 本地桥接"的页面装配。
  5. 模式区分:若请求的是 srcdoc 路径,把页面徽标文本 "🔌 Embedded MCP App" 替换为 "📦 Embedded Web App (Srcdoc)";URL 模式则替换为 "🌐 Embedded Web App (URL)",方便肉眼区分当前页面形态。
  6. 写出响应:将最终 HTML 以 UTF-8 编码写入响应流。

对于其他路径,则回退到super().do_GET()走标准静态文件服务。此外,end_headers()也被重写(pong_server.py):对所有非专用路径的响应也统一追加Access-Control-Allow-Origin: http://localhost:4200,确保被 iframe 远程引用时浏览器跨域访问不受阻。

从实现看,这种"占位符注入"的方式让页面模板保持单一来源:引擎逻辑与样式完全共享,服务器只负责按宿主形态注入不同的通信桥,改动成本被控制在最小范围。

桥接层深度剖析:Web Frame 如何与宿主安全通信

pong_web_frame_bridge.js是整个 Web Frame 方案的灵魂。它在pong_base.html<head>中注入(替换{{BRIDGE_SCRIPT}}占位符),在引擎脚本之前执行,因此可以先行注册全局通信能力。

安全模型:origin 校验与 targetOrigin

脚本开头的注释即点明了安全原则:为防止跨站消息被截获,调用window.parent.postMessage()时必须显式指定targetOrigin。其取值逻辑(pong_web_frame_bridge.js)如下:

  1. 优先读取 URL 查询参数?origin=(由 A2UI 宿主在加载 iframe 时提供),作为父页面源;
  2. 若没有origin参数且当前处于 srcdoc/sandbox 模式(window.location.origin === 'null'、协议为about:data:),则回退为'*'并打印提示日志;
  3. 若在 URL 模式下缺失origin参数,则输出错误日志并返回'null'拒绝向'*'广播 postMessage——这是刻意为之的 fail-closed 行为。

同时,初始握手的入站消息也会校验event.origin是否等于PARENT_ORIGIN'*'除外),不匹配的消息直接忽略(pong_web_frame_bridge.js),防止恶意父页面注入伪造消息。

握手协议:从 window 消息到 MessagePort 通道

桥接层定义了一组以a2ui_前缀命名的消息类型常量:

  • a2ui_action/a2ui_data_model_change/a2ui_function_call/a2ui_function_result:出站消息,方向为 Web Frame → 宿主;
  • a2ui_app_frame_init/a2ui_host_context_update/a2ui_data_model_update:入站消息,方向为宿主 → Web Frame;
  • a2ui_app_frame_ready:页面加载完成后立即向父窗口发送的就绪信号。

完整流程为:

  1. 脚本加载即执行window.parent.postMessage({type: MSG_TYPE_APP_FRAME_READY}, PARENT_ORIGIN),向宿主宣告"Web Frame 已就绪";
  2. 宿主收到就绪信号后,通过window.postMessage回传a2ui_app_frame_init,并携带一个MessagePort(位于event.ports[0]);
  3. 桥接层调用configureAppPort(port)(pong_web_frame_bridge.js):关闭旧端口、保存新端口、移除 window 层监听,此后所有通信都走MessagePort——注释明确说明"建立 MessagePort 后,端口上的后续消息无需再做 origin 校验",因为通道本身已经建立在一对一可信连接之上;
  4. 若宿主未提供 MessagePort,则抛出A2UI Protocol Violation错误并说明"ambient postMessage 将被宿主忽略"。

这种"先经 window 握手、再切换至 MessagePort"的两阶段模式,兼顾了初始鉴权的必要性持续通信的性能/安全收益

数据模型同步与游戏状态联动

桥接层维护了localPlayerScore/localCpuScore两份本地分数,并通过两类回调与宿主双向同步:

  • applyInitialPayload(data):在a2ui_app_frame_init时解析初始负载,包括config.matchingScore(获胜分数,覆盖引擎默认的DEFAULT_WINNING_SCORE = 3)、initialData.state(初始双方比分)以及hostContext.containerDimensions(宿主容器尺寸);
  • handleDataModelUpdate(data):接收a2ui_data_model_update,按key === 'state'subpath/player_score/cpu_score)或整包 value 更新本地分数。

其中有个精巧的联动逻辑(pong_web_frame_bridge.js):当检测到双方比分同时归零时,自动解除暂停、隐藏遮罩、复位小球,并派发a2ui_action动作commentate_pong(携带game_event: 'Match started! ...'silent: true)——即宿主代理侧通过该动作感知比赛重启事件。

出站消息封装

桥接层向宿主暴露三类出站原语:

  • dispatchAction(action, data):派发a2ui_action,对应 MCP 语义中的tools/call(见sendRequestmethod === 'tools/call'的分发),供游戏引擎触发宿主侧工具(如评论解说);
  • dispatchDataModelChange(key, subpath, value):派发a2ui_data_model_change,对应ui/notifications/data-model-change
  • dispatchFunctionCall(call, args):派发a2ui_function_call,返回一个 Promise,通过监听端口上的a2ui_function_result(按callId匹配、status === 'success'判定)异步等待宿主函数执行结果,对应ui/requests/function-call

functionCallId自增计数保证多个并发函数调用互不串扰;dispatchFunctionCall内还刻意捕获了端口局部引用,避免挂起期间全局appPort被重新赋值导致监听器绑错实例。

容器尺寸适配

applyContainerDimensions会把宿主下发的宽高直接写到document.documentElementdocument.body的样式上,并调用引擎的resize()(引擎据此按600x400参考尺寸重新计算缩放系数与球拍、小球尺寸)。这一机制配合 pong_engine.js 的响应式渲染,让嵌入页面可以跟随宿主布局动态伸缩。

与 MCP App 版桥接层的对比

把 pong_web_frame_bridge.js 与 MCP 版的 pong_mcp_bridge.js 对照阅读,可以清晰看出两套体系在同一款游戏上的差异:

维度MCP App 版(pong_mcp_bridge.jsWeb Frame 版(pong_web_frame_bridge.js
协议形态完整 JSON-RPC 2.0(jsonrpc: '2.0'idmethodparams精简的自定义a2ui_*消息类型
初始化发起ui/initialize并携带appInfoappCapabilitiesprotocolVersion,再发ui/notifications/initializeda2ui_app_frame_ready等待宿主a2ui_app_frame_init下发 MessagePort
通道全程window.postMessage(发送目标为'*'握手后切换到MessagePort通道
宿主来源代理侧 MCP ServerA2UI 客户端宿主页面

从源码结构可以推断,Web Frame 版的桥接层剥离了完整 MCP 能力协商,只保留"动作、数据模型、函数调用"三类最小交互面,这正是嵌入式 Web 应用场景下的轻量取舍。

安全注意事项与调试建议

  1. 显式 targetOrigin 是硬性要求:URL 模式下缺失?origin=参数时,桥接层会拒绝通信(返回'null')。A2UI 客户端在加载 iframe 时必须正确追加该参数。
  2. CORS 白名单是示例配置:服务器将Access-Control-Allow-Origin硬编码为http://localhost:4200(Angular 开发服务器默认端口)。若你的 A2UI 客户端运行在其他地址(如 React 的 5173、自建域名),需要按实际场景调整 pong_server.py 中的该响应头。
  3. srcdoc 模式的安全回退:srcdoc/sandbox 环境下无法获知父页面源,桥接层只能退化为'*';此时应依赖宿主侧的 sandbox 属性与严格的内容来源控制来兜底。
  4. 代理端安全立场:正如 samples/community/agent/adk/mcp_app_proxy/README.md 强调的,任何来自外部 Agent 的 UI 定义与数据流都应视为不可信输入,嵌入的 iframe/web view 内容必须严格沙箱化,防止恶意外部站点(如 XSS、钓鱼、DoS 型布局攻击)借道注入。
  5. 验证启动效果:浏览器直接访问http://localhost:8081/pong_app_web_frame.html可看到 Neon Pong 页面;若控制台出现Missing required "?origin=" parameter in URL mode类日志,说明宿主未正确携带 origin 参数,页面将拒绝向父窗口广播消息。

小结

Pong Web Server 示例演示了一条非常实用的嵌入路径:用标准 HTTP 服务器托管普通网页游戏,通过一个小巧的桥接层把它接入 A2UI 的 Web Frame 组件。它完整覆盖了静态服务、模板动态装配、CORS 配置、origin 安全校验、MessagePort 握手以及数据模型双向同步等关键环节,是理解 A2UI Web Frame 能力与嵌入式 Web 应用接入方式的最小可运行范本。相关实现可直接在 samples/community/web/pong 与 samples/community/agent/adk/mcp_app_proxy 两个目录中继续研读。

【免费下载链接】a2ui项目地址: https://gitcode.com/GitHub_Trending/a2/a2ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询