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:
@ -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
|
||||
|
Reference in New Issue
Block a user