@push.rocks/smartproxy 16.0.2 → 16.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/core/models/index.d.ts +2 -0
  3. package/dist_ts/core/models/index.js +3 -1
  4. package/dist_ts/core/models/route-context.d.ts +62 -0
  5. package/dist_ts/core/models/route-context.js +43 -0
  6. package/dist_ts/core/models/socket-augmentation.d.ts +12 -0
  7. package/dist_ts/core/models/socket-augmentation.js +18 -0
  8. package/dist_ts/core/utils/event-system.d.ts +200 -0
  9. package/dist_ts/core/utils/event-system.js +224 -0
  10. package/dist_ts/core/utils/index.d.ts +7 -0
  11. package/dist_ts/core/utils/index.js +8 -1
  12. package/dist_ts/core/utils/route-manager.d.ts +118 -0
  13. package/dist_ts/core/utils/route-manager.js +383 -0
  14. package/dist_ts/core/utils/route-utils.d.ts +94 -0
  15. package/dist_ts/core/utils/route-utils.js +264 -0
  16. package/dist_ts/core/utils/security-utils.d.ts +111 -0
  17. package/dist_ts/core/utils/security-utils.js +212 -0
  18. package/dist_ts/core/utils/shared-security-manager.d.ts +110 -0
  19. package/dist_ts/core/utils/shared-security-manager.js +252 -0
  20. package/dist_ts/core/utils/template-utils.d.ts +37 -0
  21. package/dist_ts/core/utils/template-utils.js +104 -0
  22. package/dist_ts/core/utils/websocket-utils.d.ts +23 -0
  23. package/dist_ts/core/utils/websocket-utils.js +86 -0
  24. package/dist_ts/http/router/index.d.ts +5 -1
  25. package/dist_ts/http/router/index.js +4 -2
  26. package/dist_ts/http/router/route-router.d.ts +108 -0
  27. package/dist_ts/http/router/route-router.js +393 -0
  28. package/dist_ts/index.d.ts +8 -2
  29. package/dist_ts/index.js +10 -3
  30. package/dist_ts/proxies/index.d.ts +7 -2
  31. package/dist_ts/proxies/index.js +10 -4
  32. package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +21 -0
  33. package/dist_ts/proxies/network-proxy/certificate-manager.js +92 -1
  34. package/dist_ts/proxies/network-proxy/context-creator.d.ts +34 -0
  35. package/dist_ts/proxies/network-proxy/context-creator.js +108 -0
  36. package/dist_ts/proxies/network-proxy/function-cache.d.ts +90 -0
  37. package/dist_ts/proxies/network-proxy/function-cache.js +198 -0
  38. package/dist_ts/proxies/network-proxy/http-request-handler.d.ts +40 -0
  39. package/dist_ts/proxies/network-proxy/http-request-handler.js +256 -0
  40. package/dist_ts/proxies/network-proxy/http2-request-handler.d.ts +24 -0
  41. package/dist_ts/proxies/network-proxy/http2-request-handler.js +201 -0
  42. package/dist_ts/proxies/network-proxy/models/types.d.ts +73 -1
  43. package/dist_ts/proxies/network-proxy/models/types.js +242 -1
  44. package/dist_ts/proxies/network-proxy/network-proxy.d.ts +23 -20
  45. package/dist_ts/proxies/network-proxy/network-proxy.js +149 -60
  46. package/dist_ts/proxies/network-proxy/request-handler.d.ts +38 -5
  47. package/dist_ts/proxies/network-proxy/request-handler.js +584 -198
  48. package/dist_ts/proxies/network-proxy/security-manager.d.ts +65 -0
  49. package/dist_ts/proxies/network-proxy/security-manager.js +255 -0
  50. package/dist_ts/proxies/network-proxy/websocket-handler.d.ts +13 -2
  51. package/dist_ts/proxies/network-proxy/websocket-handler.js +238 -20
  52. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
  53. package/dist_ts/proxies/smart-proxy/index.js +3 -3
  54. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +3 -5
  55. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +56 -4
  56. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +4 -57
  57. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +19 -228
  58. package/dist_ts/proxies/smart-proxy/port-manager.d.ts +81 -0
  59. package/dist_ts/proxies/smart-proxy/port-manager.js +166 -0
  60. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +5 -0
  61. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +131 -15
  62. package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +3 -1
  63. package/dist_ts/proxies/smart-proxy/route-helpers/index.js +5 -3
  64. package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +5 -178
  65. package/dist_ts/proxies/smart-proxy/route-helpers.js +8 -296
  66. package/dist_ts/proxies/smart-proxy/route-manager.d.ts +11 -2
  67. package/dist_ts/proxies/smart-proxy/route-manager.js +79 -10
  68. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +29 -2
  69. package/dist_ts/proxies/smart-proxy/smart-proxy.js +48 -43
  70. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +67 -1
  71. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +120 -1
  72. package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +3 -3
  73. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +27 -5
  74. package/package.json +1 -1
  75. package/readme.md +102 -14
  76. package/readme.plan.md +103 -168
  77. package/ts/00_commitinfo_data.ts +1 -1
  78. package/ts/core/models/index.ts +2 -0
  79. package/ts/core/models/route-context.ts +113 -0
  80. package/ts/core/models/socket-augmentation.ts +33 -0
  81. package/ts/core/utils/event-system.ts +376 -0
  82. package/ts/core/utils/index.ts +7 -0
  83. package/ts/core/utils/route-manager.ts +489 -0
  84. package/ts/core/utils/route-utils.ts +312 -0
  85. package/ts/core/utils/security-utils.ts +309 -0
  86. package/ts/core/utils/shared-security-manager.ts +333 -0
  87. package/ts/core/utils/template-utils.ts +124 -0
  88. package/ts/core/utils/websocket-utils.ts +81 -0
  89. package/ts/http/router/index.ts +8 -1
  90. package/ts/http/router/route-router.ts +482 -0
  91. package/ts/index.ts +14 -2
  92. package/ts/proxies/index.ts +12 -3
  93. package/ts/proxies/network-proxy/certificate-manager.ts +114 -10
  94. package/ts/proxies/network-proxy/context-creator.ts +145 -0
  95. package/ts/proxies/network-proxy/function-cache.ts +259 -0
  96. package/ts/proxies/network-proxy/http-request-handler.ts +330 -0
  97. package/ts/proxies/network-proxy/http2-request-handler.ts +255 -0
  98. package/ts/proxies/network-proxy/models/types.ts +312 -1
  99. package/ts/proxies/network-proxy/network-proxy.ts +197 -85
  100. package/ts/proxies/network-proxy/request-handler.ts +698 -246
  101. package/ts/proxies/network-proxy/security-manager.ts +298 -0
  102. package/ts/proxies/network-proxy/websocket-handler.ts +276 -33
  103. package/ts/proxies/smart-proxy/index.ts +2 -12
  104. package/ts/proxies/smart-proxy/models/interfaces.ts +7 -4
  105. package/ts/proxies/smart-proxy/models/route-types.ts +77 -10
  106. package/ts/proxies/smart-proxy/network-proxy-bridge.ts +20 -257
  107. package/ts/proxies/smart-proxy/port-manager.ts +195 -0
  108. package/ts/proxies/smart-proxy/route-connection-handler.ts +156 -21
  109. package/ts/proxies/smart-proxy/route-manager.ts +98 -14
  110. package/ts/proxies/smart-proxy/smart-proxy.ts +56 -55
  111. package/ts/proxies/smart-proxy/utils/route-helpers.ts +167 -1
  112. package/ts/proxies/smart-proxy/utils/route-validators.ts +24 -5
  113. package/ts/proxies/smart-proxy/domain-config-manager.ts.bak +0 -441
  114. package/ts/proxies/smart-proxy/route-helpers/index.ts +0 -9
  115. package/ts/proxies/smart-proxy/route-helpers.ts +0 -498
