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

419 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;