smartproxy/ts/proxies/smart-proxy/http-proxy-bridge.ts
2025-05-29 11:30:42 +00:00

162 lines
4.5 KiB
TypeScript

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<void> {
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<void> {
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 {
...route, // Keep the original route structure
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<void> {
if (!this.httpProxy) {
throw new Error('HttpProxy not initialized');
}
const proxySocket = new plugins.net.Socket();
await new Promise<void>((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<void> {
if (this.httpProxy) {
await this.httpProxy.start();
}
}
/**
* Stop HttpProxy
*/
public async stop(): Promise<void> {
if (this.httpProxy) {
await this.httpProxy.stop();
this.httpProxy = null;
}
}
}