简介
当你需要在前端页面直接调用某个API,却发现服务端不支持跨域,而你既无法控制服务端又不想额外购置服务器时,该怎么办?本文介绍一种利用 Cloudflare Workers 搭建代理转发层的实战方案。
这个方案的典型应用场景是:我希望将部署在 Coze(扣子)平台上的工作流,集成到我自己部署于 GitHub Pages 的静态网站中。由于 Coze 的 API 存在跨域限制,直接从前端调用会失败。而 Cloudflare Workers 的免费额度(每日10万次请求)和提供的免费子域名,恰好能提供一个零成本、无需服务器的代理解决方案。
核心优势:
- 免费:Cloudflare Workers 提供免费套餐。
- 无需服务器:完全基于 Serverless 函数,省去运维成本。
- 配置简单:主要工作就是编写一段 JavaScript 逻辑。
需要注意:
- 访问 Cloudflare Workers 服务可能需要具备相应的网络条件。
- 以下教程将以代理 Coze 工作流 API 为例进行说明。
准备工作
- 注册 Cloudflare 账户:访问 Cloudflare 官网,使用邮箱、Google、GitHub 或 Apple 账号注册登录。此过程无需任何资质认证或备案。
- 创建 Worker:登录后,控制台导航至
计算和 AI -> Workers 和 Pages -> 创建应用程序 -> 选择 从 Hello World! 开始,为你的 Worker 命名并创建。
- 编辑代码:创建成功后,进入该 Worker 的管理页面,点击右上角的
编辑代码 按钮,准备编写代理逻辑。
理解请求拦截机制
Cloudflare Worker 的核心是一个监听请求并返回响应的函数。当你的 Worker 收到 HTTP 请求时,它会自动调用默认导出的 fetch 函数。我们只需要在这个函数里编写转发逻辑即可。
基本模板如下,其中包含三个参数:
request: Fetch API 的 Request 对象,包含了请求的 URL、方法、请求头和请求体等信息。
env: 绑定到当前 Worker 的环境变量。我们可以将 Coze 的 API 地址和鉴权 Token 等敏感信息存放在这里,通过 env 对象访问,避免硬编码在代码中。
ctx: 执行上下文,本文示例未使用,有需要的开发者可自行查阅相关文档。
export default {
async fetch(request, env, ctx) {
return handleRequest(request, env);
}
};
实现代理转发逻辑
接下来我们实现核心的 handleRequest 函数。它的职责是:接收来自前端的请求,将其转发至 Coze API,并将 Coze 的响应原样返回给前端,同时处理好跨域(CORS)问题。
步骤分解与代码实现
1. 设置 CORS 响应头
首先定义一组跨域头,以便后续在响应中复用。
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, token',
};
2. 处理 OPTIONS 预检请求
浏览器在发送跨域请求前,会先发送一个 OPTIONS 方法的预检请求。我们需要正确响应它。
// 处理预检请求(OPTIONS)
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
...corsHeaders,
'Access-Control-Max-Age': '86400'
}
});
}
3. 限定请求方法
Coze 工作流调用通常使用 POST 方法,这里我们只允许 POST 请求,其他方法返回 405 错误。
// 只允许 POST 请求(Coze 工作流使用 POST)
if (request.method !== 'POST') {
return new Response(JSON.stringify({ error: '请求方式不支持,仅支持 POST' }), {
status: 405,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
});
}
4. 读取环境变量并校验
从 env 中读取预先配置的 Coze API 基础地址和 Token。如果未配置,则返回错误。
// Coze API 配置
const COZE_BASE_URL = env.COC_BASE_URL;
const COZE_TOKEN = env.COC_TOKEN;
// 检查环境变量
if (!COZE_BASE_URL || !COZE_TOKEN) {
return new Response(JSON.stringify({
error: '凭证错误',
message: 'COZE_BASE_URL/COZE_TOKEN 环境变量不存在'
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
});
}
(如何配置环境变量?在 Worker 页面点击 设置 -> 变量和机密 -> 编辑变量,添加名称分别为 COZE_BASE_URL 和 COZE_TOKEN 的变量即可。)
5. 构建目标 API URL
解析前端请求的路径和查询参数,与 Coze 的基础地址拼接成完整的目标 URL。此处还提供了可选的路径白名单过滤功能(示例中已注释),用于增强安全性。
// 解析请求 URL,获取路径和查询参数
const url = new URL(request.url);
const path = url.pathname; // 例如: /v1/workflow/run
const searchParams = url.search;
// 安全验证:只允许特定的 Coze API 路径(防止滥用)
const allowedPaths = [
'/v1/workflow/run',
'/v1/bot/chat',
'/v3/chat',
'/v1/conversation/create',
'/v1/conversation/message/create'
];
// 如果路径不在白名单中,返回错误(可选)
/*
if (!allowedPaths.some(allowed => path.startsWith(allowed))) {
return new Response(JSON.stringify({
error: 'Invalid Path',
message: `Path ${path} is not allowed. Allowed paths: ${allowedPaths.join(', ')}`
}), {
status: 403,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
});
}
*/
// 构建完整的 Coze API URL
const apiUrl = `${COZE_BASE_URL}${path}${searchParams}`;
console.log(`Proxying request to: ${apiUrl}`); // 在 Cloudflare Logs 中查看
6. 构建转发请求的 Headers 和 Body
创建一个新的 Headers 对象,首先设置 Coze 所需的 Authorization 头。然后,有选择地将前端请求中的一些关键头(如 Content-Type)复制过来。最后,获取前端请求的 body。
// 构建请求头
const headers = new Headers();
headers.set('Authorization', `Bearer ${COZE_TOKEN}`);
// 保留客户端的关键头部,如果请求头存在 Authorization 则使用请求头中的 Authorization
for (const [key, value] of request.headers.entries()) {
const lowerKey = key.toLowerCase();
if (['Authorization', 'content-type', 'accept', 'user-agent', 'token'].includes(lowerKey)) {
headers.set(key, value);
}
}
// 获取请求体
const requestBody = await request.text();
7. 转发请求并返回响应
使用 fetch 函数将构建好的请求转发至 Coze API。获取到 Coze 的响应后,将其状态、头部和 body 包装成新的 Response 对象返回给前端。切记在返回头中加上我们之前定义的 CORS 头。
// 转发请求
const response = await fetch(apiUrl, {
method: 'POST',
headers: headers,
body: requestBody
});
// 返回响应
const responseBody = await response.text();
return new Response(responseBody, {
status: response.status,
statusText: response.statusText,
headers: {
'Content-Type': response.headers.get('Content-Type') || 'application/json',
...corsHeaders
}
});
完整源代码
将上述所有步骤整合,并添加错误处理,得到完整的 Worker 代码如下。你可以直接复制到 Cloudflare Worker 的编辑器中部署。
export default {
async fetch(request, env, ctx) {
return handleRequest(request, env);
}
};
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization, token',
};
async function handleRequest(request, env) {
// 处理预检请求(OPTIONS)
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
...corsHeaders,
'Access-Control-Max-Age': '86400'
}
});
}
// 只允许 POST 请求(Coze 工作流使用 POST)
if (request.method !== 'POST') {
return new Response(JSON.stringify({ error: '请求方式不支持,仅支持 POST' }), {
status: 405,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
});
}
// Coze API 配置
const COZE_BASE_URL = env.COC_BASE_URL;
const COZE_TOKEN = env.COC_TOKEN;
// 检查环境变量
if (!COZE_BASE_URL || !COZE_TOKEN) {
return new Response(JSON.stringify({
error: '凭证错误',
message: 'COZE_BASE_URL/COZE_TOKEN 环境变量不存在'
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
});
}
try {
// 解析请求 URL,获取路径和查询参数
const url = new URL(request.url);
const path = url.pathname; // 例如: /v1/workflow/run
const searchParams = url.search;
// 安全验证:只允许特定的 Coze API 路径(防止滥用)
const allowedPaths = [
'/v1/workflow/run',
'/v1/bot/chat',
'/v3/chat',
'/v1/conversation/create',
'/v1/conversation/message/create'
];
// 如果路径不在白名单中,返回错误(可选)
/*
if (!allowedPaths.some(allowed => path.startsWith(allowed))) {
return new Response(JSON.stringify({
error: 'Invalid Path',
message: `Path ${path} is not allowed. Allowed paths: ${allowedPaths.join(', ')}`
}), {
status: 403,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
});
}
*/
// 构建完整的 Coze API URL
const apiUrl = `${COZE_BASE_URL}${path}${searchParams}`;
console.log(`Proxying request to: ${apiUrl}`); // 在 Cloudflare Logs 中查看
// 构建请求头
const headers = new Headers();
headers.set('Authorization', `Bearer ${COZE_TOKEN}`);
// 保留客户端的关键头部,如果请求头存在 Authorization 则使用请求头中的 Authorization
for (const [key, value] of request.headers.entries()) {
const lowerKey = key.toLowerCase();
if (['Authorization', 'content-type', 'accept', 'user-agent', 'token'].includes(lowerKey)) {
headers.set(key, value);
}
}
// 获取请求体
const requestBody = await request.text();
// 转发请求
const response = await fetch(apiUrl, {
method: 'POST',
headers: headers,
body: requestBody
});
// 返回响应
const responseBody = await response.text();
return new Response(responseBody, {
status: response.status,
statusText: response.statusText,
headers: {
'Content-Type': response.headers.get('Content-Type') || 'application/json',
...corsHeaders
}
});
} catch (error) {
console.error('Proxy error:', error);
return new Response(JSON.stringify({
error: 'Proxy error',
message: error.message
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
...corsHeaders
}
});
}
}
部署与使用
- 将上述代码粘贴到 Worker 编辑器,点击
保存并部署。
- 在
设置 -> 变量和机密 中配置好 COZE_BASE_URL (例如:https://api.coze.com) 和 COZE_TOKEN。
- 部署成功后,你会获得一个类似
https://your-worker-name.youraccount.workers.dev 的访问地址。
- 在你的前端代码中,原本直接请求
https://api.coze.com/v1/workflow/run 的地方,现在改为请求 https://your-worker-name.youraccount.workers.dev/v1/workflow/run 即可。Worker 会自动帮你完成鉴权、转发和跨域处理。
结语
通过 Cloudflare Workers,我们轻松搭建了一个安全、免费的 API 代理网关,完美解决了静态网站调用第三方 API 时的跨域难题。这个模式不仅适用于 Coze,也可以扩展到其他任何不支持跨域或需要统一鉴权的外部服务,为前端开发者提供了极大的灵活性。如果你在探索更多无服务器架构或前端工程化实践,欢迎到 云栈社区 与其他开发者交流分享。