@@ -1,7 +1,22 @@
1
1
  import * as plugins from '../../plugins.js';
2
- import { type INetworkProxyOptions, type ILogger, createLogger, type IReverseProxyConfig } from './models/types.js';
2
+ import '../../core/models/socket-augmentation.js';
3
+ import {
4
+ type INetworkProxyOptions,
5
+ type ILogger,
6
+ createLogger,
7
+ type IReverseProxyConfig,
8
+ RouteManager
9
+ } from './models/types.js';
3
10
  import { ConnectionPool } from './connection-pool.js';
4
11
  import { ProxyRouter } from '../../http/router/index.js';
12
+ import { ContextCreator } from './context-creator.js';
13
+ import { HttpRequestHandler } from './http-request-handler.js';
14
+ import { Http2RequestHandler } from './http2-request-handler.js';
15
+ import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
16
+ import type { IRouteContext, IHttpRouteContext } from '../../core/models/route-context.js';
17
+ import { toBaseContext } from '../../core/models/route-context.js';
18
+ import { TemplateUtils } from '../../core/utils/template-utils.js';
19
+ import { SecurityManager } from './security-manager.js';
5
20
 
6
21
  /**
7
22
  * Interface for tracking metrics
@@ -24,12 +39,34 @@ export class RequestHandler {
24
39
  // HTTP/2 client sessions for backend proxying
25
40
  private h2Sessions: Map<string, plugins.http2.ClientHttp2Session> = new Map();
26
41
 
42
+ // Context creator for route contexts
43
+ private contextCreator: ContextCreator = new ContextCreator();
44
+
45
+ // Security manager for IP filtering, rate limiting, etc.
46
+ public securityManager: SecurityManager;
47
+
27
48
  constructor(
28
49
  private options: INetworkProxyOptions,
29
50
  private connectionPool: ConnectionPool,
30
- private router: ProxyRouter
51
+ private legacyRouter: ProxyRouter, // Legacy router for backward compatibility
52
+ private routeManager?: RouteManager,
53
+ private functionCache?: any, // FunctionCache - using any to avoid circular dependency
54
+ private router?: any // RouteRouter - using any to avoid circular dependency
31
55
  ) {
32
56
  this.logger = createLogger(options.logLevel || 'info');
57
+ this.securityManager = new SecurityManager(this.logger);
58
+
59
+ // Schedule rate limit cleanup every minute
60
+ setInterval(() => {
61
+ this.securityManager.cleanupExpiredRateLimits();
62
+ }, 60000);
63
+ }
64
+
65
+ /**
66
+ * Set the route manager instance
67
+ */
68
+ public setRouteManager(routeManager: RouteManager): void {
69
+ this.routeManager = routeManager;
33
70
  }
34
71
 
35
72
  /**
@@ -59,39 +96,104 @@ export class RequestHandler {
59
96
 
60
97
  /**
61
98
  * Apply CORS headers to response if configured
99
+ * Implements Phase 5.5: Context-aware CORS handling
100
+ *
101
+ * @param res The server response to apply headers to
102
+ * @param req The incoming request
103
+ * @param route Optional route config with CORS settings
62
104
  */
