mirror of
https://github.com/dqzboy/Docker-Proxy.git
synced 2026-07-02 06:05:22 +08:00
419 lines
11 KiB
JavaScript
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;
|