自建Cloudflare Worker反代教程
简介
本文主要说明如何自建一个基于Cloudflare Worker的API反代服务,实现API原始数据记录+自定义模型重写功能。
由于偶尔会有部分用户想自定义模型名称以便兼容某些行为奇怪的客户端,有些用户想要获得API交互的原始数据以排查错误,但是我们出于兼容性和保护客户隐私的方面考虑,不太愿意在服务端直接对这些功能添加支持,因此我们设计了一个基于CF Worker的小脚本简单实现这些功能的同时,兼顾安全性和隐私。
效果展示:
API模型重写claude-3-haiku=>claude-3-5-haiku,claude-3.5-haiku-8080=>claude-3-5-sonnet
:
API交互原始数据记录:
配置
创建Worker
进入您的Cloudflare Dashboard控制面板
点击Worker & Pages
=> Overveiw
=> Create
创建新的Worker
名字随意,点击创建即可
编辑Worker代码
创建成功后点击编辑代码:
将原有代码清空,然后把下面整段代码全部复制进去,然后点击Deploy
保存并更新部署
/**
* API 代理与日志记录服务
*
* 环境变量配置:
* - API_LOGGING: 设置为 "enabled" 时启用日志记录
* - API_SEC_KEY: API Token,用于访问管理接口
* - API_MODEL_REWRITE: 模型重写规则,格式: "model1=>model2,model3=>model4"
*
* 数据库配置:
* - 需要在 Cloudflare D1 中创建数据库并绑定为 "DB"
*/
// 错误消息模板
const ERROR_MESSAGES = {
DB_NOT_CONFIGURED: {
en: "D1 database is not properly configured. Please check the documentation for setup instructions.",
zh: "D1 数据库未正确配置,请查阅文档进行配置。"
},
DB_TABLE_MISSING: {
en: "Required database table 'api_logs' is missing. Please create the table first.",
zh: "数据库表 'api_logs' 不存在,请先创建数据表。"
},
INVALID_MODEL_REWRITE: {
en: "Invalid model rewrite configuration. Format should be 'model1=>model2,model3=>model4'",
zh: "模型重写配置格式错误,正确格式应为:'model1=>model2,model3=>model4'"
}
};
// 创建错误响应
function createErrorResponse(errorKey) {
const error = ERROR_MESSAGES[errorKey];
return new Response(JSON.stringify({
error: {
en: error.en,
zh: error.zh
}
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
// 解析模型重写规则
function parseModelRewrites(rewriteConfig) {
if (!rewriteConfig) return null;
const rewrites = new Map();
try {
rewriteConfig.split(',').forEach(rule => {
const [from, to] = rule.trim().split('=>');
if (!from || !to) throw new Error('Invalid rule format');
rewrites.set(from.trim(), to.trim());
});
return rewrites;
} catch (error) {
throw new Error(ERROR_MESSAGES.INVALID_MODEL_REWRITE.en);
}
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
// 处理管理接口
if (url.pathname === '/logs' || url.pathname === '/clear-logs') {
return await handleAdminRequest(request, env, url);
}
// API 反向代理
if (url.pathname.startsWith('/v1')) {
return await handleProxyRequest(request, env, url);
}
return new Response('Not Found', { status: 404 });
}
};
async function handleProxyRequest(request, env, url) {
// 克隆请求信息,用于记录和模型重写
const timestamp = new Date().toISOString();
const requestHeaders = Object.fromEntries([...request.headers]);
let requestBody = '';
let modifiedRequest = request;
// 处理请求体和模型重写
if (request.method === 'POST' && request.body) {
const clonedRequest = request.clone();
requestBody = await clonedRequest.text();
// 模型重写处理
if (env.API_MODEL_REWRITE) {
try {
const bodyJson = JSON.parse(requestBody);
const rewrites = parseModelRewrites(env.API_MODEL_REWRITE);
if (bodyJson.model && rewrites?.has(bodyJson.model)) {
const newModel = rewrites.get(bodyJson.model);
bodyJson.model = newModel;
requestHeaders['x-model-rewrite'] = `${bodyJson.model}=>${newModel}`;
requestBody = JSON.stringify(bodyJson);
// 创建新的请求对象
modifiedRequest = new Request(request.url, {
method: request.method,
headers: request.headers,
body: requestBody
});
}
} catch (error) {
return createErrorResponse('INVALID_MODEL_REWRITE');
}
}
}
// 执行反向代理请求
url.host = 'api.ohmygpt.com';
const response = await fetch(url, {
method: modifiedRequest.method,
headers: modifiedRequest.headers,
body: modifiedRequest.body
});
// 如果启用了日志记录,则记录请求和响应
if (env.API_LOGGING === 'enabled') {
if (!env.DB) {
console.error('Database not configured but logging is enabled');
return createErrorResponse('DB_NOT_CONFIGURED');
}
try {
const clonedResponse = response.clone();
const responseBody = await clonedResponse.text();
const responseHeaders = JSON.stringify(Object.fromEntries([...response.headers]));
await env.DB.prepare(`
INSERT INTO api_logs (
timestamp, request_path, request_method, request_headers,
request_body, response_status, response_headers, response_body
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`).bind(
timestamp,
url.pathname,
request.method,
JSON.stringify(requestHeaders),
requestBody,
response.status,
responseHeaders,
responseBody
).run();
} catch (error) {
console.error('Failed to log request:', error);
return createErrorResponse('DB_TABLE_MISSING');
}
}
return response;
}
async function handleAdminRequest(request, env, url) {
const key = url.searchParams.get('key');
// 验证 API Token
if (!env.API_SEC_KEY || key !== env.API_SEC_KEY) {
return new Response('Unauthorized', { status: 401 });
}
// 检查数据库配置
if (!env.DB) {
return createErrorResponse('DB_NOT_CONFIGURED');
}
// 处理清理请求
if (url.pathname === '/clear-logs') {
try {
await env.DB.prepare('DELETE FROM api_logs').run();
return new Response(JSON.stringify({
success: true,
message: {
en: "Logs cleared successfully",
zh: "日志已成功清除"
}
}), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return createErrorResponse('DB_TABLE_MISSING');
}
}
// 处理日志导出
try {
const { results } = await env.DB.prepare(
'SELECT * FROM api_logs ORDER BY timestamp DESC'
).all();
const format = url.searchParams.get('format') || 'csv';
if (format === 'json') {
return new Response(JSON.stringify(results, null, 2), {
headers: { 'Content-Type': 'application/json' }
});
}
// 默认导出 CSV
const csv = [
['Timestamp', 'Path', 'Method', 'Request Headers', 'Request Body',
'Response Status', 'Response Headers', 'Response Body'].join(',')
];
for (const row of results) {
csv.push([
row.timestamp,
row.request_path,
row.request_method,
`"${row.request_headers.replace(/"/g, '""')}"`,
`"${row.request_body.replace(/"/g, '""')}"`,
row.response_status,
`"${row.response_headers.replace(/"/g, '""')}"`,
`"${row.response_body.replace(/"/g, '""')}"`
].join(','));
}
return new Response(csv.join('\n'), {
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': 'attachment; filename=api_logs.csv'
}
});
} catch (error) {
return createErrorResponse('DB_TABLE_MISSING');
}
}
创建并初始化D1数据库
Worker & Pages
=> D1
=> Create
按钮创建新的数据库
名称随意,输入完后点击创建即可
初始化数据库,执行建表语句:
CREATE TABLE IF NOT EXISTS api_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TEXT,
request_path TEXT,
request_method TEXT,
request_headers TEXT,
request_body TEXT,
response_status INTEGER,
response_headers TEXT,
response_body TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
将上面这段SQL复制到蓝框中执行
这样就算成功:
配置Worker
绑定D1数据库
找到刚刚创建的Worker
绑定D1数据库:
变量名这里填写DB
,数据库就选择刚刚初始化的那个,点击Deploy部署即可
配置Worker环境变量
默认情况下,此时此Worker已经可以转发API了,但是不会进行任何行为
可以在这里设置Worker的环境变量
如果需要启用日志记录:
新增环境变量: API_LOGGING
将其设置为 enabled
如果需要使用日志下载接口,需要配置API密钥:
环境变量: API_SEC_KEY
设置为一个只有您知道的私密密钥
如果您需要模型重写功能:
设置环境变量: API_MODEL_REWRITE
格式: model1=>model2,model3=>model4
开始请求
假设您的Worker地址是:wandering-poetry-3106.hash070.workers.dev
那么您如果想要访问OpenAI的Chat.Completions API接口,可以对这个接口发起请求 https://wandering-poetry-3106.hash070.workers.dev/v1/chat/completions
, 如果想要访问Messages API接口,可以对 https://wandering-poetry-3106.hash070.workers.dev/v1/messages
发起请求
如果您需要下载日志:
可以在浏览器中打开形如下面这个URL(注意地址和Key按照这个格式替换成你自己搭建的
https://[你的Worker地址]/logs?key=[你的API_SEC_KEY]&format=[导出数据格式]
例如:
https://wandering-poetry-3106.hash070.workers.dev/logs?key=your-secret-key&format=csv
其中URL中的这个key参数应当填写为您设置的API_SEC_KEY变量,format指导出数据格式,可以是json
或csv
表格
如果您需要清除数据:
可以在浏览器中直接访问这个URL
https://[你的Worker地址]/clear-logs?key=[你的API_SEC_KEY]
{"success":true,"message":{"en":"Logs cleared successfully","zh":"日志已成功清除"}}
附:
API 接口说明
日志导出
地址:/logs
方法:GET
参数:
key:API_SEC_KEY(必填)
format:导出格式(可选,默认 csv)
csv:CSV 格式
json:JSON 格式
示例:
/logs?key=your-api-key
/logs?key=your-api-key&format=json
日志清理
地址:/clear-logs
方法:GET
参数:
key:API_SEC_KEY(必填)
示例:
/clear-logs?key=your-api-key