"🎉 Initial release: AI Proxy Worker v1.0"
This commit is contained in:
325
worker.js
Normal file
325
worker.js
Normal file
@@ -0,0 +1,325 @@
|
||||
// 配置常量
|
||||
const CONFIG = {
|
||||
DEEPSEEK_API_URL: 'https://api.deepseek.com/chat/completions',
|
||||
MAX_BODY_SIZE: 1024 * 1024, // 1MB
|
||||
REQUEST_TIMEOUT: 30000, // 30秒
|
||||
VALIDATE_REQUEST_BODY: false, // 是否验证请求体格式(设为 true 启用严格验证)
|
||||
DEFAULT_MODEL: 'deepseek-chat', // 默认模型
|
||||
SUPPORTED_MODELS: [
|
||||
'deepseek-chat', // 通用对话模型 (DeepSeek-V3)
|
||||
'deepseek-reasoner' // 推理模型 (DeepSeek-R1)
|
||||
]
|
||||
};
|
||||
|
||||
// CORS 响应头
|
||||
const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
|
||||
// 安全响应头
|
||||
const SECURITY_HEADERS = {
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'X-Frame-Options': 'DENY',
|
||||
'X-XSS-Protection': '1; mode=block',
|
||||
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
||||
};
|
||||
|
||||
// 辅助函数:验证认证
|
||||
function validateAuth(request, env) {
|
||||
const expectedAuth = env.PROXY_KEY ? `Bearer ${env.PROXY_KEY}` : null;
|
||||
const gotAuth = request.headers.get('authorization') || '';
|
||||
return !expectedAuth || gotAuth === expectedAuth;
|
||||
}
|
||||
|
||||
// 辅助函数:创建错误响应
|
||||
function createErrorResponse(error, status = 500, details = null) {
|
||||
const errorBody = {
|
||||
error,
|
||||
timestamp: new Date().toISOString(),
|
||||
...(details && { details })
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(errorBody), {
|
||||
status,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-cache',
|
||||
...CORS_HEADERS,
|
||||
...SECURITY_HEADERS,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 辅助函数:创建成功响应
|
||||
function createResponse(body, status = 200, headers = {}) {
|
||||
return new Response(body, {
|
||||
status,
|
||||
headers: {
|
||||
...headers,
|
||||
...CORS_HEADERS,
|
||||
...SECURITY_HEADERS,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 辅助函数:验证Content-Type
|
||||
function validateContentType(request) {
|
||||
const contentType = request.headers.get('content-type') || '';
|
||||
if (!contentType.includes('application/json')) {
|
||||
throw new Error('Invalid content type. Expected application/json');
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证请求体大小
|
||||
function validateContentLength(request) {
|
||||
const contentLength = parseInt(request.headers.get('content-length') || '0');
|
||||
if (contentLength > CONFIG.MAX_BODY_SIZE) {
|
||||
throw new Error(`Request body too large. Maximum size: ${CONFIG.MAX_BODY_SIZE} bytes`);
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证单个消息格式
|
||||
function validateSingleMessage(message) {
|
||||
if (!message.role || !message.content) {
|
||||
throw new Error('Invalid request format. Each message must have role and content');
|
||||
}
|
||||
if (!['system', 'user', 'assistant', 'tool'].includes(message.role)) {
|
||||
throw new Error('Invalid request format. Invalid message role');
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证消息数组
|
||||
function validateMessages(messages) {
|
||||
if (!messages || !Array.isArray(messages)) {
|
||||
throw new Error('Invalid request format. Missing or invalid messages array');
|
||||
}
|
||||
|
||||
if (messages.length === 0) {
|
||||
throw new Error('Invalid request format. Messages array cannot be empty');
|
||||
}
|
||||
|
||||
for (const message of messages) {
|
||||
validateSingleMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证模型(警告但不阻止)
|
||||
function validateModel(model) {
|
||||
if (model && !CONFIG.SUPPORTED_MODELS.includes(model)) {
|
||||
console.warn(`Unsupported model: ${model}, supported models: ${CONFIG.SUPPORTED_MODELS.join(', ')}`);
|
||||
// 注意:这里只是警告,不阻止请求,让 DeepSeek API 自己处理
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证请求体内容
|
||||
async function validateRequestBody(request) {
|
||||
try {
|
||||
const body = await request.clone().json();
|
||||
validateMessages(body.messages);
|
||||
validateModel(body.model);
|
||||
} catch (e) {
|
||||
if (e.message.includes('Invalid request format')) {
|
||||
throw e;
|
||||
}
|
||||
throw new Error('Invalid JSON format');
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:验证请求
|
||||
async function validateRequest(request) {
|
||||
validateContentType(request);
|
||||
validateContentLength(request);
|
||||
|
||||
if (CONFIG.VALIDATE_REQUEST_BODY) {
|
||||
await validateRequestBody(request);
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:发送上游请求
|
||||
async function sendUpstreamRequest(request, env) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => {
|
||||
controller.abort();
|
||||
}, CONFIG.REQUEST_TIMEOUT);
|
||||
|
||||
try {
|
||||
const accept = request.headers.get('accept') || 'application/json';
|
||||
const contentType = request.headers.get('content-type') || 'application/json';
|
||||
|
||||
// 处理请求体,确保有默认模型
|
||||
let requestBody;
|
||||
try {
|
||||
const bodyText = await request.text();
|
||||
const body = JSON.parse(bodyText);
|
||||
|
||||
// 如果没有指定模型,使用默认模型
|
||||
if (!body.model) {
|
||||
body.model = CONFIG.DEFAULT_MODEL;
|
||||
console.log(`No model specified, using default: ${CONFIG.DEFAULT_MODEL}`);
|
||||
}
|
||||
|
||||
requestBody = JSON.stringify(body);
|
||||
} catch (err) {
|
||||
// 如果解析失败,使用原始请求体(让上游API处理错误)
|
||||
console.warn('Failed to parse request body for model injection:', err.message);
|
||||
requestBody = await request.text();
|
||||
}
|
||||
|
||||
const upstream = await fetch(CONFIG.DEEPSEEK_API_URL, {
|
||||
method: 'POST',
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${env.DEEPSEEK_API_KEY}`,
|
||||
'Content-Type': contentType,
|
||||
'Accept': accept,
|
||||
'User-Agent': 'AI-Proxy-Worker/1.0',
|
||||
},
|
||||
body: requestBody,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
// 记录请求日志
|
||||
console.log('DeepSeek API request:', {
|
||||
status: upstream.status,
|
||||
statusText: upstream.statusText,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return upstream;
|
||||
} catch (err) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (err.name === 'AbortError') {
|
||||
throw new Error('Request timeout');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:处理简单路由
|
||||
function handleSimpleRoutes(request, url) {
|
||||
// 处理 OPTIONS 请求(CORS 预检)
|
||||
if (request.method === 'OPTIONS') {
|
||||
return createResponse(null, 200, CORS_HEADERS);
|
||||
}
|
||||
|
||||
// 健康检查
|
||||
if (request.method === 'GET' && url.pathname === '/') {
|
||||
return createResponse(JSON.stringify({
|
||||
status: 'ok',
|
||||
service: 'AI Proxy Worker',
|
||||
timestamp: new Date().toISOString()
|
||||
}), 200, { 'Content-Type': 'application/json' });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 辅助函数:处理错误响应
|
||||
function handleError(err, request, url, startTime) {
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
console.error('Request failed:', {
|
||||
error: err.message,
|
||||
duration: `${duration}ms`,
|
||||
method: request.method,
|
||||
pathname: url.pathname,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// 根据错误类型返回不同的状态码
|
||||
if (err.message.includes('Invalid content type')) {
|
||||
return createErrorResponse('invalid_content_type', 400, err.message);
|
||||
}
|
||||
if (err.message.includes('Request body too large')) {
|
||||
return createErrorResponse('payload_too_large', 413, err.message);
|
||||
}
|
||||
if (err.message.includes('Invalid request format') || err.message.includes('Invalid JSON')) {
|
||||
return createErrorResponse('invalid_request', 400, err.message);
|
||||
}
|
||||
if (err.message.includes('Request timeout')) {
|
||||
return createErrorResponse('timeout', 504, 'Request to DeepSeek API timed out');
|
||||
}
|
||||
|
||||
// 通用错误
|
||||
return createErrorResponse('internal_error', 500, 'An unexpected error occurred');
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Cloudflare Workers entry point
|
||||
* AI Proxy Worker - Universal AI API proxy with enhanced error handling and security
|
||||
*
|
||||
* Current Version (v1.0): DeepSeek API support
|
||||
* - deepseek-chat: General conversation model
|
||||
* - deepseek-reasoner: Complex reasoning model
|
||||
*
|
||||
* Future Versions: Multi-AI support planned (OpenAI, Claude, Gemini)
|
||||
*/
|
||||
async fetch(request, env) {
|
||||
const url = new URL(request.url);
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
// 处理简单路由
|
||||
const simpleResponse = handleSimpleRoutes(request, url);
|
||||
if (simpleResponse) return simpleResponse;
|
||||
|
||||
// 只允许 POST /chat
|
||||
if (request.method !== 'POST' || url.pathname !== '/chat') {
|
||||
return createErrorResponse('not_found', 404, 'Endpoint not found');
|
||||
}
|
||||
|
||||
// 验证认证
|
||||
if (!validateAuth(request, env)) {
|
||||
return createErrorResponse('unauthorized', 401, 'Invalid or missing authorization');
|
||||
}
|
||||
|
||||
// 检查必需的环境变量
|
||||
if (!env.DEEPSEEK_API_KEY) {
|
||||
console.error('Missing DEEPSEEK_API_KEY environment variable');
|
||||
return createErrorResponse('configuration_error', 500, 'Service configuration error');
|
||||
}
|
||||
|
||||
// 验证请求
|
||||
await validateRequest(request);
|
||||
|
||||
// 发送上游请求
|
||||
const upstream = await sendUpstreamRequest(request, env);
|
||||
|
||||
// 处理上游错误
|
||||
if (!upstream.ok) {
|
||||
const errorText = await upstream.text();
|
||||
console.error('DeepSeek API error:', {
|
||||
status: upstream.status,
|
||||
statusText: upstream.statusText,
|
||||
body: errorText,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
return createErrorResponse('api_error', upstream.status, {
|
||||
upstream_status: upstream.status,
|
||||
upstream_message: upstream.statusText
|
||||
});
|
||||
}
|
||||
|
||||
// 返回成功响应
|
||||
const responseHeaders = {
|
||||
'Content-Type': upstream.headers.get('Content-Type') || 'application/json',
|
||||
'Cache-Control': 'no-store, no-transform',
|
||||
};
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`Request completed successfully in ${duration}ms`);
|
||||
|
||||
return createResponse(upstream.body, upstream.status, responseHeaders);
|
||||
|
||||
} catch (err) {
|
||||
return handleError(err, request, url, startTime);
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user