找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3106

积分

0

好友

423

主题
发表于 9 小时前 | 查看: 1| 回复: 0

简介

当你需要在前端页面直接调用某个API,却发现服务端不支持跨域,而你既无法控制服务端又不想额外购置服务器时,该怎么办?本文介绍一种利用 Cloudflare Workers 搭建代理转发层的实战方案。

这个方案的典型应用场景是:我希望将部署在 Coze(扣子)平台上的工作流,集成到我自己部署于 GitHub Pages 的静态网站中。由于 Coze 的 API 存在跨域限制,直接从前端调用会失败。而 Cloudflare Workers 的免费额度(每日10万次请求)和提供的免费子域名,恰好能提供一个零成本、无需服务器的代理解决方案。

核心优势:

  • 免费:Cloudflare Workers 提供免费套餐。
  • 无需服务器:完全基于 Serverless 函数,省去运维成本。
  • 配置简单:主要工作就是编写一段 JavaScript 逻辑。

需要注意:

  • 访问 Cloudflare Workers 服务可能需要具备相应的网络条件。
  • 以下教程将以代理 Coze 工作流 API 为例进行说明。

准备工作

  1. 注册 Cloudflare 账户:访问 Cloudflare 官网,使用邮箱、Google、GitHub 或 Apple 账号注册登录。此过程无需任何资质认证或备案。
  2. 创建 Worker:登录后,控制台导航至 计算和 AI -> Workers 和 Pages -> 创建应用程序 -> 选择 从 Hello World! 开始,为你的 Worker 命名并创建。
  3. 编辑代码:创建成功后,进入该 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_URLCOZE_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
      }
    });
  }
}

部署与使用

  1. 将上述代码粘贴到 Worker 编辑器,点击 保存并部署
  2. 设置 -> 变量和机密 中配置好 COZE_BASE_URL (例如:https://api.coze.com) 和 COZE_TOKEN
  3. 部署成功后,你会获得一个类似 https://your-worker-name.youraccount.workers.dev 的访问地址。
  4. 在你的前端代码中,原本直接请求 https://api.coze.com/v1/workflow/run 的地方,现在改为请求 https://your-worker-name.youraccount.workers.dev/v1/workflow/run 即可。Worker 会自动帮你完成鉴权、转发和跨域处理。

结语

通过 Cloudflare Workers,我们轻松搭建了一个安全、免费的 API 代理网关,完美解决了静态网站调用第三方 API 时的跨域难题。这个模式不仅适用于 Coze,也可以扩展到其他任何不支持跨域或需要统一鉴权的外部服务,为前端开发者提供了极大的灵活性。如果你在探索更多无服务器架构或前端工程化实践,欢迎到 云栈社区 与其他开发者交流分享。




上一篇:iOS呼叫筛选功能与数字素养:从华尔街精英误操作看技术理解断层
下一篇:深入解析Go语言panic机制:从源码到recover的异常处理实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-9 20:53 , Processed in 0.393888 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表