fix(smartproxy): Update dynamic port mapping to support
This commit is contained in:
		
							
								
								
									
										15
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,5 +1,20 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 2025-05-14 - 16.0.4 - fix(smartproxy) | ||||||
|  | Update dynamic port mapping to support 'preserve' target port value | ||||||
|  |  | ||||||
|  | - Refactored NetworkProxy to use a default port for 'preserve' values, correctly falling back to the incoming port when target.port is set to 'preserve'. | ||||||
|  | - Updated RequestHandler and WebSocketHandler to check for 'preserve' target port instead of legacy preservePort flag. | ||||||
|  | - Modified IRouteTarget type definitions to allow 'preserve' as a valid target port value. | ||||||
|  |  | ||||||
|  | ## 2025-05-14 - 16.0.4 - fix(smartproxy) | ||||||
|  | Fix dynamic port mapping: update target port resolution to properly handle 'preserve' values across route configurations. Now, when a route's target port is set to 'preserve', the incoming port is used consistently in NetworkProxy, RequestHandler, WebSocketHandler, and RouteConnectionHandler. Also update type definitions in IRouteTarget to support 'preserve'. | ||||||
|  |  | ||||||
|  | - Refactored port resolution in NetworkProxy to use a default port for 'preserve' and then correctly fall back to the incoming port when 'preserve' is specified. | ||||||
|  | - Updated RequestHandler and WebSocketHandler to check if target.port equals 'preserve' instead of using a legacy 'preservePort' flag. | ||||||
|  | - Modified RouteConnectionHandler to correctly resolve dynamic port mappings with 'preserve'. | ||||||
|  | - Updated route type definitions to allow 'preserve' as a valid target port value. | ||||||
|  |  | ||||||
| ## 2025-05-14 - 16.0.3 - fix(network-proxy, route-utils, route-manager) | ## 2025-05-14 - 16.0.3 - fix(network-proxy, route-utils, route-manager) | ||||||
| Normalize IPv6-mapped IPv4 addresses in IP matching functions and remove deprecated legacy configuration methods in NetworkProxy. Update route-utils and route-manager to compare both canonical and IPv6-mapped IP forms, adjust tests accordingly, and clean up legacy exports. | Normalize IPv6-mapped IPv4 addresses in IP matching functions and remove deprecated legacy configuration methods in NetworkProxy. Update route-utils and route-manager to compare both canonical and IPv6-mapped IP forms, adjust tests accordingly, and clean up legacy exports. | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										468
									
								
								docs/porthandling.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										468
									
								
								docs/porthandling.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,468 @@ | |||||||
