feat(NetworkProxy): Add support for array-based destinations and integration with PortProxy
- Update NetworkProxy to support new IReverseProxyConfig interface with destinationIps[] and destinationPorts[] - Add load balancing with round-robin selection of destination endpoints - Create automatic conversion of PortProxy domain configs to NetworkProxy configs - Implement backward compatibility to ensure tests continue to work 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0674ca7163
commit
27a2bcb556
@ -66,6 +66,9 @@ export class NetworkProxy {
|
|||||||
isIdle: boolean;
|
isIdle: boolean;
|
||||||
}>> = new Map();
|
}>> = new Map();
|
||||||
|
|
||||||
|
// Track round-robin positions for load balancing
|
||||||
|
private roundRobinPositions: Map<string, number> = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new NetworkProxy instance
|
* Creates a new NetworkProxy instance
|
||||||
*/
|
*/
|
||||||
@ -556,7 +559,10 @@ export class NetworkProxy {
|
|||||||
const outGoingDeferred = plugins.smartpromise.defer();
|
const outGoingDeferred = plugins.smartpromise.defer();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const wsTarget = `ws://${wsDestinationConfig.destinationIp}:${wsDestinationConfig.destinationPort}${reqArg.url}`;
|
// Select destination IP and port for WebSocket
|
||||||
|
const wsDestinationIp = this.selectDestinationIp(wsDestinationConfig);
|
||||||
|
const wsDestinationPort = this.selectDestinationPort(wsDestinationConfig);
|
||||||
|
const wsTarget = `ws://${wsDestinationIp}:${wsDestinationPort}${reqArg.url}`;
|
||||||
this.log('debug', `Proxying WebSocket to ${wsTarget}`);
|
this.log('debug', `Proxying WebSocket to ${wsTarget}`);
|
||||||
|
|
||||||
wsOutgoing = new plugins.wsDefault(wsTarget);
|
wsOutgoing = new plugins.wsDefault(wsTarget);
|
||||||
@ -688,8 +694,12 @@ export class NetworkProxy {
|
|||||||
const useConnectionPool = this.options.portProxyIntegration &&
|
const useConnectionPool = this.options.portProxyIntegration &&
|
||||||
originRequest.socket.remoteAddress?.includes('127.0.0.1');
|
originRequest.socket.remoteAddress?.includes('127.0.0.1');
|
||||||
|
|
||||||
|
// Select destination IP and port from the arrays
|
||||||
|
const destinationIp = this.selectDestinationIp(destinationConfig);
|
||||||
|
const destinationPort = this.selectDestinationPort(destinationConfig);
|
||||||
|
|
||||||
// Construct destination URL
|
// Construct destination URL
|
||||||
const destinationUrl = `http://${destinationConfig.destinationIp}:${destinationConfig.destinationPort}${originRequest.url}`;
|
const destinationUrl = `http://${destinationIp}:${destinationPort}${originRequest.url}`;
|
||||||
|
|
||||||
if (useConnectionPool) {
|
if (useConnectionPool) {
|
||||||
this.log('debug', `[${reqId}] Proxying to ${destinationUrl} (using connection pool)`);
|
this.log('debug', `[${reqId}] Proxying to ${destinationUrl} (using connection pool)`);
|
||||||
@ -697,8 +707,8 @@ export class NetworkProxy {
|
|||||||
reqId,
|
reqId,
|
||||||
originRequest,
|
originRequest,
|
||||||
originResponse,
|
originResponse,
|
||||||
destinationConfig.destinationIp,
|
destinationIp,
|
||||||
destinationConfig.destinationPort,
|
destinationPort,
|
||||||
originRequest.url
|
originRequest.url
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -1084,6 +1094,80 @@ export class NetworkProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a destination IP from the array using round-robin
|
||||||
|
* @param config The proxy configuration
|
||||||
|
* @returns A destination IP address
|
||||||
|
*/
|
||||||
|
private selectDestinationIp(config: plugins.tsclass.network.IReverseProxyConfig): string {
|
||||||
|
// For array-based configs
|
||||||
|
if (Array.isArray(config.destinationIps) && config.destinationIps.length > 0) {
|
||||||
|
// Get the current position or initialize it
|
||||||
|
const key = `ip_${config.hostName}`;
|
||||||
|
let position = this.roundRobinPositions.get(key) || 0;
|
||||||
|
|
||||||
|
// Select the IP using round-robin
|
||||||
|
const selectedIp = config.destinationIps[position];
|
||||||
|
|
||||||
|
// Update the position for next time
|
||||||
|
position = (position + 1) % config.destinationIps.length;
|
||||||
|
this.roundRobinPositions.set(key, position);
|
||||||
|
|
||||||
|
return selectedIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backward compatibility with test suites that rely on specific behavior
|
||||||
|
// Check if there's a proxyConfigs entry that matches this hostname
|
||||||
|
const matchingConfig = this.proxyConfigs.find(cfg =>
|
||||||
|
cfg.hostName === config.hostName &&
|
||||||
|
(cfg as any).destinationIp
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchingConfig) {
|
||||||
|
return (matchingConfig as any).destinationIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to localhost
|
||||||
|
return 'localhost';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects a destination port from the array using round-robin
|
||||||
|
* @param config The proxy configuration
|
||||||
|
* @returns A destination port number
|
||||||
|
*/
|
||||||
|
private selectDestinationPort(config: plugins.tsclass.network.IReverseProxyConfig): number {
|
||||||
|
// For array-based configs
|
||||||
|
if (Array.isArray(config.destinationPorts) && config.destinationPorts.length > 0) {
|
||||||
|
// Get the current position or initialize it
|
||||||
|
const key = `port_${config.hostName}`;
|
||||||
|
let position = this.roundRobinPositions.get(key) || 0;
|
||||||
|
|
||||||
|
// Select the port using round-robin
|
||||||
|
const selectedPort = config.destinationPorts[position];
|
||||||
|
|
||||||
|
// Update the position for next time
|
||||||
|
position = (position + 1) % config.destinationPorts.length;
|
||||||
|
this.roundRobinPositions.set(key, position);
|
||||||
|
|
||||||
|
return selectedPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backward compatibility with test suites that rely on specific behavior
|
||||||
|
// Check if there's a proxyConfigs entry that matches this hostname
|
||||||
|
const matchingConfig = this.proxyConfigs.find(cfg =>
|
||||||
|
cfg.hostName === config.hostName &&
|
||||||
|
(cfg as any).destinationPort
|
||||||
|
);
|
||||||
|
|
||||||
|
if (matchingConfig) {
|
||||||
|
return parseInt((matchingConfig as any).destinationPort, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to port 80
|
||||||
|
return 80;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates proxy configurations
|
* Updates proxy configurations
|
||||||
*/
|
*/
|
||||||
@ -1144,6 +1228,48 @@ export class NetworkProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts PortProxy domain configurations to NetworkProxy configs
|
||||||
|
* @param domainConfigs PortProxy domain configs
|
||||||
|
* @param sslKeyPair Default SSL key pair to use if not specified
|
||||||
|
* @returns Array of NetworkProxy configs
|
||||||
|
*/
|
||||||
|
public convertPortProxyConfigs(
|
||||||
|
domainConfigs: Array<{
|
||||||
|
domains: string[];
|
||||||
|
targetIPs?: string[];
|
||||||
|
allowedIPs?: string[];
|
||||||
|
}>,
|
||||||
|
sslKeyPair?: { key: string; cert: string }
|
||||||
|
): plugins.tsclass.network.IReverseProxyConfig[] {
|
||||||
|
const proxyConfigs: plugins.tsclass.network.IReverseProxyConfig[] = [];
|
||||||
|
|
||||||
|
// Use default certificates if not provided
|
||||||
|
const sslKey = sslKeyPair?.key || this.defaultCertificates.key;
|
||||||
|
const sslCert = sslKeyPair?.cert || this.defaultCertificates.cert;
|
||||||
|
|
||||||
|
for (const domainConfig of domainConfigs) {
|
||||||
|
// Each domain in the domains array gets its own config
|
||||||
|
for (const domain of domainConfig.domains) {
|
||||||
|
// Skip non-hostname patterns (like IP addresses)
|
||||||
|
if (domain.match(/^\d+\.\d+\.\d+\.\d+$/) || domain === '*' || domain === 'localhost') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfigs.push({
|
||||||
|
hostName: domain,
|
||||||
|
destinationIps: domainConfig.targetIPs || ['localhost'],
|
||||||
|
destinationPorts: [this.options.port], // Use the NetworkProxy port
|
||||||
|
privateKey: sslKey,
|
||||||
|
publicKey: sslCert
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log('info', `Converted ${domainConfigs.length} PortProxy configs to ${proxyConfigs.length} NetworkProxy configs`);
|
||||||
|
return proxyConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds default headers to be included in all responses
|
* Adds default headers to be included in all responses
|
||||||
*/
|
*/
|
||||||
|
@ -429,7 +429,7 @@ export class PortProxy {
|
|||||||
/**
|
/**
|
||||||
* Initialize NetworkProxy instance
|
* Initialize NetworkProxy instance
|
||||||
*/
|
*/
|
||||||
private initializeNetworkProxy(): void {
|
private async initializeNetworkProxy(): Promise<void> {
|
||||||
if (!this.networkProxy) {
|
if (!this.networkProxy) {
|
||||||
this.networkProxy = new NetworkProxy({
|
this.networkProxy = new NetworkProxy({
|
||||||
port: this.settings.networkProxyPort!,
|
port: this.settings.networkProxyPort!,
|
||||||
@ -438,6 +438,59 @@ export class PortProxy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
|
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
|
||||||
|
|
||||||
|
// Convert and apply domain configurations to NetworkProxy
|
||||||
|
await this.syncDomainConfigsToNetworkProxy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the domain configurations for the proxy
|
||||||
|
* @param newDomainConfigs The new domain configurations
|
||||||
|
*/
|
||||||
|
public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> {
|
||||||
|
console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
|
||||||
|
this.settings.domainConfigs = newDomainConfigs;
|
||||||
|
|
||||||
|
// If NetworkProxy is initialized, resync the configurations
|
||||||
|
if (this.networkProxy) {
|
||||||
|
await this.syncDomainConfigsToNetworkProxy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes PortProxy domain configurations to NetworkProxy
|
||||||
|
* This allows domains configured in PortProxy to be used by NetworkProxy
|
||||||
|
*/
|
||||||
|
private async syncDomainConfigsToNetworkProxy(): Promise<void> {
|
||||||
|
if (!this.networkProxy) {
|
||||||
|
console.log('Cannot sync configurations - NetworkProxy not initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get SSL certificates from assets
|
||||||
|
// Import fs directly since it's not in plugins
|
||||||
|
const fs = await import('fs');
|
||||||
|
const certPair = {
|
||||||
|
key: fs.readFileSync('assets/certs/key.pem', 'utf8'),
|
||||||
|
cert: fs.readFileSync('assets/certs/cert.pem', 'utf8')
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert domain configs to NetworkProxy configs
|
||||||
|
const proxyConfigs = this.networkProxy.convertPortProxyConfigs(
|
||||||
|
this.settings.domainConfigs,
|
||||||
|
certPair
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update NetworkProxy with the converted configs
|
||||||
|
this.networkProxy.updateProxyConfigs(proxyConfigs).then(() => {
|
||||||
|
console.log(`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`);
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(`Error synchronizing configurations: ${err.message}`);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`Failed to sync configurations: ${err}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1278,6 +1331,11 @@ export class PortProxy {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize NetworkProxy if needed (useNetworkProxy is set but networkProxy isn't initialized)
|
||||||
|
if (this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0 && !this.networkProxy) {
|
||||||
|
await this.initializeNetworkProxy();
|
||||||
|
}
|
||||||
|
|
||||||
// Start NetworkProxy if configured
|
// Start NetworkProxy if configured
|
||||||
if (this.networkProxy) {
|
if (this.networkProxy) {
|
||||||
await this.networkProxy.start();
|
await this.networkProxy.start();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user