import * as plugins from '../../plugins.js'; import { HttpProxy } from '../http-proxy/index.js'; import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js'; import type { IRouteConfig } from './models/route-types.js'; export class HttpProxyBridge { private httpProxy: HttpProxy | null = null; constructor(private settings: ISmartProxyOptions) {} /** * Get the HttpProxy instance */ public getHttpProxy(): HttpProxy | null { return this.httpProxy; } /** * Initialize HttpProxy instance */ public async initialize(): Promise { if (!this.httpProxy && this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) { const httpProxyOptions: any = { port: this.settings.httpProxyPort!, portProxyIntegration: true, logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info' }; this.httpProxy = new HttpProxy(httpProxyOptions); console.log(`Initialized HttpProxy on port ${this.settings.httpProxyPort}`); // Apply route configurations to HttpProxy await this.syncRoutesToHttpProxy(this.settings.routes || []); } } /** * Sync routes to HttpProxy */ public async syncRoutesToHttpProxy(routes: IRouteConfig[]): Promise { if (!this.httpProxy) return; // Convert routes to HttpProxy format const httpProxyConfigs = routes .filter(route => { // Check if this route matches any of the specified network proxy ports const routePorts = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports]; return routePorts.some(port => this.settings.useHttpProxy?.includes(port) ); }) .map(route => this.routeToHttpProxyConfig(route)); // Apply configurations to HttpProxy await this.httpProxy.updateRouteConfigs(httpProxyConfigs); } /** * Convert route to HttpProxy configuration */ private routeToHttpProxyConfig(route: IRouteConfig): any { // Convert route to HttpProxy domain config format let domain = '*'; if (route.match.domains) { if (Array.isArray(route.match.domains)) { domain = route.match.domains[0] || '*'; } else { domain = route.match.domains; } } return { domain, target: route.action.target, tls: route.action.tls, security: route.action.security, match: { ...route.match, domains: domain // Ensure domains is always set for HttpProxy } }; } /** * Check if connection should use HttpProxy */ public shouldUseHttpProxy(connection: IConnectionRecord, routeMatch: any): boolean { // Only use HttpProxy for TLS termination return ( routeMatch.route.action.tls?.mode === 'terminate' || routeMatch.route.action.tls?.mode === 'terminate-and-reencrypt' ) && this.httpProxy !== null; } /** * Forward connection to HttpProxy */ public async forwardToHttpProxy( connectionId: string, socket: plugins.net.Socket, record: IConnectionRecord, initialChunk: Buffer, httpProxyPort: number, cleanupCallback: (reason: string) => void ): Promise { if (!this.httpProxy) { throw new Error('HttpProxy not initialized'); } const proxySocket = new plugins.net.Socket(); await new Promise((resolve, reject) => { proxySocket.connect(httpProxyPort, 'localhost', () => { console.log(`[${connectionId}] Connected to HttpProxy for termination`); resolve(); }); proxySocket.on('error', reject); }); // Send initial chunk if present if (initialChunk) { proxySocket.write(initialChunk); } // Pipe the sockets together socket.pipe(proxySocket); proxySocket.pipe(socket); // Handle cleanup const cleanup = (reason: string) => { socket.unpipe(proxySocket); proxySocket.unpipe(socket); proxySocket.destroy(); cleanupCallback(reason); }; socket.on('end', () => cleanup('socket_end')); socket.on('error', () => cleanup('socket_error')); proxySocket.on('end', () => cleanup('proxy_end')); proxySocket.on('error', () => cleanup('proxy_error')); } /** * Start HttpProxy */ public async start(): Promise { if (this.httpProxy) { await this.httpProxy.start(); } } /** * Stop HttpProxy */ public async stop(): Promise { if (this.httpProxy) { await this.httpProxy.stop(); this.httpProxy = null; } } }