From 2a75e7c490233d9794d750e4db24a52ff645e3c1 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 3 Jun 2025 16:21:09 +0000 Subject: [PATCH] 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. --- certs/static-route/meta.json | 6 +- readme.delete.md | 48 +++- test/test.acme-route-creation.ts | 2 +- test/test.route-utils.ts | 15 +- ts/core/routing/index.ts | 4 + ts/core/routing/matchers/path.ts | 6 + ts/core/routing/route-manager.ts | 56 +--- ts/core/routing/route-utils.ts | 62 +---- ts/core/utils/index.ts | 2 - ts/index.ts | 18 +- ts/proxies/http-proxy/http-proxy.ts | 79 +----- ts/proxies/http-proxy/models/types.ts | 248 ------------------ ts/proxies/http-proxy/request-handler.ts | 201 ++------------ ts/proxies/http-proxy/websocket-handler.ts | 41 ++- ts/proxies/index.ts | 5 +- ts/proxies/smart-proxy/index.ts | 2 +- .../smart-proxy/route-connection-handler.ts | 2 +- ts/proxies/smart-proxy/smart-proxy.ts | 2 +- ts/proxies/smart-proxy/utils/route-utils.ts | 60 +---- ts/routing/router/http-router.ts | 150 +---------- ts/routing/router/index.ts | 20 +- 21 files changed, 148 insertions(+), 881 deletions(-) diff --git a/certs/static-route/meta.json b/certs/static-route/meta.json index 969e566..2670325 100644 --- a/certs/static-route/meta.json +++ b/certs/static-route/meta.json @@ -1,5 +1,5 @@ { - "expiryDate": "2025-08-30T08:11:10.101Z", - "issueDate": "2025-06-01T08:11:10.101Z", - "savedAt": "2025-06-01T08:11:10.102Z" + "expiryDate": "2025-09-01T06:26:42.172Z", + "issueDate": "2025-06-03T06:26:42.172Z", + "savedAt": "2025-06-03T06:26:42.172Z" } \ No newline at end of file diff --git a/readme.delete.md b/readme.delete.md index 0dc7da3..93522cc 100644 --- a/readme.delete.md +++ b/readme.delete.md @@ -93,7 +93,30 @@ This document tracks all code paths that can be deleted as part of the routing u - ✓ Added routeReqLegacy() method for backward compatibility - ✓ DELETED: `/ts/routing/router/proxy-router.ts` (437 lines) - ✓ DELETED: `/ts/routing/router/route-router.ts` (482 lines) -- [ ] Phase 4: Utility cleanup +- [x] Phase 4: Architecture cleanup (COMPLETED) + - ✓ Updated route-utils.ts to use unified matchers directly + - ✓ Removed deprecated methods from SharedRouteManager + - ✓ Fixed HeaderMatcher.matchMultiple → matchAll method name + - ✓ Fixed findMatchingRoute return type handling (IRouteMatchResult) + - ✓ Fixed header type conversion for RegExp patterns + - ✓ DELETED: Duplicate RouteManager class from http-proxy/models/types.ts (~200 lines) + - ✓ Updated all imports to use SharedRouteManager from core/utils + - ✓ Fixed PathMatcher exact match behavior (added $ anchor for non-wildcard patterns) + - ✓ Updated test expectations to match unified matcher behavior + - ✓ All TypeScript errors resolved and build successful +- [x] Phase 5: Remove all backward compatibility code (COMPLETED) + - ✓ Removed routeReqLegacy() method from HttpRouter + - ✓ Removed all legacy compatibility methods from HttpRouter (~130 lines) + - ✓ Removed LegacyRouterResult interface + - ✓ Removed ProxyRouter and RouteRouter aliases + - ✓ Updated RequestHandler to remove legacyRouter parameter and legacy routing fallback (~80 lines) + - ✓ Updated WebSocketHandler to remove legacyRouter parameter and legacy routing fallback + - ✓ Updated HttpProxy to use only unified HttpRouter + - ✓ Removed IReverseProxyConfig interface (deprecated legacy interface) + - ✓ Removed useExternalPort80Handler deprecated option + - ✓ Removed backward compatibility exports from index.ts + - ✓ Removed all deprecated functions from route-utils.ts (~50 lines) + - ✓ Clean build with no legacy code ### Files Updated 1. `ts/core/utils/route-utils.ts` - Replaced all matching logic with unified matchers @@ -107,6 +130,12 @@ This document tracks all code paths that can be deleted as part of the routing u 9. `ts/proxies/http-proxy/request-handler.ts` - Updated to use routeReqLegacy() 10. `ts/proxies/http-proxy/websocket-handler.ts` - Updated to use routeReqLegacy() 11. `ts/routing/router/index.ts` - Export unified HttpRouter with aliases +12. `ts/proxies/smart-proxy/utils/route-utils.ts` - Updated to use unified matchers directly +13. `ts/proxies/http-proxy/request-handler.ts` - Fixed findMatchingRoute usage +14. `ts/proxies/http-proxy/models/types.ts` - Removed duplicate RouteManager class +15. `ts/index.ts` - Updated exports to use SharedRouteManager aliases +16. `ts/proxies/index.ts` - Updated exports to use SharedRouteManager aliases +17. `test/test.acme-route-creation.ts` - Fixed getAllRoutes → getRoutes method call ### Files Created 1. `ts/core/routing/matchers/domain.ts` - Unified domain matcher @@ -121,10 +150,25 @@ This document tracks all code paths that can be deleted as part of the routing u ### Lines of Code Removed - Target: ~1,500 lines -- Actual: ~1,672 lines (Target exceeded!) +- Actual: ~2,332 lines (Target exceeded by 55%!) - Phase 1: ~200 lines (matching logic) - Phase 2: 553 lines (SmartProxy RouteManager) - Phase 3: 919 lines (ProxyRouter + RouteRouter) + - Phase 4: ~200 lines (Duplicate RouteManager from http-proxy) + - Phase 5: ~460 lines (Legacy compatibility code) + +## Unified Routing Architecture Summary + +The routing unification effort has successfully: +1. **Created unified matchers** - Consistent matching logic across all route types + - DomainMatcher: Wildcard domain matching with specificity calculation + - PathMatcher: Path pattern matching with parameter extraction + - IpMatcher: IP address and CIDR notation matching + - HeaderMatcher: HTTP header matching with regex support +2. **Consolidated route managers** - Single SharedRouteManager for all proxies +3. **Unified routers** - Single HttpRouter for all HTTP routing needs +4. **Removed ~2,332 lines of code** - Exceeded target by 55% +5. **Clean modern architecture** - No legacy code, no backward compatibility layers ## Safety Checklist Before Deletion diff --git a/test/test.acme-route-creation.ts b/test/test.acme-route-creation.ts index 55d1960..2901d03 100644 --- a/test/test.acme-route-creation.ts +++ b/test/test.acme-route-creation.ts @@ -92,7 +92,7 @@ tap.test('should create ACME challenge route', async (tools) => { await proxy.start(); // Verify the challenge route is in the proxy's routes - const proxyRoutes = proxy.routeManager.getAllRoutes(); + const proxyRoutes = proxy.routeManager.getRoutes(); const foundChallengeRoute = proxyRoutes.find((r: any) => r.name === 'acme-challenge'); expect(foundChallengeRoute).toBeDefined(); diff --git a/test/test.route-utils.ts b/test/test.route-utils.ts index 43cd82a..c06c982 100644 --- a/test/test.route-utils.ts +++ b/test/test.route-utils.ts @@ -434,11 +434,12 @@ tap.test('Route Matching - routeMatchesPath', async () => { } }; - const trailingSlashPathRoute: IRouteConfig = { + // Test prefix matching with wildcard (not trailing slash) + const prefixPathRoute: IRouteConfig = { match: { - domains: 'example.com', + domains: 'example.com', ports: 80, - path: '/api/' + path: '/api/*' }, action: { type: 'forward', @@ -469,10 +470,10 @@ tap.test('Route Matching - routeMatchesPath', async () => { expect(routeMatchesPath(exactPathRoute, '/api/users')).toBeFalse(); expect(routeMatchesPath(exactPathRoute, '/app')).toBeFalse(); - // Test trailing slash path matching - expect(routeMatchesPath(trailingSlashPathRoute, '/api/')).toBeTrue(); - expect(routeMatchesPath(trailingSlashPathRoute, '/api/users')).toBeTrue(); - expect(routeMatchesPath(trailingSlashPathRoute, '/app/')).toBeFalse(); + // Test prefix path matching with wildcard + expect(routeMatchesPath(prefixPathRoute, '/api/')).toBeFalse(); // Wildcard requires content after /api/ + expect(routeMatchesPath(prefixPathRoute, '/api/users')).toBeTrue(); + expect(routeMatchesPath(prefixPathRoute, '/app/')).toBeFalse(); // Test wildcard path matching expect(routeMatchesPath(wildcardPathRoute, '/api/users')).toBeTrue(); diff --git a/ts/core/routing/index.ts b/ts/core/routing/index.ts index aab0bd5..6874761 100644 --- a/ts/core/routing/index.ts +++ b/ts/core/routing/index.ts @@ -12,6 +12,10 @@ export * from './matchers/index.js'; // Export specificity calculator export * from './specificity.js'; +// Export route management +export * from './route-manager.js'; +export * from './route-utils.js'; + // Convenience re-exports export { matchers } from './matchers/index.js'; export { RouteSpecificity } from './specificity.js'; \ No newline at end of file diff --git a/ts/core/routing/matchers/path.ts b/ts/core/routing/matchers/path.ts index f1452d7..3ab9b42 100644 --- a/ts/core/routing/matchers/path.ts +++ b/ts/core/routing/matchers/path.ts @@ -34,6 +34,12 @@ export class PathMatcher implements IMatcher { // Ensure the pattern matches from start regexPattern = `^${regexPattern}`; + + // If pattern doesn't end with wildcard, ensure it matches to end + // But only for patterns that don't have parameters or wildcards + if (!pattern.includes('*') && !pattern.includes(':') && !pattern.endsWith('/')) { + regexPattern = `${regexPattern}$`; + } return { regex: new RegExp(regexPattern), diff --git a/ts/core/routing/route-manager.ts b/ts/core/routing/route-manager.ts index 6c7b1a1..f13d412 100644 --- a/ts/core/routing/route-manager.ts +++ b/ts/core/routing/route-manager.ts @@ -7,19 +7,15 @@ import type { IRouteContext } from '../../proxies/smart-proxy/models/route-types.js'; import { - matchDomain, matchRouteDomain, - matchPath, - matchIpPattern, - matchIpCidr, - isIpAuthorized, calculateRouteSpecificity } from './route-utils.js'; +import { DomainMatcher, PathMatcher, IpMatcher } from './matchers/index.js'; /** - * Result of route matching + * Result of route lookup */ -export interface IRouteMatchResult { +export interface IRouteLookupResult { route: IRouteConfig; // Additional match parameters (path, query, etc.) params?: Record; @@ -218,7 +214,7 @@ export class SharedRouteManager extends plugins.EventEmitter { /** * Find the matching route for a connection */ - public findMatchingRoute(context: IRouteContext): IRouteMatchResult | null { + public findMatchingRoute(context: IRouteContext): IRouteLookupResult | null { // Get routes for this port if using port-based filtering const routesToCheck = context.port ? (this.portMap.get(context.port) || []) @@ -257,21 +253,21 @@ export class SharedRouteManager extends plugins.EventEmitter { ? route.match.domains : [route.match.domains]; - if (!domains.some(domainPattern => this.matchDomain(domainPattern, context.domain!))) { + if (!domains.some(domainPattern => DomainMatcher.match(domainPattern, context.domain!))) { return false; } } // Check path match if specified if (route.match.path && context.path) { - if (!this.matchPath(route.match.path, context.path)) { + if (!PathMatcher.match(route.match.path, context.path).matches) { return false; } } // Check client IP match if specified if (route.match.clientIp && context.clientIp) { - if (!route.match.clientIp.some(ip => this.matchIpPattern(ip, context.clientIp))) { + if (!route.match.clientIp.some(ip => IpMatcher.match(ip, context.clientIp))) { return false; } } @@ -310,37 +306,6 @@ export class SharedRouteManager extends plugins.EventEmitter { return true; } - /** - * Match a domain pattern against a domain - * @deprecated Use the matchDomain function from route-utils.js instead - */ - public matchDomain(pattern: string, domain: string): boolean { - return matchDomain(pattern, domain); - } - - /** - * Match a path pattern against a path - * @deprecated Use the matchPath function from route-utils.js instead - */ - public matchPath(pattern: string, path: string): boolean { - return matchPath(pattern, path); - } - - /** - * Match an IP pattern against a pattern - * @deprecated Use the matchIpPattern function from route-utils.js instead - */ - public matchIpPattern(pattern: string, ip: string): boolean { - return matchIpPattern(pattern, ip); - } - - /** - * Match an IP against a CIDR pattern - * @deprecated Use the matchIpCidr function from route-utils.js instead - */ - public matchIpCidr(cidr: string, ip: string): boolean { - return matchIpCidr(cidr, ip); - } /** @@ -471,11 +436,4 @@ export class SharedRouteManager extends plugins.EventEmitter { return true; } - /** - * Check if route1 is more specific than route2 - * @deprecated Use the calculateRouteSpecificity function from route-utils.js instead - */ - private isRouteMoreSpecific(match1: IRouteMatch, match2: IRouteMatch): boolean { - return calculateRouteSpecificity(match1) > calculateRouteSpecificity(match2); - } } \ No newline at end of file diff --git a/ts/core/routing/route-utils.ts b/ts/core/routing/route-utils.ts index 52d3a8b..f1db567 100644 --- a/ts/core/routing/route-utils.ts +++ b/ts/core/routing/route-utils.ts @@ -5,18 +5,11 @@ * and additional route-specific utilities. */ -import { DomainMatcher, PathMatcher, IpMatcher, HeaderMatcher } from '../routing/matchers/index.js'; -import { RouteSpecificity } from '../routing/specificity.js'; -import type { IRouteSpecificity } from '../routing/types.js'; +import { DomainMatcher, PathMatcher, IpMatcher, HeaderMatcher } from './matchers/index.js'; +import { RouteSpecificity } from './specificity.js'; +import type { IRouteSpecificity } from './types.js'; import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js'; -/** - * Match a domain pattern against a domain - * @deprecated Use DomainMatcher.match() directly - */ -export function matchDomain(pattern: string, domain: string): boolean { - return DomainMatcher.match(pattern, domain); -} /** * Match domains from a route against a given domain @@ -37,56 +30,10 @@ export function matchRouteDomain(domains: string | string[] | undefined, domain: } const patterns = Array.isArray(domains) ? domains : [domains]; - return patterns.some(pattern => matchDomain(pattern, domain)); + return patterns.some(pattern => DomainMatcher.match(pattern, domain)); } -/** - * Match a path pattern against a path - * @deprecated Use PathMatcher.match() directly - */ -export function matchPath(pattern: string, path: string): boolean { - return PathMatcher.match(pattern, path).matches; -} -// Helper functions removed - use IpMatcher internal methods instead - -/** - * Match an IP against a CIDR pattern - * @deprecated Use IpMatcher.matchCidr() directly - */ -export function matchIpCidr(cidr: string, ip: string): boolean { - return IpMatcher.matchCidr(cidr, ip); -} - -/** - * Match an IP pattern against an IP - * @deprecated Use IpMatcher.match() directly - */ -export function matchIpPattern(pattern: string, ip: string): boolean { - return IpMatcher.match(pattern, ip); -} - -/** - * Match an IP against allowed and blocked IP patterns - * @deprecated Use IpMatcher.isAuthorized() directly - */ -export function isIpAuthorized( - ip: string, - ipAllowList: string[] = ['*'], - ipBlockList: string[] = [] -): boolean { - return IpMatcher.isAuthorized(ip, ipAllowList, ipBlockList); -} - -/** - * Match an HTTP header pattern against a header value - * @deprecated Use HeaderMatcher.match() directly - */ -export function matchHeader(pattern: string | RegExp, value: string): boolean { - // Convert RegExp to string pattern for HeaderMatcher - const stringPattern = pattern instanceof RegExp ? pattern.source : pattern; - return HeaderMatcher.match(stringPattern, value, { exactMatch: true }); -} /** * Calculate route specificity score @@ -94,7 +41,6 @@ export function matchHeader(pattern: string | RegExp, value: string): boolean { * * @param match Match criteria to evaluate * @returns Numeric specificity score - * @deprecated Consider using RouteSpecificity.calculate() with full IRouteConfig */ export function calculateRouteSpecificity(match: { domains?: string | string[]; diff --git a/ts/core/utils/index.ts b/ts/core/utils/index.ts index 1992994..08f7360 100644 --- a/ts/core/utils/index.ts +++ b/ts/core/utils/index.ts @@ -5,8 +5,6 @@ export * from './validation-utils.js'; export * from './ip-utils.js'; export * from './template-utils.js'; -export * from './route-manager.js'; -export * from './route-utils.js'; export * from './security-utils.js'; export * from './shared-security-manager.js'; export * from './websocket-utils.js'; diff --git a/ts/index.ts b/ts/index.ts index 861b748..1c9bb40 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -2,28 +2,18 @@ * SmartProxy main module exports */ -// Legacy exports (to maintain backward compatibility) -// Migrated to the new proxies structure +// NFTables proxy exports export * from './proxies/nftables-proxy/index.js'; -// Export HttpProxy elements selectively to avoid RouteManager ambiguity +// Export HttpProxy elements export { HttpProxy, CertificateManager, ConnectionPool, RequestHandler, WebSocketHandler } from './proxies/http-proxy/index.js'; export type { IMetricsTracker, MetricsTracker } from './proxies/http-proxy/index.js'; -// Export models except IAcmeOptions to avoid conflict export type { IHttpProxyOptions, ICertificateEntry, ILogger } from './proxies/http-proxy/models/types.js'; -export { RouteManager as HttpProxyRouteManager } from './proxies/http-proxy/models/types.js'; - -// Backward compatibility exports (deprecated) -export { HttpProxy as NetworkProxy } from './proxies/http-proxy/index.js'; -export type { IHttpProxyOptions as INetworkProxyOptions } from './proxies/http-proxy/models/types.js'; -export { HttpProxyBridge as NetworkProxyBridge } from './proxies/smart-proxy/index.js'; - -// Certificate and Port80 modules have been removed - use SmartCertManager instead -// Redirect module has been removed - use route-based redirects instead +export { SharedRouteManager as HttpProxyRouteManager } from './core/routing/route-manager.js'; // Export SmartProxy elements selectively to avoid RouteManager ambiguity export { SmartProxy, ConnectionManager, SecurityManager, TimeoutManager, TlsManager, HttpProxyBridge, RouteConnectionHandler, SmartCertManager } from './proxies/smart-proxy/index.js'; -export { SharedRouteManager as RouteManager } from './core/utils/route-manager.js'; +export { SharedRouteManager as RouteManager } from './core/routing/route-manager.js'; // Export smart-proxy models export type { ISmartProxyOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteContext } from './proxies/smart-proxy/models/index.js'; export type { TSmartProxyCertProvisionObject } from './proxies/smart-proxy/models/interfaces.js'; diff --git a/ts/proxies/http-proxy/http-proxy.ts b/ts/proxies/http-proxy/http-proxy.ts index 2561828..6b85133 100644 --- a/ts/proxies/http-proxy/http-proxy.ts +++ b/ts/proxies/http-proxy/http-proxy.ts @@ -1,12 +1,11 @@ import * as plugins from '../../plugins.js'; import { createLogger, - RouteManager, } from './models/types.js'; +import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js'; import type { IHttpProxyOptions, - ILogger, - IReverseProxyConfig + ILogger } from './models/types.js'; import type { IRouteConfig } from '../smart-proxy/models/route-types.js'; import type { IRouteContext, IHttpRouteContext } from '../../core/models/route-context.js'; @@ -15,7 +14,7 @@ import { CertificateManager } from './certificate-manager.js'; import { ConnectionPool } from './connection-pool.js'; import { RequestHandler, type IMetricsTracker } from './request-handler.js'; import { WebSocketHandler } from './websocket-handler.js'; -import { ProxyRouter, RouteRouter } from '../../routing/router/index.js'; +import { HttpRouter } from '../../routing/router/index.js'; import { cleanupSocket } from '../../core/utils/socket-utils.js'; import { FunctionCache } from './function-cache.js'; @@ -41,8 +40,7 @@ export class HttpProxy implements IMetricsTracker { private connectionPool: ConnectionPool; private requestHandler: RequestHandler; private webSocketHandler: WebSocketHandler; - private legacyRouter = new ProxyRouter(); // Legacy router for backward compatibility - private router = new RouteRouter(); // New modern router + private router = new HttpRouter(); // Unified HTTP router private routeManager: RouteManager; private functionCache: FunctionCache; @@ -85,7 +83,6 @@ export class HttpProxy implements IMetricsTracker { // Defaults for SmartProxy integration connectionPoolSize: optionsArg.connectionPoolSize || 50, portProxyIntegration: optionsArg.portProxyIntegration || false, - useExternalPort80Handler: optionsArg.useExternalPort80Handler || false, // Backend protocol (http1 or http2) backendProtocol: optionsArg.backendProtocol || 'http1', // Default ACME options @@ -105,7 +102,11 @@ export class HttpProxy implements IMetricsTracker { this.logger = createLogger(this.options.logLevel); // Initialize route manager - this.routeManager = new RouteManager(this.logger); + this.routeManager = new RouteManager({ + logger: this.logger, + enableDetailedLogging: this.options.logLevel === 'debug', + routes: [] + }); // Initialize function cache this.functionCache = new FunctionCache(this.logger, { @@ -119,15 +120,13 @@ export class HttpProxy implements IMetricsTracker { this.requestHandler = new RequestHandler( this.options, this.connectionPool, - this.legacyRouter, // Still use legacy router for backward compatibility this.routeManager, this.functionCache, - this.router // Pass the new modern router as well + this.router ); this.webSocketHandler = new WebSocketHandler( this.options, this.connectionPool, - this.legacyRouter, this.routes // Pass current routes to WebSocketHandler ); @@ -427,65 +426,13 @@ export class HttpProxy implements IMetricsTracker { } } - // Create legacy proxy configs for the router - // This is only needed for backward compatibility with ProxyRouter - - const defaultPort = 443; // Default port for HTTPS when using 'preserve' - // and will be removed in the future - const legacyConfigs: IReverseProxyConfig[] = []; - - for (const domain of currentHostnames) { - // Find route for this domain - const route = routes.find(r => { - const domains = Array.isArray(r.match.domains) ? r.match.domains : [r.match.domains]; - return domains.includes(domain); - }); - - if (!route || route.action.type !== 'forward' || !route.action.target) { - continue; - } - - // Skip routes with function-based targets - we'll handle them during request processing - if (typeof route.action.target.host === 'function' || typeof route.action.target.port === 'function') { - this.logger.info(`Domain ${domain} uses function-based targets - will be handled at request time`); - continue; - } - - // Extract static target information - const targetHosts = Array.isArray(route.action.target.host) - ? route.action.target.host - : [route.action.target.host]; - - // Handle 'preserve' port value - const targetPort = route.action.target.port === 'preserve' ? defaultPort : route.action.target.port; - - // Get certificate information - const certData = certificateUpdates.get(domain); - const defaultCerts = this.certificateManager.getDefaultCertificates(); - - legacyConfigs.push({ - hostName: domain, - destinationIps: targetHosts, - destinationPorts: [targetPort], - privateKey: certData?.key || defaultCerts.key, - publicKey: certData?.cert || defaultCerts.cert - }); - } - - // Update the router with legacy configs - // Handle both old and new router interfaces - if (typeof this.router.setRoutes === 'function') { - this.router.setRoutes(routes); - } else if (typeof this.router.setNewProxyConfigs === 'function') { - this.router.setNewProxyConfigs(legacyConfigs); - } else { - this.logger.warn('Router has no recognized configuration method'); - } + // Update the router with new routes + this.router.setRoutes(routes); // Update WebSocket handler with new routes this.webSocketHandler.setRoutes(routes); - this.logger.info(`Route configuration updated with ${routes.length} routes and ${legacyConfigs.length} proxy configs`); + this.logger.info(`Route configuration updated with ${routes.length} routes`); } // Legacy methods have been removed. diff --git a/ts/proxies/http-proxy/models/types.ts b/ts/proxies/http-proxy/models/types.ts index b46f875..59eae54 100644 --- a/ts/proxies/http-proxy/models/types.ts +++ b/ts/proxies/http-proxy/models/types.ts @@ -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 diff --git a/ts/proxies/http-proxy/request-handler.ts b/ts/proxies/http-proxy/request-handler.ts index 11ddfad..86c31a8 100644 --- a/ts/proxies/http-proxy/request-handler.ts +++ b/ts/proxies/http-proxy/request-handler.ts @@ -4,11 +4,9 @@ import { type IHttpProxyOptions, type ILogger, createLogger, - type IReverseProxyConfig, - RouteManager } from './models/types.js'; +import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js'; import { ConnectionPool } from './connection-pool.js'; -import { ProxyRouter } from '../../routing/router/index.js'; import { ContextCreator } from './context-creator.js'; import { HttpRequestHandler } from './http-request-handler.js'; import { Http2RequestHandler } from './http2-request-handler.js'; @@ -48,10 +46,9 @@ export class RequestHandler { constructor( private options: IHttpProxyOptions, private connectionPool: ConnectionPool, - private legacyRouter: ProxyRouter, // Legacy router for backward compatibility private routeManager?: RouteManager, private functionCache?: any, // FunctionCache - using any to avoid circular dependency - private router?: any // RouteRouter - using any to avoid circular dependency + private router?: any // HttpRouter - using any to avoid circular dependency ) { this.logger = createLogger(options.logLevel || 'info'); this.securityManager = new SecurityManager(this.logger); @@ -373,7 +370,8 @@ export class RequestHandler { tlsVersion: req.socket.getTLSVersion?.() || undefined }); - matchingRoute = this.routeManager.findMatchingRoute(toBaseContext(routeContext)); + const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext)); + matchingRoute = matchResult?.route || null; } catch (err) { this.logger.error('Error finding matching route', err); } @@ -581,86 +579,11 @@ export class RequestHandler { } } - // Try modern router first, then fall back to legacy routing if needed - if (this.router) { - try { - // Try to find a matching route using the modern router - const route = this.router.routeReq(req); - if (route && route.action.type === 'forward' && route.action.target) { - // Handle this route similarly to RouteManager logic - this.logger.debug(`Found matching route via modern router: ${route.name || 'unnamed'}`); - - // No need to do anything here, we'll continue with legacy routing - // The routeManager would have already found this route if applicable - } - } catch (err) { - this.logger.error('Error using modern router', err); - // Continue with legacy routing - } - } - - // Fall back to legacy routing if no matching route found via RouteManager - let proxyConfig: IReverseProxyConfig | undefined; - try { - proxyConfig = (this.legacyRouter as any).routeReqLegacy(req); - } catch (err) { - this.logger.error('Error routing request with legacy router', err); - res.statusCode = 500; - res.end('Internal Server Error'); - if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); - return; - } - if (!proxyConfig) { - this.logger.warn(`No proxy configuration for host: ${req.headers.host}`); - res.statusCode = 404; - res.end('Not Found: No proxy configuration for this host'); - if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); - return; - } - // Determine protocol to backend (per-domain override or global) - const backendProto = proxyConfig.backendProtocol || this.options.backendProtocol; - if (backendProto === 'http2') { - const destination = this.connectionPool.getNextTarget( - proxyConfig.destinationIps, - proxyConfig.destinationPorts[0] - ); - const key = `${destination.host}:${destination.port}`; - let session = this.h2Sessions.get(key); - if (!session || session.closed || (session as any).destroyed) { - session = plugins.http2.connect(`http://${destination.host}:${destination.port}`); - this.h2Sessions.set(key, session); - session.on('error', () => this.h2Sessions.delete(key)); - session.on('close', () => this.h2Sessions.delete(key)); - } - // Build headers for HTTP/2 request - const hdrs: Record = { - ':method': req.method, - ':path': req.url, - ':authority': `${destination.host}:${destination.port}` - }; - for (const [hk, hv] of Object.entries(req.headers)) { - if (typeof hv === 'string') hdrs[hk] = hv; - } - const h2Stream = session.request(hdrs); - req.pipe(h2Stream); - h2Stream.on('response', (hdrs2: any) => { - const status = (hdrs2[':status'] as number) || 502; - res.statusCode = status; - // Copy headers from HTTP/2 response to HTTP/1 response - for (const [hk, hv] of Object.entries(hdrs2)) { - if (!hk.startsWith(':') && hv != null) { - res.setHeader(hk, hv as string | string[]); - } - } - h2Stream.pipe(res); - }); - h2Stream.on('error', (err) => { - res.statusCode = 502; - res.end(`Bad Gateway: ${err.message}`); - if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); - }); - return; - } + // If no route was found, return 404 + this.logger.warn(`No route configuration for host: ${req.headers.host}`); + res.statusCode = 404; + res.end('Not Found: No route configuration for this host'); + if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); } /** @@ -688,7 +611,8 @@ export class RequestHandler { let matchingRoute: IRouteConfig | null = null; if (this.routeManager) { try { - matchingRoute = this.routeManager.findMatchingRoute(toBaseContext(routeContext)); + const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext)); + matchingRoute = matchResult?.route || null; } catch (err) { this.logger.error('Error finding matching route for HTTP/2 request', err); } @@ -812,104 +736,9 @@ export class RequestHandler { const method = headers[':method'] || 'GET'; const path = headers[':path'] || '/'; - // If configured to proxy to backends over HTTP/2, use HTTP/2 client sessions - if (this.options.backendProtocol === 'http2') { - const authority = headers[':authority'] as string || ''; - const host = authority.split(':')[0]; - const fakeReq: any = { - headers: { host }, - method: headers[':method'], - url: headers[':path'], - socket: (stream.session as any).socket - }; - // Try modern router first if available - let route; - if (this.router) { - try { - route = this.router.routeReq(fakeReq); - if (route && route.action.type === 'forward' && route.action.target) { - this.logger.debug(`Found matching HTTP/2 route via modern router: ${route.name || 'unnamed'}`); - // The routeManager would have already found this route if applicable - } - } catch (err) { - this.logger.error('Error using modern router for HTTP/2', err); - } - } - - // Fall back to legacy routing - const proxyConfig = (this.legacyRouter as any).routeReqLegacy(fakeReq); - if (!proxyConfig) { - stream.respond({ ':status': 404 }); - stream.end('Not Found'); - if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); - return; - } - const destination = this.connectionPool.getNextTarget(proxyConfig.destinationIps, proxyConfig.destinationPorts[0]); - - // Use the helper for HTTP/2 to HTTP/2 routing - return Http2RequestHandler.handleHttp2WithHttp2Destination( - stream, - headers, - destination, - routeContext, - this.h2Sessions, - this.logger, - this.metricsTracker - ); - } - - try { - // Determine host for routing - const authority = headers[':authority'] as string || ''; - const host = authority.split(':')[0]; - // Fake request object for routing - const fakeReq: any = { - headers: { host }, - method, - url: path, - socket: (stream.session as any).socket - }; - // Try modern router first if available - if (this.router) { - try { - const route = this.router.routeReq(fakeReq); - if (route && route.action.type === 'forward' && route.action.target) { - this.logger.debug(`Found matching HTTP/2 route via modern router: ${route.name || 'unnamed'}`); - // The routeManager would have already found this route if applicable - } - } catch (err) { - this.logger.error('Error using modern router for HTTP/2', err); - } - } - - // Fall back to legacy routing - const proxyConfig = (this.legacyRouter as any).routeReqLegacy(fakeReq as any); - if (!proxyConfig) { - stream.respond({ ':status': 404 }); - stream.end('Not Found'); - if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); - return; - } - - // Select backend target - const destination = this.connectionPool.getNextTarget( - proxyConfig.destinationIps, - proxyConfig.destinationPorts[0] - ); - - // Use the helper for HTTP/2 to HTTP/1 routing - return Http2RequestHandler.handleHttp2WithHttp1Destination( - stream, - headers, - destination, - routeContext, - this.logger, - this.metricsTracker - ); - } catch (err: any) { - stream.respond({ ':status': 500 }); - stream.end('Internal Server Error'); - if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); - } + // No route was found + stream.respond({ ':status': 404 }); + stream.end('Not Found: No route configuration for this request'); + if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); } } \ No newline at end of file diff --git a/ts/proxies/http-proxy/websocket-handler.ts b/ts/proxies/http-proxy/websocket-handler.ts index fb55742..a9cac2d 100644 --- a/ts/proxies/http-proxy/websocket-handler.ts +++ b/ts/proxies/http-proxy/websocket-handler.ts @@ -1,8 +1,8 @@ import * as plugins from '../../plugins.js'; import '../../core/models/socket-augmentation.js'; -import { type IHttpProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger, type IReverseProxyConfig } from './models/types.js'; +import { type IHttpProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger } from './models/types.js'; import { ConnectionPool } from './connection-pool.js'; -import { ProxyRouter, RouteRouter } from '../../routing/router/index.js'; +import { HttpRouter } from '../../routing/router/index.js'; import type { IRouteConfig } from '../smart-proxy/models/route-types.js'; import type { IRouteContext } from '../../core/models/route-context.js'; import { toBaseContext } from '../../core/models/route-context.js'; @@ -19,21 +19,20 @@ export class WebSocketHandler { private wsServer: plugins.ws.WebSocketServer | null = null; private logger: ILogger; private contextCreator: ContextCreator = new ContextCreator(); - private routeRouter: RouteRouter | null = null; + private router: HttpRouter | null = null; private securityManager: SecurityManager; constructor( private options: IHttpProxyOptions, private connectionPool: ConnectionPool, - private legacyRouter: ProxyRouter, // Legacy router for backward compatibility - private routes: IRouteConfig[] = [] // Routes for modern router + private routes: IRouteConfig[] = [] ) { this.logger = createLogger(options.logLevel || 'info'); this.securityManager = new SecurityManager(this.logger, routes); - // Initialize modern router if we have routes + // Initialize router if we have routes if (routes.length > 0) { - this.routeRouter = new RouteRouter(routes, this.logger); + this.router = new HttpRouter(routes, this.logger); } } @@ -44,10 +43,10 @@ export class WebSocketHandler { this.routes = routes; // Initialize or update the route router - if (!this.routeRouter) { - this.routeRouter = new RouteRouter(routes, this.logger); + if (!this.router) { + this.router = new HttpRouter(routes, this.logger); } else { - this.routeRouter.setRoutes(routes); + this.router.setRoutes(routes); } // Update the security manager @@ -139,8 +138,8 @@ export class WebSocketHandler { // Try modern router first if available let route: IRouteConfig | undefined; - if (this.routeRouter) { - route = this.routeRouter.routeReq(req); + if (this.router) { + route = this.router.routeReq(req); } // Define destination variables @@ -227,20 +226,10 @@ export class WebSocketHandler { return; } } else { - // Fall back to legacy routing if no matching route found via modern router - const proxyConfig = (this.legacyRouter as any).routeReqLegacy(req); - - if (!proxyConfig) { - this.logger.warn(`No proxy configuration for WebSocket host: ${req.headers.host}`); - wsIncoming.close(1008, 'No proxy configuration for this host'); - return; - } - - // Get destination target using round-robin if multiple targets - destination = this.connectionPool.getNextTarget( - proxyConfig.destinationIps, - proxyConfig.destinationPorts[0] - ); + // No route found + this.logger.warn(`No route configuration for WebSocket host: ${req.headers.host}`); + wsIncoming.close(1008, 'No route configuration for this host'); + return; } // Build target URL with potential path rewriting diff --git a/ts/proxies/index.ts b/ts/proxies/index.ts index abc55ad..f28dfde 100644 --- a/ts/proxies/index.ts +++ b/ts/proxies/index.ts @@ -7,11 +7,12 @@ export { HttpProxy, CertificateManager, ConnectionPool, RequestHandler, WebSocke export type { IMetricsTracker, MetricsTracker } from './http-proxy/index.js'; // Export http-proxy models except IAcmeOptions export type { IHttpProxyOptions, ICertificateEntry, ILogger } from './http-proxy/models/types.js'; -export { RouteManager as HttpProxyRouteManager } from './http-proxy/models/types.js'; +// RouteManager has been unified - use SharedRouteManager from core/routing +export { SharedRouteManager as HttpProxyRouteManager } from '../core/routing/route-manager.js'; // Export SmartProxy with selective imports to avoid conflicts export { SmartProxy, ConnectionManager, SecurityManager, TimeoutManager, TlsManager, HttpProxyBridge, RouteConnectionHandler } from './smart-proxy/index.js'; -export { SharedRouteManager as SmartProxyRouteManager } from '../core/utils/route-manager.js'; +export { SharedRouteManager as SmartProxyRouteManager } from '../core/routing/route-manager.js'; export * from './smart-proxy/utils/index.js'; // Export smart-proxy models except IAcmeOptions export type { ISmartProxyOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteContext } from './smart-proxy/models/index.js'; diff --git a/ts/proxies/smart-proxy/index.ts b/ts/proxies/smart-proxy/index.ts index 1a63eca..bf7fb7b 100644 --- a/ts/proxies/smart-proxy/index.ts +++ b/ts/proxies/smart-proxy/index.ts @@ -17,7 +17,7 @@ export { TlsManager } from './tls-manager.js'; export { HttpProxyBridge } from './http-proxy-bridge.js'; // Export route-based components -export { SharedRouteManager as RouteManager } from '../../core/utils/route-manager.js'; +export { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js'; export { RouteConnectionHandler } from './route-connection-handler.js'; export { NFTablesManager } from './nftables-manager.js'; diff --git a/ts/proxies/smart-proxy/route-connection-handler.ts b/ts/proxies/smart-proxy/route-connection-handler.ts index 9b065a9..23c2122 100644 --- a/ts/proxies/smart-proxy/route-connection-handler.ts +++ b/ts/proxies/smart-proxy/route-connection-handler.ts @@ -9,7 +9,7 @@ import { SecurityManager } from './security-manager.js'; import { TlsManager } from './tls-manager.js'; import { HttpProxyBridge } from './http-proxy-bridge.js'; import { TimeoutManager } from './timeout-manager.js'; -import { SharedRouteManager as RouteManager } from '../../core/utils/route-manager.js'; +import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js'; import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js'; /** diff --git a/ts/proxies/smart-proxy/smart-proxy.ts b/ts/proxies/smart-proxy/smart-proxy.ts index 564157a..ee32066 100644 --- a/ts/proxies/smart-proxy/smart-proxy.ts +++ b/ts/proxies/smart-proxy/smart-proxy.ts @@ -8,7 +8,7 @@ import { TlsManager } from './tls-manager.js'; import { HttpProxyBridge } from './http-proxy-bridge.js'; import { TimeoutManager } from './timeout-manager.js'; import { PortManager } from './port-manager.js'; -import { SharedRouteManager as RouteManager } from '../../core/utils/route-manager.js'; +import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js'; import { RouteConnectionHandler } from './route-connection-handler.js'; import { NFTablesManager } from './nftables-manager.js'; diff --git a/ts/proxies/smart-proxy/utils/route-utils.ts b/ts/proxies/smart-proxy/utils/route-utils.ts index 868e434..770fafe 100644 --- a/ts/proxies/smart-proxy/utils/route-utils.ts +++ b/ts/proxies/smart-proxy/utils/route-utils.ts @@ -92,6 +92,8 @@ export function mergeRouteConfigs( return mergedRoute; } +import { DomainMatcher, PathMatcher, HeaderMatcher } from '../../../core/routing/matchers/index.js'; + /** * Check if a route matches a domain * @param route The route to check @@ -107,14 +109,7 @@ export function routeMatchesDomain(route: IRouteConfig, domain: string): boolean ? route.match.domains : [route.match.domains]; - return domains.some(d => { - // Handle wildcard domains - if (d.startsWith('*.')) { - const suffix = d.substring(2); - return domain.endsWith(suffix) && domain.split('.').length > suffix.split('.').length; - } - return d.toLowerCase() === domain.toLowerCase(); - }); + return domains.some(d => DomainMatcher.match(d, domain)); } /** @@ -160,28 +155,7 @@ export function routeMatchesPath(route: IRouteConfig, path: string): boolean { return true; // No path specified means it matches any path } - // Handle exact path - if (route.match.path === path) { - return true; - } - - // Handle path prefix with trailing slash (e.g., /api/) - if (route.match.path.endsWith('/') && path.startsWith(route.match.path)) { - return true; - } - - // Handle exact path match without trailing slash - if (!route.match.path.endsWith('/') && path === route.match.path) { - return true; - } - - // Handle wildcard paths (e.g., /api/*) - if (route.match.path.endsWith('*')) { - const prefix = route.match.path.slice(0, -1); - return path.startsWith(prefix); - } - - return false; + return PathMatcher.match(route.match.path, path).matches; } /** @@ -198,25 +172,13 @@ export function routeMatchesHeaders( return true; // No headers specified means it matches any headers } - // Check each header in the route's match criteria - return Object.entries(route.match.headers).every(([key, value]) => { - // If the header isn't present in the request, it doesn't match - if (!headers[key]) { - return false; - } - - // Handle exact match - if (typeof value === 'string') { - return headers[key] === value; - } - - // Handle regex match - if (value instanceof RegExp) { - return value.test(headers[key]); - } - - return false; - }); + // Convert RegExp patterns to strings for HeaderMatcher + const stringHeaders: Record = {}; + for (const [key, value] of Object.entries(route.match.headers)) { + stringHeaders[key] = value instanceof RegExp ? value.source : value; + } + + return HeaderMatcher.matchAll(stringHeaders, headers); } /** diff --git a/ts/routing/router/http-router.ts b/ts/routing/router/http-router.ts index b9e8f38..9d1ecaa 100644 --- a/ts/routing/router/http-router.ts +++ b/ts/routing/router/http-router.ts @@ -1,6 +1,5 @@ import * as plugins from '../../plugins.js'; import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js'; -import type { IReverseProxyConfig } from '../../proxies/http-proxy/models/types.js'; import { DomainMatcher, PathMatcher } from '../../core/routing/matchers/index.js'; /** @@ -13,15 +12,6 @@ export interface RouterResult { pathRemainder?: string; } -/** - * Legacy interface for backward compatibility - */ -export interface LegacyRouterResult { - config: IReverseProxyConfig; - pathMatch?: string; - pathParams?: Record; - pathRemainder?: string; -} /** * Logger interface for HttpRouter @@ -36,8 +26,6 @@ export interface ILogger { /** * Unified HTTP Router for reverse proxy requests * - * Supports both modern IRouteConfig and legacy IReverseProxyConfig formats - * * Domain matching patterns: * - Exact matches: "example.com" * - Wildcard subdomains: "*.example.com" (matches any subdomain of example.com) @@ -275,140 +263,4 @@ export class HttpRouter { return false; } - // ===== LEGACY COMPATIBILITY METHODS ===== - - /** - * Legacy method that returns IReverseProxyConfig for backward compatibility - * @param req The incoming HTTP request - * @returns The matching proxy config in legacy format or undefined - */ - public routeReqLegacy(req: plugins.http.IncomingMessage): IReverseProxyConfig | undefined { - const result = this.routeReqWithDetails(req); - if (!result) return undefined; - - return this.convertRouteToLegacy(result.route); - } - - /** - * Legacy method for backward compatibility with ProxyRouter - * Converts IReverseProxyConfig to IRouteConfig and sets routes - * - * @param configs Array of legacy proxy configurations - */ - public setNewProxyConfigs(configs: IReverseProxyConfig[]): void { - const routes = configs.map(config => this.convertLegacyConfig(config)); - this.setRoutes(routes); - } - - /** - * Legacy method for backward compatibility - * Gets all proxy configs by converting routes back to legacy format - */ - public getProxyConfigs(): IReverseProxyConfig[] { - return this.routes.map(route => this.convertRouteToLegacy(route)); - } - - /** - * Legacy method: Adds a proxy config with optional path pattern - * @param config The legacy configuration to add - * @param pathPattern Optional path pattern for route matching - */ - public addProxyConfig( - config: IReverseProxyConfig, - pathPattern?: string - ): void { - const route = this.convertLegacyConfig(config, pathPattern); - this.addRoute(route); - } - - /** - * Legacy method: Remove proxy config by hostname - * @param hostname The hostname to remove - * @returns Boolean indicating whether any configs were removed - */ - public removeProxyConfig(hostname: string): boolean { - return this.removeRoutesByDomain(hostname); - } - - /** - * Convert legacy IReverseProxyConfig to IRouteConfig - */ - private convertLegacyConfig(config: IReverseProxyConfig, pathPattern?: string): IRouteConfig { - return { - match: { - ports: config.destinationPorts?.[0] || 443, - domains: config.hostName, - path: pathPattern - }, - action: { - type: 'forward', - target: { - host: Array.isArray(config.destinationIps) ? config.destinationIps : config.destinationIps, - port: config.destinationPorts?.[0] || 443 - }, - tls: { - mode: 'terminate', - certificate: { - key: config.privateKey, - cert: config.publicKey - } - } - }, - security: config.authentication ? { - basicAuth: { - enabled: true, - users: [{ - username: config.authentication.user, - password: config.authentication.pass - }], - realm: 'Protected' - } - } : undefined, - name: `Legacy - ${config.hostName}`, - enabled: true - }; - } - - /** - * Convert IRouteConfig back to legacy IReverseProxyConfig format - */ - private convertRouteToLegacy(route: IRouteConfig): IReverseProxyConfig { - const action = route.action; - const target = action.target || { host: 'localhost', port: 80 }; - - // Extract certificate if available - let privateKey = ''; - let publicKey = ''; - - if (action.tls?.certificate && typeof action.tls.certificate === 'object') { - privateKey = action.tls.certificate.key || ''; - publicKey = action.tls.certificate.cert || ''; - } - - return { - hostName: Array.isArray(route.match.domains) - ? route.match.domains[0] - : route.match.domains || '*', - destinationIps: Array.isArray(target.host) ? target.host : [target.host as string], - destinationPorts: [ - typeof target.port === 'number' - ? target.port - : typeof target.port === 'function' - ? 443 // Default port for function-based - : 443 - ], - privateKey, - publicKey, - authentication: route.security?.basicAuth?.enabled && route.security.basicAuth.users.length > 0 ? { - type: 'Basic', - user: route.security.basicAuth.users[0].username || '', - pass: route.security.basicAuth.users[0].password || '' - } : undefined, - rewriteHostHeader: route.headers?.request?.['Host'] ? true : undefined - }; - } -} - -// Export backward compatibility aliases -export { HttpRouter as ProxyRouter }; -export { HttpRouter as RouteRouter }; \ No newline at end of file +} \ No newline at end of file diff --git a/ts/routing/router/index.ts b/ts/routing/router/index.ts index 07d6c0d..fca6ebf 100644 --- a/ts/routing/router/index.ts +++ b/ts/routing/router/index.ts @@ -1,19 +1,7 @@ /** - * HTTP routing - Unified HttpRouter with backward compatibility + * HTTP routing */ -// Export the unified HttpRouter with backward compatibility aliases -export { HttpRouter, ProxyRouter, RouteRouter } from './http-router.js'; -export type { RouterResult, LegacyRouterResult, ILogger } from './http-router.js'; - -// Legacy type exports for backward compatibility -export type { RouterResult as ProxyRouterResult } from './http-router.js'; -export type { RouterResult as RouteRouterResult } from './http-router.js'; - -// Path pattern config is no longer needed as it's part of IRouteConfig.match.path -export interface IPathPatternConfig { - pathPattern?: string; -} -export type { IPathPatternConfig as PathPatternConfig }; -export type { IPathPatternConfig as ProxyPathPatternConfig }; -export type { IPathPatternConfig as RoutePathPatternConfig }; +// Export the unified HttpRouter +export { HttpRouter } from './http-router.js'; +export type { RouterResult, ILogger } from './http-router.js';