diff --git a/src/proxy.ts b/src/proxy.ts index d67dd81..f9e4cc2 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,75 +1,149 @@ +// 导入 FileSystemConfig 类型,用于定义文件系统配置 import type { FileSystemConfig } from 'common/fileSystemConfig'; +// 导入 dns 模块,用于域名解析 import * as dns from 'dns'; +// 导入 http 模块中的 request 函数,用于发送 HTTP 请求 import { request } from 'http'; +// 导入 socks 模块中的 SocksClient 类,用于创建 SOCKS 代理客户端 import { SocksClient } from 'socks'; +// 导入 Logging 模块,用于日志记录 import { Logging } from './logging'; +// 导入 toPromise 和 validatePort 函数,用于将回调函数转换为 Promise 和验证端口号 import { toPromise, validatePort } from './utils'; +/** + * 解析主机名并返回其对应的 IP 地址。 + * + * @param hostname - 要解析的主机名。 + * @returns 解析后的 IP 地址。 + * @throws {Error} 如果无法解析主机名,则抛出错误。 + */ async function resolveHostname(hostname: string): Promise { + // 使用 toPromise 函数将 dns.lookup 方法转换为 Promise return toPromise(cb => dns.lookup(hostname, cb)).then((ip) => { + // 记录调试信息,显示已解析的主机名和 IP 地址 Logging.debug`Resolved hostname "${hostname}" to: ${ip}`; + // 返回解析后的 IP 地址 return ip; }); } +/** + * 验证文件系统配置对象的有效性。 + * + * @param config - 要验证的文件系统配置对象。 + * @throws {Error} 如果配置对象缺少必要的字段,则抛出错误。 + */ function validateConfig(config: FileSystemConfig) { + // 检查 config.proxy 是否存在,如果不存在则抛出错误 if (!config.proxy) throw new Error(`Missing field 'config.proxy'`); + // 检查 config.proxy.type 是否存在,如果不存在则抛出错误 if (!config.proxy.type) throw new Error(`Missing field 'config.proxy.type'`); + // 检查 config.proxy.host 是否存在,如果不存在则抛出错误 if (!config.proxy.host) throw new Error(`Missing field 'config.proxy.host'`); + // 检查 config.proxy.port 是否存在,如果不存在则抛出错误 if (!config.proxy.port) throw new Error(`Missing field 'config.proxy.port'`); + // 验证 config.proxy.port 是否为有效的端口号,并更新 config.proxy.port 的值 config.proxy.port = validatePort(config.proxy.port); } +/** + * 创建一个通过 SOCKS 代理的网络连接。 + * + * @param config - 包含连接配置的对象。 + * @returns 一个可读流,表示与目标主机的连接。 + * @throws {Error} 如果配置无效或连接失败,则抛出错误。 + */ export async function socks(config: FileSystemConfig): Promise { + // 记录创建 SOCKS 代理连接的信息 Logging.info`Creating socks proxy connection for ${config.name}`; + // 验证配置对象的有效性 validateConfig(config); + // 检查代理类型是否为 socks4 或 socks5 if (config.proxy!.type !== 'socks4' && config.proxy!.type !== 'socks5') { + // 如果不是,抛出错误 throw new Error(`Expected 'config.proxy.type' to be 'socks4' or 'socks5'`); } try { + // 解析代理主机名为 IP 地址 const ipaddress = (await resolveHostname(config.proxy!.host)); + // 如果解析失败,抛出错误 if (!ipaddress) throw new Error(`Couldn't resolve '${config.proxy!.host}'`); + // 记录连接信息 Logging.debug`\tConnecting to ${config.host}:${config.port} over ${config.proxy!.type} proxy at ${ipaddress}:${config.proxy!.port}`; + // 创建到目标主机的 SOCKS 连接 const con = await SocksClient.createConnection({ + // 定义要执行的命令为 'connect',即建立连接 command: 'connect', + // 目标地址,即要连接的主机和端口 destination: { + // 目标主机,使用配置中的 host 值,该值是必需的 host: config.host!, + // 目标端口,使用配置中的 port 值,该值是必需的 port: config.port!, }, + // 代理服务器的配置 proxy: { + // 代理服务器的 IP 地址,通过解析配置中的 proxy.host 得到 ipaddress, + // 代理服务器的端口号,使用配置中的 proxy.port 值,该值是必需的 port: config.proxy!.port, + // 代理服务器的类型,根据配置中的 proxy.type 确定,支持 socks4 和 socks5 type: config.proxy!.type === 'socks4' ? 4 : 5, }, }); + // 返回连接的可读流 return con.socket as NodeJS.ReadableStream; } catch (e) { + // 如果连接失败,抛出错误 throw new Error(`Error while connecting to the the proxy: ${e.message}`); } } +/** + * 创建一个通过 HTTP 代理的网络连接。 + * + * @param config - 包含连接配置的对象。 + * @returns 一个可读流,表示与目标主机的连接。 + * @throws {Error} 如果配置无效或连接失败,则抛出错误。 + */ export function http(config: FileSystemConfig): Promise { + // 记录创建 HTTP 代理连接的信息 Logging.info`Creating http proxy connection for ${config.name}`; + // 验证配置对象的有效性 validateConfig(config); + // 返回一个 Promise,用于处理连接结果 return new Promise((resolve, reject) => { + // 检查代理类型是否为 http if (config.proxy!.type !== 'http') { + // 如果不是,拒绝 Promise 并抛出错误 reject(new Error(`Expected config.proxy.type' to be 'http'`)); } try { + // 记录连接信息 Logging.debug`\tConnecting to ${config.host}:${config.port} over http proxy at ${config.proxy!.host}:${config.proxy!.port}`; + // 创建到目标主机的 HTTP 连接 const req = request({ + // 代理服务器的端口号,使用配置中的 proxy.port 值,该值是必需的 port: config.proxy!.port, + // 代理服务器的主机名,使用配置中的 proxy.host 值,该值是必需的 hostname: config.proxy!.host, + // 使用 CONNECT 方法建立隧道 method: 'CONNECT', + // 目标主机和端口,使用配置中的 host 和 port 值,该值是必需的 path: `${config.host}:${config.port}`, }); + // 结束请求 req.end(); + // 监听连接事件 req.on('connect', (res, socket) => { + // 连接成功,解析 socket 并返回可读流 resolve(socket as NodeJS.ReadableStream); }); } catch (e) { + // 如果连接失败,拒绝 Promise 并抛出错误 reject(new Error(`Error while connecting to the the proxy: ${e.message}`)); } }); -} +} \ No newline at end of file