| const express = require('express'); |
| const axios = require('axios'); |
|
|
| const app = express(); |
| const PORT = process.env.PORT || 7860; |
|
|
| |
| const TARGET_API = process.env.TARGET_API || "https://huggingface.co"; |
|
|
| |
| const AUTH_API_KEY = process.env.AUTH_API_KEY; |
|
|
| |
| let DEST_API_KEYS = []; |
| if (process.env.DEST_API_KEY) { |
| try { |
| DEST_API_KEYS = JSON.parse(process.env.DEST_API_KEY); |
| if (!Array.isArray(DEST_API_KEYS)) { |
| DEST_API_KEYS = [process.env.DEST_API_KEY]; |
| } |
| } catch (e) { |
| DEST_API_KEYS = process.env.DEST_API_KEY.split(',').map(k => k.trim()).filter(Boolean); |
| } |
| } |
|
|
| |
| let PATH_MAPPINGS = { "/": "/" }; |
| try { |
| if (process.env.PATH_MAPPINGS) { |
| PATH_MAPPINGS = JSON.parse(process.env.PATH_MAPPINGS); |
| } |
| } catch (e) { |
| console.error("解析 PATH_MAPPINGS 失败,使用默认配置:", e.message); |
| PATH_MAPPINGS = { "/": "/" }; |
| } |
|
|
| |
| app.get('/', (req, res) => { |
| res.send("service running."); |
| }); |
|
|
| |
| app.use(async (req, res) => { |
| |
| |
| if (AUTH_API_KEY) { |
| let clientKey = ''; |
|
|
| |
| if (req.path.includes('/v1/messages') || req.path.includes('/v1/responses')) { |
| |
| clientKey = req.headers['x-api-key'] || (req.headers['authorization'] || '').replace(/^Bearer\s+/i, '').trim(); |
| } else { |
| |
| const authHeader = req.headers['authorization'] || ''; |
| clientKey = authHeader.replace(/^Bearer\s+/i, '').trim(); |
| } |
|
|
| if (clientKey !== AUTH_API_KEY) { |
| return res.status(401).send("Unauthorized: Invalid or missing API Key"); |
| } |
| } |
|
|
| let fullPath = req.path; |
| |
| for (const [originalPath, newPath] of Object.entries(PATH_MAPPINGS)) { |
| if (fullPath.startsWith(originalPath)) { |
| fullPath = fullPath.replace(originalPath, newPath); |
| break; |
| } |
| } |
|
|
| |
| const targetUrl = `${TARGET_API}${fullPath}`; |
|
|
| |
| const headers = { ...req.headers }; |
| delete headers.host; |
| |
| |
| let bodyBuffer = []; |
| req.on('data', chunk => bodyBuffer.push(chunk)); |
| |
| await new Promise(resolve => req.on('end', resolve)); |
| const bodyData = Buffer.concat(bodyBuffer); |
|
|
| |
| if (DEST_API_KEYS.length > 0) { |
| const randomKey = DEST_API_KEYS[Math.floor(Math.random() * DEST_API_KEYS.length)]; |
| |
| |
| if (fullPath.includes('messages')) { |
| |
| headers['x-api-key'] = randomKey; |
| delete headers['authorization']; |
| } else if (fullPath.includes('responses')) { |
| |
| headers['x-api-key'] = randomKey; |
| headers['authorization'] = `Bearer ${randomKey}`; |
| } else { |
| |
| headers['authorization'] = `Bearer ${randomKey}`; |
| delete headers['x-api-key']; |
| } |
| } |
|
|
| |
| |
| |
|
|
| const MAX_RETRIES = 60; |
| let attempt = 0; |
|
|
| |
| while (attempt < MAX_RETRIES) { |
| attempt++; |
| try { |
| |
| const response = await axios({ |
| method: req.method, |
| url: targetUrl, |
| headers: headers, |
| params: req.query, |
| data: bodyData.length > 0 ? bodyData : undefined, |
| responseType: 'stream', |
| validateStatus: () => true |
| }); |
|
|
| |
| |
|
|
| |
| if (response.status === 200) { |
| |
| res.status(response.status); |
|
|
| |
| const skipHeaders = ['content-length', 'transfer-encoding', 'connection']; |
| Object.entries(response.headers).forEach(([key, value]) => { |
| if (!skipHeaders.includes(key.toLowerCase())) { |
| res.setHeader(key, value); |
| } |
| }); |
|
|
| |
| response.data.pipe(res); |
| return; |
| } else { |
| |
| response.data.destroy(); |
| |
| |
| if (attempt >= MAX_RETRIES) { |
| console.error(`[Proxy Failed] Reached max retries (${MAX_RETRIES}). Last status: ${response.status}`); |
| if (!res.headersSent) { |
| res.status(response.status).send(`Proxy Error: Target server returned status ${response.status} after ${MAX_RETRIES} attempts.`); |
| } |
| return; |
| } |
| |
| |
| } |
|
|
| } catch (error) { |
| |
| console.error(`[Proxy Fatal Error on attempt ${attempt}]: ${error.message}`); |
| |
| |
| if (attempt >= MAX_RETRIES) { |
| if (!res.headersSent) { |
| res.status(500).send(`Proxy Error: ${error.message}`); |
| } |
| return; |
| } |
| |
| } |
| } |
| }); |
|
|
| app.listen(PORT, '0.0.0.0', () => { |
| console.log(`服务已在端口 ${PORT} 启动`); |
| }); |