Cloudflare Agents 跨域通信实战:React 客户端与 Worker Agent 的 WebSocket/HTTP 完整接入指南
【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents
跨域(Cross-Domain)是 Web 前端接入 Agent 后端时最常踩的坑:浏览器出于同源策略,会拦截来自不同端口、不同域名下的 WebSocket 升级请求与 HTTP 响应,导致 Agent 消息"发出去却收不到"。本指南以 examples/cross-domain 为例,讲解如何在 React 客户端与 Cloudflare Worker Agent 运行在不同域时,打通 WebSocket 实时通信与 HTTP 请求——核心结论只有一句话:务必给routeAgentRequest传入cors: true。读完本文,你将掌握跨域场景下 Worker 端 CORS 配置、鉴权中间件接入,以及前端useAgent的静态与异步两种鉴权连接方式。
示例概览:一前端一 Worker 的跨域拓扑
示例由两个独立进程组成,各自运行在不同的"域"(本例为不同的本地端口,生产环境则对应不同的域名):
- React 客户端:Vite 开发服务器,默认运行在
http://localhost:5173,页面代码见 src/client.tsx; - Worker 服务端:Wrangler 本地开发服务器,运行在
http://localhost:8787,Agent 实现见 src/server.ts。
浏览器将5173与8787视为两个不同的源(origin),这正是触发 CORS 与 WebSocket 握手校验的典型场景。客户端的host明确指向 Worker 地址,其实现也在 UI 中直接展示了两端拓扑(见 src/client.tsx):
Client: http://localhost:5173(当前页面) Server: http://localhost:8787(不同端口)一键启动两端的方式来自 package.json 的start脚本,用concurrently同时拉起 Vite 与 Wrangler,并保证任一进程退出时杀掉另一个:
npm i && npm start等价于分别执行:
# 终端 A:前端开发服务器 npx vite dev # 终端 B:Agent Worker 本地开发服务器 npx wrangler dev服务端:cors: true 与 CORS 预检处理
routeAgentRequest 的 cors 选项
README 的 tl;dr 指出,跨域能否成功,关键在于调用routeAgentRequest时传入cors: true。routeAgentRequest是agents包提供的核心路由函数,用于把进入 Worker 的请求分发给对应的 Agent(Durable Object)。其cors选项支持boolean | HeadersInit两种形态,从 packages/agents/src/agent-routing.ts 的类型定义可见:
cors: true:自动启用一组默认 CORS 响应头;cors: HeadersInit:传入自定义响应头,覆盖默认值,适合需要指定具体来源、携带凭据的场景;- 不传(或
false):不添加任何 CORS 头,跨域浏览器请求将失败。
当传入true时,默认解析出的响应头如下(见 packages/agents/src/agent-routing.ts):
| 响应头 | 值 |
|---|---|
Access-Control-Allow-Origin | * |
Access-Control-Allow-Methods | GET, POST, HEAD, OPTIONS |
Access-Control-Allow-Headers | * |
Access-Control-Max-Age | 86400 |
同时,底层路由逻辑会自动拦截OPTIONS预检请求并直接返回 CORS 头(见 packages/agents/src/agent-routing.ts),并在非 WebSocket 响应上统一附加这些头(见 packages/agents/src/agent-routing.ts)。也就是说,开启cors: true后,预检和实际响应两头都由框架代劳。
自定义 CORS 头与预检处理
示例并未满足于默认配置,而是在 src/server.ts 中定义了更贴近生产的一组响应头:
const CORS_HEADERS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS, PUT, DELETE", "Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key", "Access-Control-Allow-Credentials": "true", "Access-Control-Max-Age": "86400" };相比默认值,这里额外声明了X-API-Key请求头与Access-Control-Allow-Credentials。随后在 Worker 的fetch入口中,先手动处理OPTIONS预检请求,再把其余请求交给routeAgentRequest(见 src/server.ts):
export default { async fetch(request: Request) { // 1. 处理 CORS 预检请求 if (request.method === "OPTIONS") { return new Response(null, { headers: CORS_HEADERS }); } // 2. 路由 Agent 请求并注入鉴权中间件 return ( (await routeAgentRequest(request, env, { cors: true, onBeforeConnect: async (request: Request) => { console.log("🔍 onBeforeConnect called!"); return authMiddleware(request); }, onBeforeRequest: async (request: Request) => { console.log("🔍 onBeforeRequest called!"); return authMiddleware(request); } })) || new Response("Not found", { status: 404 }) ); } };这里值得注意的实践组合是:框架的cors: true负责兜底、自定义的OPTIONS分支负责精细化控制(如允许凭据与自定义头),两者同时启用并不冲突——手动预检返回后,Agent 路由内的 CORS 逻辑自然不会再有OPTIONS进入。
鉴权中间件:WebSocket 与 HTTP 的统一拦截
跨域之外,示例还演示了如何在握手与请求两个阶段统一鉴权。authMiddleware从 URL 查询参数或Authorization请求头提取 token,校验通过则原样返回request放行,失败则返回 401 响应中断流程(见 src/server.ts):
function authMiddleware(request: Request): Response | Request { const url = new URL(request.url); // URL 参数可能进入应用日志,切勿在日志中记录长期有效 token let token: string | null | undefined = url.searchParams.get("token"); if (!token) token = request.headers.get("Authorization")?.substring(7); if (token) { console.log("Token found:", token); // 超级"强"的 token 校验(演示用) if (token === "demo-token-123") { return request; // 放行,继续请求流程 } } // 中断请求,返回 401 console.log("Authentication failed"); return new Response("Unauthorized: Invalid or missing authentication", { status: 401 }); }它通过routeAgentRequest的两个回调接入:
onBeforeConnect:在WebSocket 握手建立之前执行,决定是否允许客户端连接 Agent;onBeforeRequest:在HTTP 请求派发到 Agent 的onRequest之前执行,决定是否允许请求进入。
两个回调的返回契约一致:返回Request表示放行,返回Response表示中断。代码中Authorization的解析request.headers.get("Authorization")?.substring(7)正是提取Bearer <token>中Bearer之后的 token 部分。
Agent 端到端逻辑
鉴权通过后,请求会被路由到MyAgent(Durable Object),其生命周期回调覆盖了连接的完整周期(见 src/server.ts):
onConnect:握手成功后读取 URL 查询参数中的token、userId,向新连接发送欢迎消息;onMessage:收到客户端消息后,回复带时间戳的回执,并向其他所有连接广播Client <id> says: ...(通过this.getConnections()遍历排除自己);onClose:客户端断开时打印日志;onRequest:处理 HTTP 请求,返回"已鉴权"的处理结果文本。
Worker 的 Durable Object 绑定在 wrangler.jsonc 中声明,注意migrations使用的是new_sqlite_classes(SQLite 支持的 Durable Object),并启用了nodejs_compat兼容标志:
{ "compatibility_date": "2026-06-11", "compatibility_flags": ["nodejs_compat"], "durable_objects": { "bindings": [ { "class_name": "MyAgent", "name": "MyAgent" } ] }, "main": "src/server.ts", "migrations": [ { "new_sqlite_classes": ["MyAgent"], "tag": "v1" } ], "name": "cross-domain" }客户端:useAgent 的两种跨域鉴权接入方式
React 端通过agents/react提供的useAgenthook 连接 Worker,页面 index.html 挂载 src/client.tsx。示例在同一个页面里提供了两种鉴权模式的切换(通过顶部 checkbox),并在 ReactSuspense中渲染,加载鉴权数据期间展示 fallback。
静态鉴权:query 传参 + Bearer 头
StaticAuthApp使用对象形式的query,把token与userId作为 WebSocket 握手 URL 的查询参数传给服务端(对应服务端onConnect中从searchParams读取的值),见 src/client.tsx:
const agent = useAgent({ agent: "my-agent", host: "http://localhost:8787", // 跨域指向 Worker query: { token: authToken, // 鉴权 token(demo-token-123) userId: "demo-user" // 服务端校验用的用户标识 }, onMessage: (message) => { /* 收到消息追加到消息列表 */ }, onError: (error) => { console.error("WebSocket auth error:", error); } });UI 上还提供了输入框允许修改 token 并"Update Token"刷新连接。与此同时,HTTP 通道使用fetch直接请求 Agent 的 REST 路径/agents/my-agent/default,并把 token 放进Authorization: Bearer <token>头(见 src/client.tsx)——这正是服务端authMiddleware中"Authorization头 → 取Bearer后缀"那条解析路径的来源。
异步鉴权:async query 自动缓存
生产场景中,token 往往需要先从鉴权服务异步获取。AsyncAuthApp演示了把query写成异步函数的写法:useAgent会自动检测并缓存该函数的返回结果,连接会等到鉴权数据就绪后才发起,见 src/client.tsx:
// 模拟鉴权服务:并发获取 token 与用户信息 const asyncQuery = useCallback(async () => { console.log("🔐 Fetching authentication data..."); const [token, user] = await Promise.all([getAuthToken(), getCurrentUser()]); return { token, userId: user.id, timestamp: Date.now().toString() // 转为字符串以兼容 WebSocket 查询参数 }; }, []); const agent = useAgent({ agent: "my-agent", host: "http://localhost:8787", query: asyncQuery, // 异步函数——自动检测并缓存 onMessage: (message) => { /* ... */ }, onError: (error) => { console.error("WebSocket error:", error); } });这种写法与useAgent的类型定义一致:query既可以是静态的QueryObject,也可以是返回Promise<QueryObject>的函数(见 packages/agents/src/react.tsx),同时配套queryDeps(异步查询缓存依赖)与cacheTtl(毫秒级缓存 TTL,适合时间敏感的 token)。UI 顶部的Suspensefallback("🔐 Loading authentication...")正是在异步查询期间展示的加载态。
端到端效果与验证
启动npm start后,打开 Vite 提供的页面,即可验证两条跨域链路:
- WebSocket 链路:在输入框发送消息 → 服务端
onMessage触发,客户端收到回执Server received "..." at <时间>;若同时开两个浏览器标签,还能看到广播消息Client <id> says: ...; - HTTP 链路:点击 "Send Authenticated HTTP Request" →
fetch携带Bearertoken 请求/agents/my-agent/default→ 服务端onBeforeRequest校验后进入onRequest,客户端收到🔐 Authenticated HTTP request processed ...响应文本。
若去掉cors: true或不处理OPTIONS预检,浏览器控制台将出现典型的 CORS 报错(如Failed to fetch、WebSocket 握手被浏览器拦截)。这也再次印证 README 的核心结论:跨域场景下,cors: true是routeAgentRequest的必选项;而结合自定义 CORS 头、onBeforeConnect/onBeforeRequest鉴权中间件与useAgent的静态/异步query,即可在 React 与 Worker Agent 分域部署时,构建一条完整、安全的实时通信链路。
进一步参考:packages/agents/src/react.tsx(useAgent全部选项)、packages/agents/src/agent-routing.ts(routeAgentRequest的 CORS 与路由实现),以及 examples/agents-as-tools、examples/channels 等其他 Agent 接入示例。
【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考