Files
Docker-Proxy/hubcmdui/services/httpProxyService.js
T

419 lines
11 KiB
JavaScript

/**
* HTTP代理服务模块
*/
const http = require('http');
const https = require('https');
const url = require('url');
const net = require('net');
const logger = require('../logger');
const configServiceDB = require('./configServiceDB');
class HttpProxyService {
constructor() {
this.proxyServer = null;
this.isRunning = false;
this.config = {
port: 8080,
host: '0.0.0.0',
enableHttps: true,
enableAuth: false,
username: '',
password: '',
allowedHosts: [],
blockedHosts: [],
logRequests: true
};
}
/**
* 启动代理服务器
*/
async start(config = {}) {
try {
this.config = { ...this.config, ...config };
if (this.isRunning) {
logger.warn('HTTP代理服务器已在运行');
return;
}
this.proxyServer = http.createServer();
// 处理HTTP请求
this.proxyServer.on('request', this.handleHttpRequest.bind(this));
// 处理HTTPS CONNECT请求
this.proxyServer.on('connect', this.handleHttpsConnect.bind(this));
// 错误处理
this.proxyServer.on('error', this.handleServerError.bind(this));
return new Promise((resolve, reject) => {
this.proxyServer.listen(this.config.port, this.config.host, (err) => {
if (err) {
reject(err);
} else {
this.isRunning = true;
logger.info(`HTTP代理服务器已启动,监听 ${this.config.host}:${this.config.port}`);
resolve();
}
});
});
} catch (error) {
logger.error('启动HTTP代理服务器失败:', error);
throw error;
}
}
/**
* 停止代理服务器
*/
async stop() {
return new Promise((resolve) => {
if (this.proxyServer && this.isRunning) {
this.proxyServer.close(() => {
this.isRunning = false;
logger.info('HTTP代理服务器已停止');
resolve();
});
} else {
resolve();
}
});
}
/**
* 处理HTTP请求
*/
handleHttpRequest(clientReq, clientRes) {
try {
const targetUrl = clientReq.url;
const parsedUrl = url.parse(targetUrl);
// 记录请求日志
if (this.config.logRequests) {
logger.info(`HTTP代理请求: ${clientReq.method} ${targetUrl}`);
}
// 认证检查
if (this.config.enableAuth && !this.checkAuth(clientReq)) {
this.sendAuthRequired(clientRes);
return;
}
// 主机检查
if (!this.isHostAllowed(parsedUrl.hostname)) {
this.sendForbidden(clientRes, '主机不在允许列表中');
return;
}
if (this.isHostBlocked(parsedUrl.hostname)) {
this.sendForbidden(clientRes, '主机已被阻止');
return;
}
// 创建目标请求选项
const options = {
hostname: parsedUrl.hostname,
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
path: parsedUrl.path,
method: clientReq.method,
headers: { ...clientReq.headers }
};
// 移除代理相关的头部
delete options.headers['proxy-connection'];
delete options.headers['proxy-authorization'];
// 选择HTTP或HTTPS
const httpModule = parsedUrl.protocol === 'https:' ? https : http;
// 发送请求到目标服务器
const proxyReq = httpModule.request(options, (proxyRes) => {
// 复制响应头
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
// 管道传输响应数据
proxyRes.pipe(clientRes);
});
// 错误处理
proxyReq.on('error', (err) => {
logger.error('代理请求错误:', err);
if (!clientRes.headersSent) {
clientRes.writeHead(500, { 'Content-Type': 'text/plain' });
clientRes.end('代理服务器错误');
}
});
// 管道传输请求数据
clientReq.pipe(proxyReq);
} catch (error) {
logger.error('处理HTTP请求失败:', error);
if (!clientRes.headersSent) {
clientRes.writeHead(500, { 'Content-Type': 'text/plain' });
clientRes.end('内部服务器错误');
}
}
}
/**
* 处理HTTPS CONNECT请求
*/
handleHttpsConnect(clientReq, clientSocket, head) {
try {
const { hostname, port } = this.parseConnectUrl(clientReq.url);
// 记录请求日志
if (this.config.logRequests) {
logger.info(`HTTPS代理请求: CONNECT ${hostname}:${port}`);
}
// 认证检查
if (this.config.enableAuth && !this.checkAuth(clientReq)) {
clientSocket.write('HTTP/1.1 407 Proxy Authentication Required\r\n\r\n');
clientSocket.end();
return;
}
// 主机检查
if (!this.isHostAllowed(hostname)) {
clientSocket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
clientSocket.end();
return;
}
if (this.isHostBlocked(hostname)) {
clientSocket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
clientSocket.end();
return;
}
// 连接到目标服务器
const serverSocket = net.connect(port, hostname, () => {
// 发送连接成功响应
clientSocket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
// 建立隧道
serverSocket.write(head);
serverSocket.pipe(clientSocket);
clientSocket.pipe(serverSocket);
});
// 错误处理
serverSocket.on('error', (err) => {
logger.error('服务器连接错误:', err);
clientSocket.write('HTTP/1.1 502 Bad Gateway\r\n\r\n');
clientSocket.end();
});
clientSocket.on('error', (err) => {
logger.error('客户端连接错误:', err);
serverSocket.end();
});
} catch (error) {
logger.error('处理HTTPS CONNECT请求失败:', error);
clientSocket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n');
clientSocket.end();
}
}
/**
* 解析CONNECT请求URL
*/
parseConnectUrl(connectUrl) {
const [hostname, port] = connectUrl.split(':');
return {
hostname,
port: parseInt(port) || 443
};
}
/**
* 检查认证
*/
checkAuth(req) {
if (!this.config.enableAuth) {
return true;
}
const auth = req.headers['proxy-authorization'];
if (!auth) {
return false;
}
const [type, credentials] = auth.split(' ');
if (type !== 'Basic') {
return false;
}
const decoded = Buffer.from(credentials, 'base64').toString();
const [username, password] = decoded.split(':');
return username === this.config.username && password === this.config.password;
}
/**
* 检查主机是否允许
*/
isHostAllowed(hostname) {
if (this.config.allowedHosts.length === 0) {
return true; // 如果没有设置允许列表,则允许所有
}
return this.config.allowedHosts.some(allowed => {
if (allowed.startsWith('*.')) {
const domain = allowed.substring(2);
return hostname.endsWith(domain);
}
return hostname === allowed;
});
}
/**
* 检查主机是否被阻止
*/
isHostBlocked(hostname) {
return this.config.blockedHosts.some(blocked => {
if (blocked.startsWith('*.')) {
const domain = blocked.substring(2);
return hostname.endsWith(domain);
}
return hostname === blocked;
});
}
/**
* 发送认证要求响应
*/
sendAuthRequired(res) {
res.writeHead(407, {
'Proxy-Authenticate': 'Basic realm="Proxy"',
'Content-Type': 'text/plain'
});
res.end('需要代理认证');
}
/**
* 发送禁止访问响应
*/
sendForbidden(res, message) {
res.writeHead(403, { 'Content-Type': 'text/plain' });
res.end(message || '禁止访问');
}
/**
* 处理服务器错误
*/
handleServerError(error) {
logger.error('HTTP代理服务器错误:', error);
}
/**
* 获取代理状态
*/
getStatus() {
return {
isRunning: this.isRunning,
config: this.config,
port: this.config.port,
host: this.config.host
};
}
/**
* 更新配置
*/
async updateConfig(newConfig) {
try {
const needRestart = this.isRunning && (
newConfig.port !== this.config.port ||
newConfig.host !== this.config.host
);
this.config = { ...this.config, ...newConfig };
if (needRestart) {
await this.stop();
await this.start();
logger.info('HTTP代理服务器配置已更新并重启');
} else {
logger.info('HTTP代理服务器配置已更新');
}
// 保存配置到数据库
await configServiceDB.saveConfig('httpProxyConfig', this.config);
} catch (error) {
logger.error('更新HTTP代理配置失败:', error);
throw error;
}
}
/**
* 从数据库加载配置
*/
async loadConfig() {
try {
const savedConfig = await configServiceDB.getConfig('httpProxyConfig');
if (savedConfig) {
this.config = { ...this.config, ...savedConfig };
logger.info('HTTP代理配置已从数据库加载');
} else {
// 从环境变量加载默认配置
this.config = {
...this.config,
port: parseInt(process.env.PROXY_PORT) || this.config.port,
host: process.env.PROXY_HOST || this.config.host
};
logger.info('使用默认HTTP代理配置');
}
} catch (error) {
logger.error('加载HTTP代理配置失败:', error);
// 使用默认配置
logger.info('使用默认HTTP代理配置');
}
}
/**
* 检查环境变量并自动启动代理
*/
async checkEnvironmentAndAutoStart() {
const autoStart = process.env.PROXY_AUTO_START;
const proxyPort = process.env.PROXY_PORT;
const proxyHost = process.env.PROXY_HOST;
const enableAuth = process.env.PROXY_ENABLE_AUTH;
const username = process.env.PROXY_USERNAME;
const password = process.env.PROXY_PASSWORD;
// 检查是否应该自动启动代理
if (autoStart === 'true' || proxyPort || proxyHost) {
logger.info('检测到代理环境变量,尝试自动启动HTTP代理服务...');
const envConfig = {};
if (proxyPort) envConfig.port = parseInt(proxyPort);
if (proxyHost) envConfig.host = proxyHost;
if (enableAuth === 'true') {
envConfig.enableAuth = true;
if (username) envConfig.username = username;
if (password) envConfig.password = password;
}
try {
await this.start(envConfig);
logger.info(`HTTP代理服务已自动启动 - ${envConfig.host || '0.0.0.0'}:${envConfig.port || 8080}`);
} catch (error) {
logger.warn('自动启动HTTP代理服务失败:', error.message);
}
} else {
logger.info('未检测到代理自动启动环境变量');
}
}
}
// 创建单例实例
const httpProxyService = new HttpProxyService();
module.exports = httpProxyService;