Refactor routing and proxy components for improved structure and compatibility

- Removed deprecated route utility functions in favor of direct matcher usage.
- Updated imports to reflect new module structure for routing utilities.
- Consolidated route manager functionality into SharedRouteManager for better consistency.
- Eliminated legacy routing methods and interfaces, streamlining the HttpProxy and associated components.
- Enhanced WebSocket and HTTP request handling to utilize the new unified HttpRouter.
- Updated route matching logic to leverage matcher classes for domain, path, and header checks.
- Cleaned up legacy compatibility code across various modules, ensuring a more maintainable codebase.
This commit is contained in:
Juergen Kunz
2025-06-03 16:21:09 +00:00
parent cf70b6ace5
commit 2a75e7c490
21 changed files with 148 additions and 881 deletions

View File

@ -13,7 +13,6 @@ export interface IAcmeOptions {
skipConfiguredCerts?: boolean;
}
import type { IRouteConfig } from '../../smart-proxy/models/route-types.js';
import type { IRouteContext } from '../../../core/models/route-context.js';
/**
* Configuration options for HttpProxy
@ -34,7 +33,6 @@ export interface IHttpProxyOptions {
// Settings for SmartProxy integration
connectionPoolSize?: number; // Maximum connections to maintain in the pool to each backend
portProxyIntegration?: boolean; // Flag to indicate this proxy is used by SmartProxy
useExternalPort80Handler?: boolean; // @deprecated - use SmartCertManager instead
// Protocol to use when proxying to backends: HTTP/1.x or HTTP/2
backendProtocol?: 'http1' | 'http2';
@ -58,253 +56,7 @@ export interface ICertificateEntry {
expires?: Date;
}
/**
* @deprecated Use IRouteConfig instead. This interface will be removed in a future release.
*
* IMPORTANT: This is a legacy interface maintained only for backward compatibility.
* New code should use IRouteConfig for all configuration purposes.
*
* @see IRouteConfig for the modern, recommended configuration format
*/
export interface IReverseProxyConfig {
/** Target hostnames/IPs to proxy requests to */
destinationIps: string[];
/** Target ports to proxy requests to */
destinationPorts: number[];
/** Hostname to match for routing */
hostName: string;
/** SSL private key for this host (PEM format) */
privateKey: string;
/** SSL public key/certificate for this host (PEM format) */
publicKey: string;
/** Basic authentication configuration */
authentication?: {
type: 'Basic';
user: string;
pass: string;
};
/** Whether to rewrite the Host header to match the target */
rewriteHostHeader?: boolean;
/**
* Protocol to use when proxying to this backend: 'http1' or 'http2'.
* Overrides the global backendProtocol option if set.
*/
backendProtocol?: 'http1' | 'http2';
}
/**
* Route manager for NetworkProxy
* Handles route matching and configuration
*/
export class RouteManager {
private routes: IRouteConfig[] = [];
private logger: ILogger;
constructor(logger: ILogger) {
this.logger = logger;
}
/**
* Update the routes configuration
*/
public updateRoutes(routes: IRouteConfig[]): void {
// Sort routes by priority (higher first)
this.routes = [...routes].sort((a, b) => {
const priorityA = a.priority ?? 0;
const priorityB = b.priority ?? 0;
return priorityB - priorityA;
});
this.logger.info(`Updated RouteManager with ${this.routes.length} routes`);
}
/**
* Get all routes
*/
public getRoutes(): IRouteConfig[] {
return [...this.routes];
}
/**
* Find the first matching route for a context
*/
public findMatchingRoute(context: IRouteContext): IRouteConfig | null {
for (const route of this.routes) {
if (this.matchesRoute(route, context)) {
return route;
}
}
return null;
}
/**
* Check if a route matches the given context
*/
private matchesRoute(route: IRouteConfig, context: IRouteContext): boolean {
// Skip disabled routes
if (route.enabled === false) {
return false;
}
// Check domain match if specified
if (route.match.domains && context.domain) {
const domains = Array.isArray(route.match.domains)
? route.match.domains
: [route.match.domains];
if (!domains.some(domainPattern => this.matchDomain(domainPattern, context.domain!))) {
return false;
}
}
// Check path match if specified
if (route.match.path && context.path) {
if (!this.matchPath(route.match.path, context.path)) {
return false;
}
}
// Check client IP match if specified
if (route.match.clientIp && context.clientIp) {
if (!route.match.clientIp.some(ip => this.matchIp(ip, context.clientIp))) {
return false;
}
}
// Check TLS version match if specified
if (route.match.tlsVersion && context.tlsVersion) {
if (!route.match.tlsVersion.includes(context.tlsVersion)) {
return false;
}
}
// All criteria matched
return true;
}
/**
* Match a domain pattern against a domain
*/
private matchDomain(pattern: string, domain: string): boolean {
if (pattern === domain) {
return true;
}
if (pattern.includes('*')) {
const regexPattern = pattern
.replace(/\./g, '\\.')
.replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`, 'i');
return regex.test(domain);
}
return false;
}
/**
* Match a path pattern against a path
*/
private matchPath(pattern: string, path: string): boolean {
if (pattern === path) {
return true;
}
if (pattern.endsWith('*')) {
const prefix = pattern.slice(0, -1);
return path.startsWith(prefix);
}
return false;
}
/**
* Match an IP pattern against an IP
* Supports exact matches, wildcard patterns, and CIDR notation
*/
private matchIp(pattern: string, ip: string): boolean {
// Exact match
if (pattern === ip) {
return true;
}
// Wildcard matching (e.g., 192.168.0.*)
if (pattern.includes('*')) {
const regexPattern = pattern
.replace(/\./g, '\\.')
.replace(/\*/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(ip);
}
// CIDR matching (e.g., 192.168.0.0/24)
if (pattern.includes('/')) {
try {
const [subnet, bits] = pattern.split('/');
// Convert IP addresses to numeric format for comparison
const ipBinary = this.ipToBinary(ip);
const subnetBinary = this.ipToBinary(subnet);
if (!ipBinary || !subnetBinary) {
return false;
}
// Get the subnet mask from CIDR notation
const mask = parseInt(bits, 10);
if (isNaN(mask) || mask < 0 || mask > 32) {
return false;
}
// Check if the first 'mask' bits match between IP and subnet
return ipBinary.slice(0, mask) === subnetBinary.slice(0, mask);
} catch (error) {
// If we encounter any error during CIDR matching, return false
return false;
}
}
return false;
}
/**
* Convert an IP address to its binary representation
* @param ip The IP address to convert
* @returns Binary string representation or null if invalid
*/
private ipToBinary(ip: string): string | null {
// Handle IPv4 addresses only for now
const parts = ip.split('.');
// Validate IP format
if (parts.length !== 4) {
return null;
}
// Convert each octet to 8-bit binary and concatenate
try {
return parts
.map(part => {
const num = parseInt(part, 10);
if (isNaN(num) || num < 0 || num > 255) {
throw new Error('Invalid IP octet');
}
return num.toString(2).padStart(8, '0');
})
.join('');
} catch (error) {
return null;
}
}
}
/**
* Interface for connection tracking in the pool