2025-05-09 21:21:28 +00:00
|
|
|
import * as plugins from '../../plugins.js';
|
2025-05-19 17:28:05 +00:00
|
|
|
import { HttpProxy } from '../http-proxy/index.js';
|
2025-05-10 00:49:39 +00:00
|
|
|
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
|
|
|
|
import type { IRouteConfig } from './models/route-types.js';
|
2025-03-14 09:53:25 +00:00
|
|
|
|
2025-05-19 17:28:05 +00:00
|
|
|
export class HttpProxyBridge {
|
|
|
|
private httpProxy: HttpProxy | null = null;
|
2025-05-09 21:21:28 +00:00
|
|
|
|
2025-05-09 22:46:53 +00:00
|
|
|
constructor(private settings: ISmartProxyOptions) {}
|
2025-03-14 09:53:25 +00:00
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
/**
|
2025-05-19 17:28:05 +00:00
|
|
|
* Get the HttpProxy instance
|
2025-03-25 22:30:57 +00:00
|
|
|
*/
|
2025-05-19 17:28:05 +00:00
|
|
|
public getHttpProxy(): HttpProxy | null {
|
|
|
|
return this.httpProxy;
|
2025-03-25 22:30:57 +00:00
|
|
|
}
|
|
|
|
|
2025-03-14 09:53:25 +00:00
|
|
|
/**
|
2025-05-19 17:28:05 +00:00
|
|
|
* Initialize HttpProxy instance
|
2025-03-14 09:53:25 +00:00
|
|
|
*/
|
|
|
|
public async initialize(): Promise<void> {
|
2025-05-19 17:28:05 +00:00
|
|
|
if (!this.httpProxy && this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) {
|
|
|
|
const httpProxyOptions: any = {
|
|
|
|
port: this.settings.httpProxyPort!,
|
2025-03-14 09:53:25 +00:00
|
|
|
portProxyIntegration: true,
|
2025-05-18 15:38:07 +00:00
|
|
|
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info'
|
2025-03-14 09:53:25 +00:00
|
|
|
};
|
|
|
|
|
2025-05-19 17:28:05 +00:00
|
|
|
this.httpProxy = new HttpProxy(httpProxyOptions);
|
|
|
|
console.log(`Initialized HttpProxy on port ${this.settings.httpProxyPort}`);
|
2025-03-14 09:53:25 +00:00
|
|
|
|
2025-05-19 17:28:05 +00:00
|
|
|
// Apply route configurations to HttpProxy
|
|
|
|
await this.syncRoutesToHttpProxy(this.settings.routes || []);
|
2025-03-14 09:53:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-25 22:30:57 +00:00
|
|
|
/**
|
2025-05-19 17:28:05 +00:00
|
|
|
* Sync routes to HttpProxy
|
2025-03-25 22:30:57 +00:00
|
|
|
*/
|
2025-05-19 17:28:05 +00:00
|
|
|
public async syncRoutesToHttpProxy(routes: IRouteConfig[]): Promise<void> {
|
|
|
|
if (!this.httpProxy) return;
|
2025-05-13 12:48:41 +00:00
|
|
|
|
2025-05-19 17:28:05 +00:00
|
|
|
// Convert routes to HttpProxy format
|
|
|
|
const httpProxyConfigs = routes
|
2025-05-18 15:38:07 +00:00
|
|
|
.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 =>
|
2025-05-19 17:28:05 +00:00
|
|
|
this.settings.useHttpProxy?.includes(port)
|
2025-05-18 15:38:07 +00:00
|
|
|
);
|
|
|
|
})
|
2025-05-19 17:28:05 +00:00
|
|
|
.map(route => this.routeToHttpProxyConfig(route));
|
2025-05-18 15:38:07 +00:00
|
|
|
|
2025-05-19 17:28:05 +00:00
|
|
|
// Apply configurations to HttpProxy
|
|
|
|
await this.httpProxy.updateRouteConfigs(httpProxyConfigs);
|
2025-03-14 09:53:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-05-19 17:28:05 +00:00
|
|
|
* Convert route to HttpProxy configuration
|
2025-03-14 09:53:25 +00:00
|
|
|
*/
|
2025-05-19 17:28:05 +00:00
|
|
|
private routeToHttpProxyConfig(route: IRouteConfig): any {
|
|
|
|
// Convert route to HttpProxy domain config format
|
2025-05-19 19:59:22 +00:00
|
|
|
let domain = '*';
|
|
|
|
if (route.match.domains) {
|
|
|
|
if (Array.isArray(route.match.domains)) {
|
|
|
|
domain = route.match.domains[0] || '*';
|
|
|
|
} else {
|
|
|
|
domain = route.match.domains;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-18 15:38:07 +00:00
|
|
|
return {
|
2025-05-29 11:30:42 +00:00
|
|
|
...route, // Keep the original route structure
|
2025-05-19 19:59:22 +00:00
|
|
|
match: {
|
|
|
|
...route.match,
|
|
|
|
domains: domain // Ensure domains is always set for HttpProxy
|
|
|
|
}
|
2025-05-18 15:38:07 +00:00
|
|
|
};
|
2025-03-14 09:53:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-05-19 17:28:05 +00:00
|
|
|
* Check if connection should use HttpProxy
|
2025-03-14 09:53:25 +00:00
|
|
|
*/
|
2025-05-19 17:28:05 +00:00
|
|
|
public shouldUseHttpProxy(connection: IConnectionRecord, routeMatch: any): boolean {
|
|
|
|
// Only use HttpProxy for TLS termination
|
2025-05-18 15:38:07 +00:00
|
|
|
return (
|
|
|
|
routeMatch.route.action.tls?.mode === 'terminate' ||
|
|
|
|
routeMatch.route.action.tls?.mode === 'terminate-and-reencrypt'
|
2025-05-19 17:28:05 +00:00
|
|
|
) && this.httpProxy !== null;
|
2025-03-14 09:53:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-05-19 17:28:05 +00:00
|
|
|
* Forward connection to HttpProxy
|
2025-03-14 09:53:25 +00:00
|
|
|
*/
|
2025-05-19 17:28:05 +00:00
|
|
|
public async forwardToHttpProxy(
|
2025-03-14 09:53:25 +00:00
|
|
|
connectionId: string,
|
|
|
|
socket: plugins.net.Socket,
|
2025-05-09 22:46:53 +00:00
|
|
|
record: IConnectionRecord,
|
2025-05-18 15:38:07 +00:00
|
|
|
initialChunk: Buffer,
|
2025-05-19 17:28:05 +00:00
|
|
|
httpProxyPort: number,
|
2025-05-18 15:38:07 +00:00
|
|
|
cleanupCallback: (reason: string) => void
|
|
|
|
): Promise<void> {
|
2025-05-19 17:28:05 +00:00
|
|
|
if (!this.httpProxy) {
|
|
|
|
throw new Error('HttpProxy not initialized');
|
2025-03-14 09:53:25 +00:00
|
|
|
}
|
2025-05-18 15:38:07 +00:00
|
|
|
|
|
|
|
const proxySocket = new plugins.net.Socket();
|
|
|
|
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
2025-05-19 17:28:05 +00:00
|
|
|
proxySocket.connect(httpProxyPort, 'localhost', () => {
|
|
|
|
console.log(`[${connectionId}] Connected to HttpProxy for termination`);
|
2025-05-18 15:38:07 +00:00
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
|
|
|
|
proxySocket.on('error', reject);
|
2025-03-14 09:53:25 +00:00
|
|
|
});
|
2025-05-18 15:38:07 +00:00
|
|
|
|
|
|
|
// 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'));
|
2025-03-14 09:53:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-05-19 17:28:05 +00:00
|
|
|
* Start HttpProxy
|
2025-03-14 09:53:25 +00:00
|
|
|
*/
|
2025-05-18 15:38:07 +00:00
|
|
|
public async start(): Promise<void> {
|
2025-05-19 17:28:05 +00:00
|
|
|
if (this.httpProxy) {
|
|
|
|
await this.httpProxy.start();
|
2025-03-14 09:53:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2025-05-19 17:28:05 +00:00
|
|
|
* Stop HttpProxy
|
2025-03-14 09:53:25 +00:00
|
|
|
*/
|
2025-05-18 15:38:07 +00:00
|
|
|
public async stop(): Promise<void> {
|
2025-05-19 17:28:05 +00:00
|
|
|
if (this.httpProxy) {
|
|
|
|
await this.httpProxy.stop();
|
|
|
|
this.httpProxy = null;
|
2025-03-14 09:53:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|