63
105
  private applyCorsHeaders(
64
- res: plugins.http.ServerResponse,
65
- req: plugins.http.IncomingMessage
106
+ res: plugins.http.ServerResponse,
107
+ req: plugins.http.IncomingMessage,
108
+ route?: IRouteConfig
66
109
  ): void {
67
- if (!this.options.cors) {
110
+ // Use route-specific CORS config if available, otherwise use global config
111
+ let corsConfig: any = null;
112
+
113
+ // Route CORS config takes precedence if enabled
114
+ if (route?.headers?.cors?.enabled) {
115
+ corsConfig = route.headers.cors;
116
+ this.logger.debug(`Using route-specific CORS config for ${route.name || 'unnamed route'}`);
117
+ }
118
+ // Fall back to global CORS config if available
119
+ else if (this.options.cors) {
120
+ corsConfig = this.options.cors;
121
+ this.logger.debug('Using global CORS config');
122
+ }
123
+
124
+ // If no CORS config available, skip
125
+ if (!corsConfig) {
68
126
  return;
69
127
  }
70
-
71
- // Apply CORS headers
72
- if (this.options.cors.allowOrigin) {
73
- res.setHeader('Access-Control-Allow-Origin', this.options.cors.allowOrigin);
128
+
129
+ // Get origin from request
130
+ const origin = req.headers.origin;
131
+
132
+ // Apply Allow-Origin (with dynamic validation if needed)
133
+ if (corsConfig.allowOrigin) {
134
+ // Handle multiple origins in array format
135
+ if (Array.isArray(corsConfig.allowOrigin)) {
136
+ if (origin && corsConfig.allowOrigin.includes(origin)) {
137
+ // Match found, set specific origin
138
+ res.setHeader('Access-Control-Allow-Origin', origin);
139
+ res.setHeader('Vary', 'Origin'); // Important for caching
140
+ } else if (corsConfig.allowOrigin.includes('*')) {
141
+ // Wildcard match
142
+ res.setHeader('Access-Control-Allow-Origin', '*');
143
+ }
144
+ }
145
+ // Handle single origin or wildcard
146
+ else if (corsConfig.allowOrigin === '*') {
147
+ res.setHeader('Access-Control-Allow-Origin', '*');
148
+ }
149
+ // Match single origin against request
150
+ else if (origin && corsConfig.allowOrigin === origin) {
151
+ res.setHeader('Access-Control-Allow-Origin', origin);
152
+ res.setHeader('Vary', 'Origin');
153
+ }
154
+ // Use template variables if present
155
+ else if (origin && corsConfig.allowOrigin.includes('{')) {
156
+ const resolvedOrigin = TemplateUtils.resolveTemplateVariables(
157
+ corsConfig.allowOrigin,
158
+ { domain: req.headers.host } as any
159
+ );
160
+ if (resolvedOrigin === origin || resolvedOrigin === '*') {
161
+ res.setHeader('Access-Control-Allow-Origin', origin);
162
+ res.setHeader('Vary', 'Origin');
163
+ }
164
+ }
165
+ }
166
+
167
+ // Apply other CORS headers
168
+ if (corsConfig.allowMethods) {
169
+ res.setHeader('Access-Control-Allow-Methods', corsConfig.allowMethods);
74
170
  }
75
-
76
- if (this.options.cors.allowMethods) {
77
- res.setHeader('Access-Control-Allow-Methods', this.options.cors.allowMethods);
171
+
172
+ if (corsConfig.allowHeaders) {
173
+ res.setHeader('Access-Control-Allow-Headers', corsConfig.allowHeaders);
78
174
  }
79
-
80
- if (this.options.cors.allowHeaders) {
81
- res.setHeader('Access-Control-Allow-Headers', this.options.cors.allowHeaders);
175
+
176
+ if (corsConfig.allowCredentials) {
177
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
82
178
  }
83
-
84
- if (this.options.cors.maxAge) {
85
- res.setHeader('Access-Control-Max-Age', this.options.cors.maxAge.toString());
179
+
180
+ if (corsConfig.exposeHeaders) {
181
+ res.setHeader('Access-Control-Expose-Headers', corsConfig.exposeHeaders);
86
182
  }
87
-
88
- // Handle CORS preflight requests
89
- if (req.method === 'OPTIONS') {
183
+
184
+ if (corsConfig.maxAge) {
185
+ res.setHeader('Access-Control-Max-Age', corsConfig.maxAge.toString());
186
+ }
187
+
188
+ // Handle CORS preflight requests if enabled (default: true)
189
+ if (req.method === 'OPTIONS' && corsConfig.preflight !== false) {
90
190
  res.statusCode = 204; // No content
91
191
  res.end();
92
192
  return;
93
193
  }
94
194
  }
195
+
196
+ // First implementation of applyRouteHeaderModifications moved to the second implementation below
95
197
 
96
198
  /**
97
199
  * Apply default headers to response
@@ -103,12 +205,147 @@ export class RequestHandler {
103
205
  res.setHeader(key, value);
104
206
  }
105
207
  }
106
-
208
+
107
209
  // Add server identifier if not already set
108
210
  if (!res.hasHeader('Server')) {
109
211
  res.setHeader('Server', 'NetworkProxy');
110
212
  }
111
213
  }
214
+
215
+ /**
216
+ * Apply URL rewriting based on route configuration
217
+ * Implements Phase 5.2: URL rewriting using route context
218
+ *
219
+ * @param req The request with the URL to rewrite
220
+ * @param route The route configuration containing rewrite rules
221
+ * @param routeContext Context for template variable resolution
222
+ * @returns True if URL was rewritten, false otherwise
223
+ */
224
+ private applyUrlRewriting(
225
+ req: plugins.http.IncomingMessage,
226
+ route: IRouteConfig,
227
+ routeContext: IHttpRouteContext
228
+ ): boolean {
229
+ // Check if route has URL rewriting configuration
230
+ if (!route.action.advanced?.urlRewrite) {
231
+ return false;
232
+ }
233
+
234
+ const rewriteConfig = route.action.advanced.urlRewrite;
235
+
236
+ // Store original URL for logging
237
+ const originalUrl = req.url;
238
+
239
+ if (rewriteConfig.pattern && rewriteConfig.target) {
240
+ try {
241
+ // Create a RegExp from the pattern
242
+ const regex = new RegExp(rewriteConfig.pattern, rewriteConfig.flags || '');
243
+
244
+ // Apply rewriting with template variable resolution
245
+ let target = rewriteConfig.target;
246
+
247
+ // Replace template variables in target with values from context
248
+ target = TemplateUtils.resolveTemplateVariables(target, routeContext);
249
+
250
+ // If onlyRewritePath is set, split URL into path and query parts
251
+ if (rewriteConfig.onlyRewritePath && req.url) {
252
+ const [path, query] = req.url.split('?');
253
+ const rewrittenPath = path.replace(regex, target);
254
+ req.url = query ? `${rewrittenPath}?${query}` : rewrittenPath;
255
+ } else {
256
+ // Perform the replacement on the entire URL
257
+ req.url = req.url?.replace(regex, target);
258
+ }
259
+
260
+ this.logger.debug(`URL rewritten: ${originalUrl} -> ${req.url}`);
261
+ return true;
262
+ } catch (err) {
263
+ this.logger.error(`Error in URL rewriting: ${err}`);
264
+ return false;
265
+ }
266
+ }
267
+
268
+ return false;
269
+ }
270
+
271
+ /**
272
+ * Apply header modifications from route configuration
273
+ * Implements Phase 5.1: Route-based header manipulation
274
+ */
275
+ private applyRouteHeaderModifications(
276
+ route: IRouteConfig,
277
+ req: plugins.http.IncomingMessage,
278
+ res: plugins.http.ServerResponse
279
+ ): void {
280
+ // Check if route has header modifications
281
+ if (!route.headers) {
282
+ return;
283
+ }
284
+
285
+ // Apply request header modifications (these will be sent to the backend)
286
+ if (route.headers.request && req.headers) {
287
+ for (const [key, value] of Object.entries(route.headers.request)) {
288
+ // Skip if header already exists and we're not overriding
289
+ if (req.headers[key.toLowerCase()] && !value.startsWith('!')) {
290
+ continue;
291
+ }
292
+
293
+ // Handle special delete directive (!delete)
294
+ if (value === '!delete') {
295
+ delete req.headers[key.toLowerCase()];
296
+ this.logger.debug(`Deleted request header: ${key}`);
297
+ continue;
298
+ }
299
+
300
+ // Handle forced override (!value)
301
+ let finalValue: string;
302
+ if (value.startsWith('!') && value !== '!delete') {
303
+ // Keep the ! but resolve any templates in the rest
304
+ const templateValue = value.substring(1);
305
+ finalValue = '!' + TemplateUtils.resolveTemplateVariables(templateValue, {} as IRouteContext);
306
+ } else {
307
+ // Resolve templates in the entire value
308
+ finalValue = TemplateUtils.resolveTemplateVariables(value, {} as IRouteContext);
309
+ }
310
+
311
+ // Set the header
312
+ req.headers[key.toLowerCase()] = finalValue;
313
+ this.logger.debug(`Modified request header: ${key}=${finalValue}`);
314
+ }
315
+ }
316
+
317
+ // Apply response header modifications (these will be stored for later use)
318
+ if (route.headers.response) {
319
+ for (const [key, value] of Object.entries(route.headers.response)) {
320
+ // Skip if header already exists and we're not overriding
321
+ if (res.hasHeader(key) && !value.startsWith('!')) {
322
+ continue;
323
+ }
324
+
325
+ // Handle special delete directive (!delete)
326
+ if (value === '!delete') {
327
+ res.removeHeader(key);
328
+ this.logger.debug(`Deleted response header: ${key}`);
329
+ continue;
330
+ }
331
+
332
+ // Handle forced override (!value)
333
+ let finalValue: string;
334
+ if (value.startsWith('!') && value !== '!delete') {
335
+ // Keep the ! but resolve any templates in the rest
336
+ const templateValue = value.substring(1);
337
+ finalValue = '!' + TemplateUtils.resolveTemplateVariables(templateValue, {} as IRouteContext);
338
+ } else {
339
+ // Resolve templates in the entire value
340
+ finalValue = TemplateUtils.resolveTemplateVariables(value, {} as IRouteContext);
341
+ }
342
+
343
+ // Set the header
344
+ res.setHeader(key, finalValue);
345
+ this.logger.debug(`Modified response header: ${key}=${finalValue}`);
346
+ }
347
+ }
348
+ }
112
349
 
113
350
  /**
114
351
  * Handle an HTTP request
@@ -119,10 +356,32 @@ export class RequestHandler {
119
356
  ): Promise<void> {
120
357
  // Record start time for logging
121
358
  const startTime = Date.now();
122
-
123
- // Apply CORS headers if configured
124
- this.applyCorsHeaders(res, req);
125
-
359
+
360
+ // Get route before applying CORS (we might need its settings)
361
+ // Try to find a matching route using RouteManager
362
+ let matchingRoute: IRouteConfig | null = null;
363
+ if (this.routeManager) {
364
+ try {
365
+ // Create a connection ID for this request
366
+ const connectionId = `http-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
367
+
368
+ // Create route context for function-based targets
369
+ const routeContext = this.contextCreator.createHttpRouteContext(req, {
370
+ connectionId,
371
+ clientIp: req.socket.remoteAddress?.replace('::ffff:', '') || '0.0.0.0',
372
+ serverIp: req.socket.localAddress?.replace('::ffff:', '') || '0.0.0.0',
373
+ tlsVersion: req.socket.getTLSVersion?.() || undefined
374
+ });
375
+
376
+ matchingRoute = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
377
+ } catch (err) {
378
+ this.logger.error('Error finding matching route', err);
379
+ }
380
+ }
381
+
382
+ // Apply CORS headers with route-specific settings if available
383
+ this.applyCorsHeaders(res, req, matchingRoute);
384
+
126
385
  // If this is an OPTIONS request, the response has already been ended in applyCorsHeaders
127
386
  // so we should return early to avoid trying to set more headers
128
387
  if (req.method === 'OPTIONS') {
@@ -132,16 +391,220 @@ export class RequestHandler {
132
391
  }
133
392
  return;
134
393
  }
135
-
394
+
136
395
  // Apply default headers
137
396
  this.applyDefaultHeaders(res);
138
-
139
- // Determine routing configuration
397
+
398
+ // We already have the connection ID and routeContext from CORS handling
399
+ const connectionId = `http-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
400
+
401
+ // Create route context for function-based targets (if we don't already have one)
402
+ const routeContext = this.contextCreator.createHttpRouteContext(req, {
403
+ connectionId,
404
+ clientIp: req.socket.remoteAddress?.replace('::ffff:', '') || '0.0.0.0',
405
+ serverIp: req.socket.localAddress?.replace('::ffff:', '') || '0.0.0.0',
406
+ tlsVersion: req.socket.getTLSVersion?.() || undefined
407
+ });
408
+
409
+ // Check security restrictions if we have a matching route
410
+ if (matchingRoute) {
411
+ // Check IP filtering and rate limiting
412
+ if (!this.securityManager.isAllowed(matchingRoute, routeContext)) {
413
+ this.logger.warn(`Access denied for ${routeContext.clientIp} to ${matchingRoute.name || 'unnamed'}`);
414
+ res.statusCode = 403;
415
+ res.end('Forbidden: Access denied by security policy');
416
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
417
+ return;
418
+ }
419
+
420
+ // Check basic auth
421
+ if (matchingRoute.security?.basicAuth?.enabled) {
422
+ const authHeader = req.headers.authorization;
423
+ if (!authHeader || !authHeader.startsWith('Basic ')) {
424
+ // No auth header provided - send 401 with WWW-Authenticate header
425
+ res.statusCode = 401;
426
+ const realm = matchingRoute.security.basicAuth.realm || 'Protected Area';
427
+ res.setHeader('WWW-Authenticate', `Basic realm="${realm}", charset="UTF-8"`);
428
+ res.end('Authentication Required');
429
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
430
+ return;
431
+ }
432
+
433
+ // Verify credentials
434
+ try {
435
+ const credentials = Buffer.from(authHeader.substring(6), 'base64').toString('utf-8');
436
+ const [username, password] = credentials.split(':');
437
+
438
+ if (!this.securityManager.checkBasicAuth(matchingRoute, username, password)) {
439
+ res.statusCode = 401;
440
+ const realm = matchingRoute.security.basicAuth.realm || 'Protected Area';
441
+ res.setHeader('WWW-Authenticate', `Basic realm="${realm}", charset="UTF-8"`);
442
+ res.end('Invalid Credentials');
443
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
444
+ return;
445
+ }
446
+ } catch (err) {
447
+ this.logger.error(`Error verifying basic auth: ${err}`);
448
+ res.statusCode = 401;
449
+ res.end('Authentication Error');
450
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
451
+ return;
452
+ }
453
+ }
454
+
455
+ // Check JWT auth
456
+ if (matchingRoute.security?.jwtAuth?.enabled) {
457
+ const authHeader = req.headers.authorization;
458
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
459
+ // No auth header provided - send 401
460
+ res.statusCode = 401;
461
+ res.end('Authentication Required: JWT token missing');
462
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
463
+ return;
464
+ }
465
+
466
+ // Verify token
467
+ const token = authHeader.substring(7);
468
+ if (!this.securityManager.verifyJwtToken(matchingRoute, token)) {
469
+ res.statusCode = 401;
470
+ res.end('Invalid or Expired JWT');
471
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
472
+ return;
473
+ }
474
+ }
475
+ }
476
+
477
+ // If we found a matching route with function-based targets, use it
478
+ if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.target) {
479
+ this.logger.debug(`Found matching route: ${matchingRoute.name || 'unnamed'}`);
480
+
481
+ // Extract target information, resolving functions if needed
482
+ let targetHost: string | string[];
483
+ let targetPort: number;
484
+
485
+ try {
486
+ // Check function cache for host and resolve or use cached value
487
+ if (typeof matchingRoute.action.target.host === 'function') {
488
+ // Generate a function ID for caching (use route name or ID if available)
489
+ const functionId = `host-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
490
+
491
+ // Check if we have a cached result
492
+ if (this.functionCache) {
493
+ const cachedHost = this.functionCache.getCachedHost(routeContext, functionId);
494
+ if (cachedHost !== undefined) {
495
+ targetHost = cachedHost;
496
+ this.logger.debug(`Using cached host value for ${functionId}`);
497
+ } else {
498
+ // Resolve the function and cache the result
499
+ const resolvedHost = matchingRoute.action.target.host(toBaseContext(routeContext));
500
+ targetHost = resolvedHost;
501
+
502
+ // Cache the result
503
+ this.functionCache.cacheHost(routeContext, functionId, resolvedHost);
504
+ this.logger.debug(`Resolved and cached function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
505
+ }
506
+ } else {
507
+ // No cache available, just resolve
508
+ const resolvedHost = matchingRoute.action.target.host(routeContext);
509
+ targetHost = resolvedHost;
510
+ this.logger.debug(`Resolved function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
511
+ }
512
+ } else {
513
+ targetHost = matchingRoute.action.target.host;
514
+ }
515
+
516
+ // Check function cache for port and resolve or use cached value
517
+ if (typeof matchingRoute.action.target.port === 'function') {
518
+ // Generate a function ID for caching
519
+ const functionId = `port-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
520
+
521
+ // Check if we have a cached result
522
+ if (this.functionCache) {
523
+ const cachedPort = this.functionCache.getCachedPort(routeContext, functionId);
524
+ if (cachedPort !== undefined) {
525
+ targetPort = cachedPort;
526
+ this.logger.debug(`Using cached port value for ${functionId}`);
527
+ } else {
528
+ // Resolve the function and cache the result
529
+ const resolvedPort = matchingRoute.action.target.port(toBaseContext(routeContext));
530
+ targetPort = resolvedPort;
531
+
532
+ // Cache the result
533
+ this.functionCache.cachePort(routeContext, functionId, resolvedPort);
534
+ this.logger.debug(`Resolved and cached function-based port to: ${resolvedPort}`);
535
+ }
536
+ } else {
537
+ // No cache available, just resolve
538
+ const resolvedPort = matchingRoute.action.target.port(routeContext);
539
+ targetPort = resolvedPort;
540
+ this.logger.debug(`Resolved function-based port to: ${resolvedPort}`);
541
+ }
542
+ } else {
543
+ targetPort = matchingRoute.action.target.port === 'preserve' ? routeContext.port : matchingRoute.action.target.port as number;
544
+ }
545
+
546
+ // Select a single host if an array was provided
547
+ const selectedHost = Array.isArray(targetHost)
548
+ ? targetHost[Math.floor(Math.random() * targetHost.length)]
549
+ : targetHost;
550
+
551
+ // Create a destination for the connection pool
552
+ const destination = {
553
+ host: selectedHost,
554
+ port: targetPort
555
+ };
556
+
557
+ // Apply URL rewriting if configured
558
+ this.applyUrlRewriting(req, matchingRoute, routeContext);
559
+
560
+ // Apply header modifications if configured
561
+ this.applyRouteHeaderModifications(matchingRoute, req, res);
562
+
563
+ // Continue with handling using the resolved destination
564
+ HttpRequestHandler.handleHttpRequestWithDestination(
565
+ req,
566
+ res,
567
+ destination,
568
+ routeContext,
569
+ startTime,
570
+ this.logger,
571
+ this.metricsTracker,
572
+ matchingRoute // Pass the route config for additional processing
573
+ );
574
+ return;
575
+ } catch (err) {
576
+ this.logger.error(`Error evaluating function-based target: ${err}`);
577
+ res.statusCode = 500;
578
+ res.end('Internal Server Error: Failed to evaluate target functions');
579
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
580
+ return;
581
+ }
582
+ }
583
+
584
+ // Try modern router first, then fall back to legacy routing if needed
585
+ if (this.router) {
586
+ try {
587
+ // Try to find a matching route using the modern router
588
+ const route = this.router.routeReq(req);
589
+ if (route && route.action.type === 'forward' && route.action.target) {
590
+ // Handle this route similarly to RouteManager logic
591
+ this.logger.debug(`Found matching route via modern router: ${route.name || 'unnamed'}`);
592
+
593
+ // No need to do anything here, we'll continue with legacy routing
594
+ // The routeManager would have already found this route if applicable
595
+ }
596
+ } catch (err) {
597
+ this.logger.error('Error using modern router', err);
598
+ // Continue with legacy routing
599
+ }
600
+ }
601
+
602
+ // Fall back to legacy routing if no matching route found via RouteManager
140
603
  let proxyConfig: IReverseProxyConfig | undefined;
141
604
  try {
142
- proxyConfig = this.router.routeReq(req);
605
+ proxyConfig = this.legacyRouter.routeReq(req);
143
606
  } catch (err) {
144
- this.logger.error('Error routing request', err);
607
+ this.logger.error('Error routing request with legacy router', err);
145
608
  res.statusCode = 500;
146
609
  res.end('Internal Server Error');
147
610
  if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
@@ -198,165 +661,183 @@ export class RequestHandler {
198
661
  });
199
662
  return;
200
663
  }
201
-
202
- try {
203
- // Find target based on hostname
204
- const proxyConfig = this.router.routeReq(req);
205
-
206
- if (!proxyConfig) {
207
- // No matching proxy configuration
208
- this.logger.warn(`No proxy configuration for host: ${req.headers.host}`);
209
- res.statusCode = 404;
210
- res.end('Not Found: No proxy configuration for this host');
211
-
212
- // Increment failed requests counter
213
- if (this.metricsTracker) {
214
- this.metricsTracker.incrementFailedRequests();
215
- }
216
-
217
- return;
218
- }
219
-
220
- // Get destination IP using round-robin if multiple IPs configured
221
- const destination = this.connectionPool.getNextTarget(
222
- proxyConfig.destinationIps,
223
- proxyConfig.destinationPorts[0]
224
- );
225
-
226
- // Create options for the proxy request
227
- const options: plugins.http.RequestOptions = {
228
- hostname: destination.host,
229
- port: destination.port,
230
- path: req.url,
231
- method: req.method,
232
- headers: { ...req.headers }
233
- };
234
-
235
- // Remove host header to avoid issues with virtual hosts on target server
236
- // The host header should match the target server's expected hostname
237
- if (options.headers && options.headers.host) {
238
- if ((proxyConfig as IReverseProxyConfig).rewriteHostHeader) {
239
- options.headers.host = `${destination.host}:${destination.port}`;
240
- }
664
+ }
665
+
666
+ /**
667
+ * Handle HTTP/2 stream requests with function-based target support
668
+ */
669
+ public async handleHttp2(stream: plugins.http2.ServerHttp2Stream, headers: plugins.http2.IncomingHttpHeaders): Promise<void> {
670
+ const startTime = Date.now();
671
+
672
+ // Create a connection ID for this HTTP/2 stream
673
+ const connectionId = `http2-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
674
+
675
+ // Get client IP and server IP from the socket
676
+ const socket = (stream.session as any)?.socket;
677
+ const clientIp = socket?.remoteAddress?.replace('::ffff:', '') || '0.0.0.0';
678
+ const serverIp = socket?.localAddress?.replace('::ffff:', '') || '0.0.0.0';
679
+
680
+ // Create route context for function-based targets
681
+ const routeContext = this.contextCreator.createHttp2RouteContext(stream, headers, {
682
+ connectionId,
683
+ clientIp,
684
+ serverIp
685
+ });
686
+
687
+ // Try to find a matching route using RouteManager
688
+ let matchingRoute: IRouteConfig | null = null;
689
+ if (this.routeManager) {
690
+ try {
691
+ matchingRoute = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
692
+ } catch (err) {
693
+ this.logger.error('Error finding matching route for HTTP/2 request', err);
241
694
  }
242
-
243
- this.logger.debug(
244
- `Proxying request to ${destination.host}:${destination.port}${req.url}`,
245
- { method: req.method }
246
- );
247
-
248
- // Create proxy request
249
- const proxyReq = plugins.http.request(options, (proxyRes) => {
250
- // Copy status code
251
- res.statusCode = proxyRes.statusCode || 500;
252
-
253
- // Copy headers from proxy response to client response
254
- for (const [key, value] of Object.entries(proxyRes.headers)) {
255
- if (value !== undefined) {
256
- res.setHeader(key, value);
695
+ }
696
+
697
+ // If we found a matching route with function-based targets, use it
698
+ if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.target) {
699
+ this.logger.debug(`Found matching route for HTTP/2 request: ${matchingRoute.name || 'unnamed'}`);
700
+
701
+ // Extract target information, resolving functions if needed
702
+ let targetHost: string | string[];
703
+ let targetPort: number;
704
+
705
+ try {
706
+ // Check function cache for host and resolve or use cached value
707
+ if (typeof matchingRoute.action.target.host === 'function') {
708
+ // Generate a function ID for caching (use route name or ID if available)
709
+ const functionId = `host-http2-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
710
+
711
+ // Check if we have a cached result
712
+ if (this.functionCache) {
713
+ const cachedHost = this.functionCache.getCachedHost(routeContext, functionId);
714
+ if (cachedHost !== undefined) {
715
+ targetHost = cachedHost;
716
+ this.logger.debug(`Using cached host value for HTTP/2: ${functionId}`);
717
+ } else {
718
+ // Resolve the function and cache the result
719
+ const resolvedHost = matchingRoute.action.target.host(toBaseContext(routeContext));
720
+ targetHost = resolvedHost;
721
+
722
+ // Cache the result
723
+ this.functionCache.cacheHost(routeContext, functionId, resolvedHost);
724
+ this.logger.debug(`Resolved and cached HTTP/2 function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
725
+ }
726
+ } else {
727
+ // No cache available, just resolve
728
+ const resolvedHost = matchingRoute.action.target.host(routeContext);
729
+ targetHost = resolvedHost;
730
+ this.logger.debug(`Resolved HTTP/2 function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
257
731
  }
732
+ } else {
733
+ targetHost = matchingRoute.action.target.host;
258
734
  }
259
-
260
- // Pipe proxy response to client response
261
- proxyRes.pipe(res);
262
-
263
- // Increment served requests counter when the response finishes
264
- res.on('finish', () => {
265
- if (this.metricsTracker) {
266
- this.metricsTracker.incrementRequestsServed();
735
+
736
+ // Check function cache for port and resolve or use cached value
737
+ if (typeof matchingRoute.action.target.port === 'function') {
738
+ // Generate a function ID for caching
739
+ const functionId = `port-http2-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
740
+
741
+ // Check if we have a cached result
742
+ if (this.functionCache) {
743
+ const cachedPort = this.functionCache.getCachedPort(routeContext, functionId);
744
+ if (cachedPort !== undefined) {
745
+ targetPort = cachedPort;
746
+ this.logger.debug(`Using cached port value for HTTP/2: ${functionId}`);
747
+ } else {
748
+ // Resolve the function and cache the result
749
+ const resolvedPort = matchingRoute.action.target.port(toBaseContext(routeContext));
750
+ targetPort = resolvedPort;
751
+
752
+ // Cache the result
753
+ this.functionCache.cachePort(routeContext, functionId, resolvedPort);
754
+ this.logger.debug(`Resolved and cached HTTP/2 function-based port to: ${resolvedPort}`);
755
+ }
756
+ } else {
757
+ // No cache available, just resolve
758
+ const resolvedPort = matchingRoute.action.target.port(routeContext);
759
+ targetPort = resolvedPort;
760
+ this.logger.debug(`Resolved HTTP/2 function-based port to: ${resolvedPort}`);
267
761
  }
268
-
269
- // Log the completed request
270
- const duration = Date.now() - startTime;
271
- this.logger.debug(
272
- `Request completed in ${duration}ms: ${req.method} ${req.url} ${res.statusCode}`,
273
- { duration, statusCode: res.statusCode }
274
- );
275
- });
276
- });
277
-
278
- // Handle proxy request errors
279
- proxyReq.on('error', (error) => {
280
- const duration = Date.now() - startTime;
281
- this.logger.error(
282
- `Proxy error for ${req.method} ${req.url}: ${error.message}`,
283
- { duration, error: error.message }
284
- );
285
-
286
- // Increment failed requests counter
287
- if (this.metricsTracker) {
288
- this.metricsTracker.incrementFailedRequests();
289
- }
290
-
291
- // Check if headers have already been sent
292
- if (!res.headersSent) {
293
- res.statusCode = 502;
294
- res.end(`Bad Gateway: ${error.message}`);
295
762
  } else {
296
- // If headers already sent, just close the connection
297
- res.end();
298
- }
299
- });
300
-
301
- // Pipe request body to proxy request and handle client-side errors
302
- req.pipe(proxyReq);
303
-
304
- // Handle client disconnection
305
- req.on('error', (error) => {
306
- this.logger.debug(`Client connection error: ${error.message}`);
307
- proxyReq.destroy();
308
-
309
- // Increment failed requests counter on client errors
310
- if (this.metricsTracker) {
311
- this.metricsTracker.incrementFailedRequests();
763
+ targetPort = matchingRoute.action.target.port === 'preserve' ? routeContext.port : matchingRoute.action.target.port as number;
312
764
  }
313
- });
314
-
315
- // Handle response errors
316
- res.on('error', (error) => {
317
- this.logger.debug(`Response error: ${error.message}`);
318
- proxyReq.destroy();
319
-
320
- // Increment failed requests counter on response errors
321
- if (this.metricsTracker) {
322
- this.metricsTracker.incrementFailedRequests();
765
+
766
+ // Select a single host if an array was provided
767
+ const selectedHost = Array.isArray(targetHost)
768
+ ? targetHost[Math.floor(Math.random() * targetHost.length)]
769
+ : targetHost;
770
+
771
+ // Create a destination for forwarding
772
+ const destination = {
773
+ host: selectedHost,
774
+ port: targetPort
775
+ };
776
+
777
+ // Handle HTTP/2 stream based on backend protocol
778
+ const backendProtocol = matchingRoute.action.options?.backendProtocol || this.options.backendProtocol;
779
+
780
+ if (backendProtocol === 'http2') {
781
+ // Forward to HTTP/2 backend
782
+ return Http2RequestHandler.handleHttp2WithHttp2Destination(
783
+ stream,
784
+ headers,
785
+ destination,
786
+ routeContext,
787
+ this.h2Sessions,
788
+ this.logger,
789
+ this.metricsTracker
790
+ );
791
+ } else {
792
+ // Forward to HTTP/1.1 backend
793
+ return Http2RequestHandler.handleHttp2WithHttp1Destination(
794
+ stream,
795
+ headers,
796
+ destination,
797
+ routeContext,
798
+ this.logger,
799
+ this.metricsTracker
800
+ );
323
801
  }
324
- });
325
-
326
- } catch (error) {
327
- // Handle any unexpected errors
328
- this.logger.error(
329
- `Unexpected error handling request: ${error.message}`,
330
- { error: error.stack }
331
- );
332
-
333
- // Increment failed requests counter
334
- if (this.metricsTracker) {
335
- this.metricsTracker.incrementFailedRequests();
336
- }
337
-
338
- if (!res.headersSent) {
339
- res.statusCode = 500;
340
- res.end('Internal Server Error');
341
- } else {
342
- res.end();
802
+ } catch (err) {
803
+ this.logger.error(`Error evaluating function-based target for HTTP/2: ${err}`);
804
+ stream.respond({ ':status': 500 });
805
+ stream.end('Internal Server Error: Failed to evaluate target functions');
806
+ if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
807
+ return;
343
808
  }
344
809
  }
345
- }
346
810
 
347
- /**
348
- * Handle HTTP/2 stream requests by proxying to HTTP/1 backends
349
- */
350
- public async handleHttp2(stream: any, headers: any): Promise<void> {
351
- const startTime = Date.now();
811
+ // Fall back to legacy routing if no matching route found
352
812
  const method = headers[':method'] || 'GET';
353
813
  const path = headers[':path'] || '/';
814
+
354
815
  // If configured to proxy to backends over HTTP/2, use HTTP/2 client sessions
355
816
  if (this.options.backendProtocol === 'http2') {
356
817
  const authority = headers[':authority'] as string || '';
357
818
  const host = authority.split(':')[0];
358
- const fakeReq: any = { headers: { host }, method: headers[':method'], url: headers[':path'], socket: (stream.session as any).socket };
359
- const proxyConfig = this.router.routeReq(fakeReq);
819
+ const fakeReq: any = {
820
+ headers: { host },
821
+ method: headers[':method'],
822
+ url: headers[':path'],
823
+ socket: (stream.session as any).socket
824
+ };
825
+ // Try modern router first if available
826
+ let route;
827
+ if (this.router) {
828
+ try {
829
+ route = this.router.routeReq(fakeReq);
830
+ if (route && route.action.type === 'forward' && route.action.target) {
831
+ this.logger.debug(`Found matching HTTP/2 route via modern router: ${route.name || 'unnamed'}`);
832
+ // The routeManager would have already found this route if applicable
833
+ }
834
+ } catch (err) {
835
+ this.logger.error('Error using modern router for HTTP/2', err);
836
+ }
837
+ }
838
+
839
+ // Fall back to legacy routing
840
+ const proxyConfig = this.legacyRouter.routeReq(fakeReq);
360
841
  if (!proxyConfig) {
361
842
  stream.respond({ ':status': 404 });
362
843
  stream.end('Not Found');
@@ -364,96 +845,67 @@ export class RequestHandler {
364
845
  return;
365
846
  }
366
847
  const destination = this.connectionPool.getNextTarget(proxyConfig.destinationIps, proxyConfig.destinationPorts[0]);
367
- const key = `${destination.host}:${destination.port}`;
368
- let session = this.h2Sessions.get(key);
369
- if (!session || session.closed || (session as any).destroyed) {
370
- session = plugins.http2.connect(`http://${destination.host}:${destination.port}`);
371
- this.h2Sessions.set(key, session);
372
- session.on('error', () => this.h2Sessions.delete(key));
373
- session.on('close', () => this.h2Sessions.delete(key));
374
- }
375
- // Build headers for backend HTTP/2 request
376
- const h2Headers: Record<string, any> = {
377
- ':method': headers[':method'],
378
- ':path': headers[':path'],
379
- ':authority': `${destination.host}:${destination.port}`
380
- };
381
- for (const [k, v] of Object.entries(headers)) {
382
- if (!k.startsWith(':') && typeof v === 'string') {
383
- h2Headers[k] = v;
384
- }
385
- }
386
- const h2Stream2 = session.request(h2Headers);
387
- stream.pipe(h2Stream2);
388
- h2Stream2.on('response', (hdrs: any) => {
389
- // Map status and headers to client
390
- const resp: Record<string, any> = { ':status': hdrs[':status'] as number };
391
- for (const [hk, hv] of Object.entries(hdrs)) {
392
- if (!hk.startsWith(':') && hv) resp[hk] = hv;
393
- }
394
- stream.respond(resp);
395
- h2Stream2.pipe(stream);
396
- });
397
- h2Stream2.on('error', (err) => {
398
- stream.respond({ ':status': 502 });
399
- stream.end(`Bad Gateway: ${err.message}`);
400
- if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
401
- });
402
- return;
848
+
849
+ // Use the helper for HTTP/2 to HTTP/2 routing
850
+ return Http2RequestHandler.handleHttp2WithHttp2Destination(
851
+ stream,
852
+ headers,
853
+ destination,
854
+ routeContext,
855
+ this.h2Sessions,
856
+ this.logger,
857
+ this.metricsTracker
858
+ );
403
859
  }
860
+
404
861
  try {
405
862
  // Determine host for routing
406
863
  const authority = headers[':authority'] as string || '';
407
864
  const host = authority.split(':')[0];
408
865
  // Fake request object for routing
409
- const fakeReq: any = { headers: { host }, method, url: path, socket: (stream.session as any).socket };
410
- const proxyConfig = this.router.routeReq(fakeReq as any);
866
+ const fakeReq: any = {
867
+ headers: { host },
868
+ method,
869
+ url: path,
870
+ socket: (stream.session as any).socket
871
+ };
872
+ // Try modern router first if available
873
+ if (this.router) {
874
+ try {
875
+ const route = this.router.routeReq(fakeReq);
876
+ if (route && route.action.type === 'forward' && route.action.target) {
877
+ this.logger.debug(`Found matching HTTP/2 route via modern router: ${route.name || 'unnamed'}`);
878
+ // The routeManager would have already found this route if applicable
879
+ }
880
+ } catch (err) {
881
+ this.logger.error('Error using modern router for HTTP/2', err);
882
+ }
883
+ }
884
+
885
+ // Fall back to legacy routing
886
+ const proxyConfig = this.legacyRouter.routeReq(fakeReq as any);
411
887
  if (!proxyConfig) {
412
888
  stream.respond({ ':status': 404 });
413
889
  stream.end('Not Found');
414
890
  if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
415
891
  return;
416
892
  }
893
+
417
894
  // Select backend target
418
895
  const destination = this.connectionPool.getNextTarget(
419
896
  proxyConfig.destinationIps,
420
897
  proxyConfig.destinationPorts[0]
421
898
  );
422
- // Build headers for HTTP/1 proxy
423
- const outboundHeaders: Record<string,string> = {};
424
- for (const [key, value] of Object.entries(headers)) {
425
- if (typeof key === 'string' && typeof value === 'string' && !key.startsWith(':')) {
426
- outboundHeaders[key] = value;
427
- }
428
- }
429
- if (outboundHeaders.host && (proxyConfig as IReverseProxyConfig).rewriteHostHeader) {
430
- outboundHeaders.host = `${destination.host}:${destination.port}`;
431
- }
432
- // Create HTTP/1 proxy request
433
- const proxyReq = plugins.http.request(
434
- { hostname: destination.host, port: destination.port, path, method, headers: outboundHeaders },
435
- (proxyRes) => {
436
- // Map status and headers back to HTTP/2
437
- const responseHeaders: Record<string, number|string|string[]> = {};
438
- for (const [k, v] of Object.entries(proxyRes.headers)) {
439
- if (v !== undefined) {
440
- responseHeaders[k] = v as string | string[];
441
- }
442
- }
443
- stream.respond({ ':status': proxyRes.statusCode || 500, ...responseHeaders });
444
- proxyRes.pipe(stream);
445
- stream.on('close', () => proxyReq.destroy());
446
- stream.on('error', () => proxyReq.destroy());
447
- if (this.metricsTracker) stream.on('end', () => this.metricsTracker.incrementRequestsServed());
448
- }
899
+
900
+ // Use the helper for HTTP/2 to HTTP/1 routing
901
+ return Http2RequestHandler.handleHttp2WithHttp1Destination(
902
+ stream,
903
+ headers,
904
+ destination,
905
+ routeContext,
906
+ this.logger,
907
+ this.metricsTracker
449
908
  );
450
- proxyReq.on('error', (err) => {
451
- stream.respond({ ':status': 502 });
452
- stream.end(`Bad Gateway: ${err.message}`);
453
- if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
454
- });
455
- // Pipe client stream to backend
456
- stream.pipe(proxyReq);
457
909
  } catch (err: any) {
458
910
  stream.respond({ ':status': 500 });
459
911
  stream.end('Internal Server Error');