|  | # SmartProxy Port Handling | ||||||
|  |  | ||||||
|  | This document covers all the port handling capabilities in SmartProxy, including port range specification, dynamic port mapping, and runtime port management. | ||||||
|  |  | ||||||
|  | ## Port Range Syntax | ||||||
|  |  | ||||||
|  | SmartProxy offers flexible port range specification through the `TPortRange` type, which can be defined in three different ways: | ||||||
|  |  | ||||||
|  | ### 1. Single Port | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | // Match a single port | ||||||
|  | { | ||||||
|  |   match: { | ||||||
|  |     ports: 443 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 2. Array of Specific Ports | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | // Match multiple specific ports | ||||||
|  | { | ||||||
|  |   match: { | ||||||
|  |     ports: [80, 443, 8080] | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 3. Port Range | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | // Match a range of ports | ||||||
|  | { | ||||||
|  |   match: { | ||||||
|  |     ports: [{ from: 8000, to: 8100 }] | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 4. Mixed Port Specifications | ||||||
|  |  | ||||||
|  | You can combine different port specification methods in a single rule: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | // Match both specific ports and port ranges | ||||||
|  | { | ||||||
|  |   match: { | ||||||
|  |     ports: [80, 443, { from: 8000, to: 8100 }] | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Port Forwarding Options | ||||||
|  |  | ||||||
|  | SmartProxy offers several ways to handle port forwarding from source to target: | ||||||
|  |  | ||||||
|  | ### 1. Static Port Forwarding | ||||||
|  |  | ||||||
|  | Forward to a fixed target port: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | { | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'backend.example.com', | ||||||
|  |       port: 8080 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 2. Preserve Source Port | ||||||
|  |  | ||||||
|  | Forward to the same port on the target: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | { | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'backend.example.com', | ||||||
|  |       port: 'preserve' | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 3. Dynamic Port Mapping | ||||||
|  |  | ||||||
|  | Use a function to determine the target port based on connection context: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | { | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'backend.example.com', | ||||||
|  |       port: (context) => { | ||||||
|  |         // Calculate port based on request details | ||||||
|  |         return 8000 + (context.port % 100); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Port Selection Context | ||||||
|  |  | ||||||
|  | When using dynamic port mapping functions, you have access to a rich context object that provides details about the connection: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | interface IRouteContext { | ||||||
|  |   // Connection information | ||||||
|  |   port: number;          // The matched incoming port | ||||||
|  |   domain?: string;       // The domain from SNI or Host header | ||||||
|  |   clientIp: string;      // The client's IP address | ||||||
|  |   serverIp: string;      // The server's IP address | ||||||
|  |   path?: string;         // URL path (for HTTP connections) | ||||||
|  |   query?: string;        // Query string (for HTTP connections) | ||||||
|  |   headers?: Record<string, string>; // HTTP headers (for HTTP connections) | ||||||
|  |  | ||||||
|  |   // TLS information | ||||||
|  |   isTls: boolean;        // Whether the connection is TLS | ||||||
|  |   tlsVersion?: string;   // TLS version if applicable | ||||||
|  |  | ||||||
|  |   // Route information | ||||||
|  |   routeName?: string;    // The name of the matched route | ||||||
|  |   routeId?: string;      // The ID of the matched route | ||||||
|  |  | ||||||
|  |   // Additional properties | ||||||
|  |   timestamp: number;     // The request timestamp | ||||||
|  |   connectionId: string;  // Unique connection identifier | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Common Port Mapping Patterns | ||||||
|  |  | ||||||
|  | ### 1. Port Offset Mapping | ||||||
|  |  | ||||||
|  | Forward traffic to target ports with a fixed offset: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | { | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'backend.example.com', | ||||||
|  |       port: (context) => context.port + 1000 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 2. Domain-Based Port Mapping | ||||||
|  |  | ||||||
|  | Forward to different backend ports based on the domain: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | { | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'backend.example.com', | ||||||
|  |       port: (context) => { | ||||||
|  |         switch (context.domain) { | ||||||
|  |           case 'api.example.com': return 8001; | ||||||
|  |           case 'admin.example.com': return 8002; | ||||||
|  |           case 'staging.example.com': return 8003; | ||||||
|  |           default: return 8000; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 3. Load Balancing with Hash-Based Distribution | ||||||
|  |  | ||||||
|  | Distribute connections across a port range using a deterministic hash function: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | { | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'backend.example.com', | ||||||
|  |       port: (context) => { | ||||||
|  |         // Simple hash function to ensure consistent mapping | ||||||
|  |         const hostname = context.domain || ''; | ||||||
|  |         const hash = hostname.split('').reduce((a, b) => a + b.charCodeAt(0), 0); | ||||||
|  |         return 8000 + (hash % 10); // Map to ports 8000-8009 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## IPv6-Mapped IPv4 Compatibility | ||||||
|  |  | ||||||
|  | SmartProxy automatically handles IPv6-mapped IPv4 addresses for optimal compatibility. When a connection from an IPv4 address (e.g., `192.168.1.1`) arrives as an IPv6-mapped address (`::ffff:192.168.1.1`), the system normalizes these addresses for consistent matching. | ||||||
|  |  | ||||||
|  | This is particularly important when: | ||||||
|  |  | ||||||
|  | 1. Matching client IP restrictions in route configurations | ||||||
|  | 2. Preserving source IP for outgoing connections | ||||||
|  | 3. Tracking connections and rate limits | ||||||
|  |  | ||||||
|  | No special configuration is needed - the system handles this normalization automatically. | ||||||
|  |  | ||||||
|  | ## Dynamic Port Management | ||||||
|  |  | ||||||
|  | SmartProxy allows for runtime port configuration changes without requiring a restart. | ||||||
|  |  | ||||||
|  | ### Adding and Removing Ports | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | // Get the SmartProxy instance | ||||||
|  | const proxy = new SmartProxy({ /* config */ }); | ||||||
|  |  | ||||||
|  | // Add a new listening port | ||||||
|  | await proxy.addListeningPort(8081); | ||||||
|  |  | ||||||
|  | // Remove a listening port | ||||||
|  | await proxy.removeListeningPort(8082); | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Runtime Route Updates | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | // Get current routes | ||||||
|  | const currentRoutes = proxy.getRoutes(); | ||||||
|  |  | ||||||
|  | // Add new route for the new port | ||||||
|  | const newRoute = { | ||||||
|  |   name: 'New Dynamic Route', | ||||||
|  |   match: { | ||||||
|  |     ports: 8081, | ||||||
|  |     domains: ['dynamic.example.com'] | ||||||
|  |   }, | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'backend.example.com', | ||||||
|  |       port: 9000 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // Update the route configuration | ||||||
|  | await proxy.updateRoutes([...currentRoutes, newRoute]); | ||||||
|  |  | ||||||
|  | // Remove routes for a specific port | ||||||
|  | const routesWithout8082 = currentRoutes.filter(route => { | ||||||
|  |   const ports = proxy.routeManager.expandPortRange(route.match.ports); | ||||||
|  |   return !ports.includes(8082); | ||||||
|  | }); | ||||||
|  | await proxy.updateRoutes(routesWithout8082); | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Performance Considerations | ||||||
|  |  | ||||||
|  | ### Port Range Expansion | ||||||
|  |  | ||||||
|  | When using large port ranges, SmartProxy uses internal caching to optimize performance. For example, a range like `{ from: 1000, to: 2000 }` is expanded only once and then cached for future use. | ||||||
|  |  | ||||||
|  | ### Port Range Validation | ||||||
|  |  | ||||||
|  | The system automatically validates port ranges to ensure: | ||||||
|  |  | ||||||
|  | 1. Port numbers are within the valid range (1-65535) | ||||||
|  | 2. The "from" value is not greater than the "to" value in range specifications | ||||||
|  | 3. Port ranges do not contain duplicate entries | ||||||
|  |  | ||||||
|  | Invalid port ranges will be logged as warnings and skipped during configuration. | ||||||
|  |  | ||||||
|  | ## Configuration Recipes | ||||||
|  |  | ||||||
|  | ### Global Port Range | ||||||
|  |  | ||||||
|  | Listen on a large range of ports and forward to the same ports on a backend: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | { | ||||||
|  |   name: 'Global port range forwarding', | ||||||
|  |   match: { | ||||||
|  |     ports: [{ from: 8000, to: 9000 }] | ||||||
|  |   }, | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'backend.example.com', | ||||||
|  |       port: 'preserve' | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Domain-Specific Port Ranges | ||||||
|  |  | ||||||
|  | Different port ranges for different domain groups: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | [ | ||||||
|  |   { | ||||||
|  |     name: 'API port range', | ||||||
|  |     match: { | ||||||
|  |       ports: [{ from: 8000, to: 8099 }] | ||||||
|  |     }, | ||||||
|  |     action: { | ||||||
|  |       type: 'forward', | ||||||
|  |       target: { | ||||||
|  |         host: 'api.backend.example.com', | ||||||
|  |         port: 'preserve' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'Admin port range', | ||||||
|  |     match: { | ||||||
|  |       ports: [{ from: 9000, to: 9099 }] | ||||||
|  |     }, | ||||||
|  |     action: { | ||||||
|  |       type: 'forward', | ||||||
|  |       target: { | ||||||
|  |         host: 'admin.backend.example.com', | ||||||
|  |         port: 'preserve' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | ] | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Mixed Internal/External Port Forwarding | ||||||
|  |  | ||||||
|  | Forward specific high-numbered ports to standard ports on internal servers: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | [ | ||||||
|  |   { | ||||||
|  |     name: 'Web server forwarding', | ||||||
|  |     match: { | ||||||
|  |       ports: [8080, 8443] | ||||||
|  |     }, | ||||||
|  |     action: { | ||||||
|  |       type: 'forward', | ||||||
|  |       target: { | ||||||
|  |         host: 'web.internal', | ||||||
|  |         port: (context) => context.port === 8080 ? 80 : 443 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'Database forwarding', | ||||||
|  |     match: { | ||||||
|  |       ports: [15432] | ||||||
|  |     }, | ||||||
|  |     action: { | ||||||
|  |       type: 'forward', | ||||||
|  |       target: { | ||||||
|  |         host: 'db.internal', | ||||||
|  |         port: 5432 | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | ] | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Debugging Port Configurations | ||||||
|  |  | ||||||
|  | When troubleshooting port forwarding issues, enable detailed logging: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | const proxy = new SmartProxy({ | ||||||
|  |   routes: [ /* your routes */ ], | ||||||
|  |   enableDetailedLogging: true | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | This will log: | ||||||
|  | - Port configuration during startup | ||||||
|  | - Port matching decisions during routing | ||||||
|  | - Dynamic port function results | ||||||
|  | - Connection details including source and target ports | ||||||
|  |  | ||||||
|  | ## Port Security Considerations | ||||||
|  |  | ||||||
|  | ### Restricting Ports | ||||||
|  |  | ||||||
|  | For security, you may want to restrict which ports can be accessed by specific clients: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | { | ||||||
|  |   name: 'Restricted port range', | ||||||
|  |   match: { | ||||||
|  |     ports: [{ from: 8000, to: 9000 }], | ||||||
|  |     clientIp: ['10.0.0.0/8'] // Only internal network can access these ports | ||||||
|  |   }, | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'internal.example.com', | ||||||
|  |       port: 'preserve' | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Rate Limiting by Port | ||||||
|  |  | ||||||
|  | Apply different rate limits for different port ranges: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | { | ||||||
|  |   name: 'API ports with rate limiting', | ||||||
|  |   match: { | ||||||
|  |     ports: [{ from: 8000, to: 8100 }] | ||||||
|  |   }, | ||||||
|  |   action: { | ||||||
|  |     type: 'forward', | ||||||
|  |     target: { | ||||||
|  |       host: 'api.example.com', | ||||||
|  |       port: 'preserve' | ||||||
|  |     }, | ||||||
|  |     security: { | ||||||
|  |       rateLimit: { | ||||||
|  |         enabled: true, | ||||||
|  |         maxRequests: 100, | ||||||
|  |         window: 60 // 60 seconds | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Best Practices | ||||||
|  |  | ||||||
|  | 1. **Use Specific Port Ranges**: Instead of large ranges (e.g., 1-65535), use specific ranges for specific purposes | ||||||
|  |  | ||||||
|  | 2. **Prioritize Routes**: When multiple routes could match, use the `priority` field to ensure the most specific route is matched first | ||||||
|  |  | ||||||
|  | 3. **Name Your Routes**: Use descriptive names to make debugging easier, especially when using port ranges | ||||||
|  |  | ||||||
|  | 4. **Use Preserve Port Where Possible**: Using `port: 'preserve'` is more efficient and easier to maintain than creating multiple specific mappings | ||||||
|  |  | ||||||
|  | 5. **Limit Dynamic Port Functions**: While powerful, complex port functions can be harder to debug; prefer simple map or math-based functions | ||||||
|  |  | ||||||
|  | 6. **Use Port Variables**: For complex setups, define your port ranges as variables for easier maintenance: | ||||||
|  |  | ||||||
|  | ```typescript | ||||||
|  | const API_PORTS = [{ from: 8000, to: 8099 }]; | ||||||
|  | const ADMIN_PORTS = [{ from: 9000, to: 9099 }]; | ||||||
|  |  | ||||||
|  | const routes = [ | ||||||
|  |   { | ||||||
|  |     name: 'API Routes', | ||||||
|  |     match: { ports: API_PORTS, /* ... */ }, | ||||||
|  |     // ... | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'Admin Routes', | ||||||
|  |     match: { ports: ADMIN_PORTS, /* ... */ }, | ||||||
|  |     // ... | ||||||
|  |   } | ||||||
|  | ]; | ||||||
|  | ``` | ||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@push.rocks/smartproxy', |   name: '@push.rocks/smartproxy', | ||||||
|   version: '16.0.3', |   version: '16.0.4', | ||||||
|   description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' |   description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -447,6 +447,8 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|  |  | ||||||
|     // Create legacy proxy configs for the router |     // Create legacy proxy configs for the router | ||||||
|     // This is only needed for backward compatibility with ProxyRouter |     // 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 |     // and will be removed in the future | ||||||
|     const legacyConfigs: IReverseProxyConfig[] = []; |     const legacyConfigs: IReverseProxyConfig[] = []; | ||||||
|  |  | ||||||
| @@ -472,7 +474,8 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|         ? route.action.target.host |         ? route.action.target.host | ||||||
|         : [route.action.target.host]; |         : [route.action.target.host]; | ||||||
|  |  | ||||||
|       const targetPort = route.action.target.port; |       // Handle 'preserve' port value | ||||||
|  |       const targetPort = route.action.target.port === 'preserve' ? defaultPort : route.action.target.port; | ||||||
|  |  | ||||||
|       // Get certificate information |       // Get certificate information | ||||||
|       const certData = certificateUpdates.get(domain); |       const certData = certificateUpdates.get(domain); | ||||||
|   | |||||||
| @@ -540,7 +540,7 @@ export class RequestHandler { | |||||||
|             this.logger.debug(`Resolved function-based port to: ${resolvedPort}`); |             this.logger.debug(`Resolved function-based port to: ${resolvedPort}`); | ||||||
|           } |           } | ||||||
|         } else { |         } else { | ||||||
|           targetPort = matchingRoute.action.target.port; |           targetPort = matchingRoute.action.target.port === 'preserve' ? routeContext.port : matchingRoute.action.target.port as number; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Select a single host if an array was provided |         // Select a single host if an array was provided | ||||||
| @@ -760,7 +760,7 @@ export class RequestHandler { | |||||||
|             this.logger.debug(`Resolved HTTP/2 function-based port to: ${resolvedPort}`); |             this.logger.debug(`Resolved HTTP/2 function-based port to: ${resolvedPort}`); | ||||||
|           } |           } | ||||||
|         } else { |         } else { | ||||||
|           targetPort = matchingRoute.action.target.port; |           targetPort = matchingRoute.action.target.port === 'preserve' ? routeContext.port : matchingRoute.action.target.port as number; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Select a single host if an array was provided |         // Select a single host if an array was provided | ||||||
|   | |||||||
| @@ -204,7 +204,7 @@ export class WebSocketHandler { | |||||||
|             targetPort = route.action.target.port(toBaseContext(routeContext)); |             targetPort = route.action.target.port(toBaseContext(routeContext)); | ||||||
|             this.logger.debug(`Resolved function-based port for WebSocket: ${targetPort}`); |             this.logger.debug(`Resolved function-based port for WebSocket: ${targetPort}`); | ||||||
|           } else { |           } else { | ||||||
|             targetPort = route.action.target.port; |             targetPort = route.action.target.port === 'preserve' ? routeContext.port : route.action.target.port as number; | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           // Select a single host if an array was provided |           // Select a single host if an array was provided | ||||||
|   | |||||||
| @@ -69,8 +69,7 @@ export interface IRouteContext { | |||||||
|  */ |  */ | ||||||
| export interface IRouteTarget { | export interface IRouteTarget { | ||||||
|   host: string | string[] | ((context: IRouteContext) => string | string[]);  // Host or hosts with optional function for dynamic resolution |   host: string | string[] | ((context: IRouteContext) => string | string[]);  // Host or hosts with optional function for dynamic resolution | ||||||
|   port: number | ((context: IRouteContext) => number);  // Port with optional function for dynamic mapping |   port: number | 'preserve' | ((context: IRouteContext) => number);  // Port with optional function for dynamic mapping (use 'preserve' to keep the incoming port) | ||||||
|   preservePort?: boolean;   // Use incoming port as target port (ignored if port is a function) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -434,8 +434,8 @@ export class RouteConnectionHandler { | |||||||
|         this.connectionManager.cleanupConnection(record, 'port_mapping_error'); |         this.connectionManager.cleanupConnection(record, 'port_mapping_error'); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|     } else if (action.target.preservePort) { |     } else if (action.target.port === 'preserve') { | ||||||
|       // Use incoming port if preservePort is true |       // Use incoming port if port is 'preserve' | ||||||
|       targetPort = record.localPort; |       targetPort = record.localPort; | ||||||
|     } else { |     } else { | ||||||
|       // Use static port from configuration |       // Use static port from configuration | ||||||
| @@ -525,7 +525,7 @@ export class RouteConnectionHandler { | |||||||
|       let targetPort: number; |       let targetPort: number; | ||||||
|       if (typeof action.target.port === 'function') { |       if (typeof action.target.port === 'function') { | ||||||
|         targetPort = action.target.port(routeContext); |         targetPort = action.target.port(routeContext); | ||||||
|       } else if (action.target.preservePort) { |       } else if (action.target.port === 'preserve') { | ||||||
|         targetPort = record.localPort; |         targetPort = record.localPort; | ||||||
|       } else { |       } else { | ||||||
|         targetPort = action.target.port; |         targetPort = action.target.port; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user