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

@ -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.

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

View File

@ -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<string, any> = {
':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();
}
}

View File

@ -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

View File

@ -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';

View File

@ -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';

View File

@ -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';
/**

View File

@ -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';

View File

@ -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<string, string> = {};
for (const [key, value] of Object.entries(route.match.headers)) {
stringHeaders[key] = value instanceof RegExp ? value.source : value;
}
return HeaderMatcher.matchAll(stringHeaders, headers);
}
/**