@push.rocks/smartproxy 16.0.2 → 16.0.3

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 +147 -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 -3
  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 +195 -86
  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 +78 -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
@@ -0,0 +1,330 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import '../../core/models/socket-augmentation.js';
3
+ import type { IHttpRouteContext, IRouteContext } from '../../core/models/route-context.js';
4
+ import type { ILogger } from './models/types.js';
5
+ import type { IMetricsTracker } from './request-handler.js';
6
+ import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
7
+ import { TemplateUtils } from '../../core/utils/template-utils.js';
8
+
9
+ /**
10
+ * HTTP Request Handler Helper - handles requests with specific destinations
11
+ * This is a helper class for the main RequestHandler
12
+ */
13
+ export class HttpRequestHandler {
14
+ /**
15
+ * Handle HTTP request with a specific destination
16
+ */
17
+ public static async handleHttpRequestWithDestination(
18
+ req: plugins.http.IncomingMessage,
19
+ res: plugins.http.ServerResponse,
20
+ destination: { host: string, port: number },
21
+ routeContext: IHttpRouteContext,
22
+ startTime: number,
23
+ logger: ILogger,
24
+ metricsTracker?: IMetricsTracker | null,
25
+ route?: IRouteConfig
26
+ ): Promise<void> {
27
+ try {
28
+ // Apply URL rewriting if route config is provided
29
+ if (route) {
30
+ HttpRequestHandler.applyUrlRewriting(req, route, routeContext, logger);
31
+ HttpRequestHandler.applyRouteHeaderModifications(route, req, res, logger);
32
+ }
33
+
34
+ // Create options for the proxy request
35
+ const options: plugins.http.RequestOptions = {
36
+ hostname: destination.host,
37
+ port: destination.port,
38
+ path: req.url,
39
+ method: req.method,
40
+ headers: { ...req.headers }
41
+ };
42
+
43
+ // Optionally rewrite host header to match target
44
+ if (options.headers && options.headers.host) {
45
+ // Only apply if host header rewrite is enabled or not explicitly disabled
46
+ const shouldRewriteHost = route?.action.options?.rewriteHostHeader !== false;
47
+ if (shouldRewriteHost) {
48
+ options.headers.host = `${destination.host}:${destination.port}`;
49
+ }
50
+ }
51
+
52
+ logger.debug(
53
+ `Proxying request to ${destination.host}:${destination.port}${req.url}`,
54
+ { method: req.method }
55
+ );
56
+
57
+ // Create proxy request
58
+ const proxyReq = plugins.http.request(options, (proxyRes) => {
59
+ // Copy status code
60
+ res.statusCode = proxyRes.statusCode || 500;
61
+
62
+ // Copy headers from proxy response to client response
63
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
64
+ if (value !== undefined) {
65
+ res.setHeader(key, value);
66
+ }
67
+ }
68
+
69
+ // Apply response header modifications if route config is provided
70
+ if (route && route.headers?.response) {
71
+ HttpRequestHandler.applyResponseHeaderModifications(route, res, logger, routeContext);
72
+ }
73
+
74
+ // Pipe proxy response to client response
75
+ proxyRes.pipe(res);
76
+
77
+ // Increment served requests counter when the response finishes
78
+ res.on('finish', () => {
79
+ if (metricsTracker) {
80
+ metricsTracker.incrementRequestsServed();
81
+ }
82
+
83
+ // Log the completed request
84
+ const duration = Date.now() - startTime;
85
+ logger.debug(
86
+ `Request completed in ${duration}ms: ${req.method} ${req.url} ${res.statusCode}`,
87
+ { duration, statusCode: res.statusCode }
88
+ );
89
+ });
90
+ });
91
+
92
+ // Handle proxy request errors
93
+ proxyReq.on('error', (error) => {
94
+ const duration = Date.now() - startTime;
95
+ logger.error(
96
+ `Proxy error for ${req.method} ${req.url}: ${error.message}`,
97
+ { duration, error: error.message }
98
+ );
99
+
100
+ // Increment failed requests counter
101
+ if (metricsTracker) {
102
+ metricsTracker.incrementFailedRequests();
103
+ }
104
+
105
+ // Check if headers have already been sent
106
+ if (!res.headersSent) {
107
+ res.statusCode = 502;
108
+ res.end(`Bad Gateway: ${error.message}`);
109
+ } else {
110
+ // If headers already sent, just close the connection
111
+ res.end();
112
+ }
113
+ });
114
+
115
+ // Pipe request body to proxy request and handle client-side errors
116
+ req.pipe(proxyReq);
117
+
118
+ // Handle client disconnection
119
+ req.on('error', (error) => {
120
+ logger.debug(`Client connection error: ${error.message}`);
121
+ proxyReq.destroy();
122
+
123
+ // Increment failed requests counter on client errors
124
+ if (metricsTracker) {
125
+ metricsTracker.incrementFailedRequests();
126
+ }
127
+ });
128
+
129
+ // Handle response errors
130
+ res.on('error', (error) => {
131
+ logger.debug(`Response error: ${error.message}`);
132
+ proxyReq.destroy();
133
+
134
+ // Increment failed requests counter on response errors
135
+ if (metricsTracker) {
136
+ metricsTracker.incrementFailedRequests();
137
+ }
138
+ });
139
+ } catch (error) {
140
+ // Handle any unexpected errors
141
+ logger.error(
142
+ `Unexpected error handling request: ${error.message}`,
143
+ { error: error.stack }
144
+ );
145
+
146
+ // Increment failed requests counter
147
+ if (metricsTracker) {
148
+ metricsTracker.incrementFailedRequests();
149
+ }
150
+
151
+ if (!res.headersSent) {
152
+ res.statusCode = 500;
153
+ res.end('Internal Server Error');
154
+ } else {
155
+ res.end();
156
+ }
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Apply URL rewriting based on route configuration
162
+ * Implements Phase 5.2: URL rewriting using route context
163
+ *
164
+ * @param req The request with the URL to rewrite
165
+ * @param route The route configuration containing rewrite rules
166
+ * @param routeContext Context for template variable resolution
167
+ * @param logger Logger for debugging information
168
+ * @returns True if URL was rewritten, false otherwise
169
+ */
170
+ private static applyUrlRewriting(
171
+ req: plugins.http.IncomingMessage,
172
+ route: IRouteConfig,
173
+ routeContext: IHttpRouteContext,
174
+ logger: ILogger
175
+ ): boolean {
176
+ // Check if route has URL rewriting configuration
177
+ if (!route.action.advanced?.urlRewrite) {
178
+ return false;
179
+ }
180
+
181
+ const rewriteConfig = route.action.advanced.urlRewrite;
182
+
183
+ // Store original URL for logging
184
+ const originalUrl = req.url;
185
+
186
+ if (rewriteConfig.pattern && rewriteConfig.target) {
187
+ try {
188
+ // Create a RegExp from the pattern with optional flags
189
+ const regex = new RegExp(rewriteConfig.pattern, rewriteConfig.flags || '');
190
+
191
+ // Apply rewriting with template variable resolution
192
+ let target = rewriteConfig.target;
193
+
194
+ // Replace template variables in target with values from context
195
+ target = TemplateUtils.resolveTemplateVariables(target, routeContext);
196
+
197
+ // If onlyRewritePath is set, split URL into path and query parts
198
+ if (rewriteConfig.onlyRewritePath && req.url) {
199
+ const [path, query] = req.url.split('?');
200
+ const rewrittenPath = path.replace(regex, target);
201
+ req.url = query ? `${rewrittenPath}?${query}` : rewrittenPath;
202
+ } else {
203
+ // Perform the replacement on the entire URL
204
+ req.url = req.url?.replace(regex, target);
205
+ }
206
+
207
+ logger.debug(`URL rewritten: ${originalUrl} -> ${req.url}`);
208
+ return true;
209
+ } catch (err) {
210
+ logger.error(`Error in URL rewriting: ${err}`);
211
+ return false;
212
+ }
213
+ }
214
+
215
+ return false;
216
+ }
217
+
218
+ /**
219
+ * Apply header modifications from route configuration to request headers
220
+ * Implements Phase 5.1: Route-based header manipulation for requests
221
+ */
222
+ private static applyRouteHeaderModifications(
223
+ route: IRouteConfig,
224
+ req: plugins.http.IncomingMessage,
225
+ res: plugins.http.ServerResponse,
226
+ logger: ILogger
227
+ ): void {
228
+ // Check if route has header modifications
229
+ if (!route.headers) {
230
+ return;
231
+ }
232
+
233
+ // Apply request header modifications (these will be sent to the backend)
234
+ if (route.headers.request && req.headers) {
235
+ // Create routing context for template resolution
236
+ const routeContext: IRouteContext = {
237
+ domain: req.headers.host as string || '',
238
+ path: req.url || '',
239
+ clientIp: req.socket.remoteAddress?.replace('::ffff:', '') || '',
240
+ serverIp: req.socket.localAddress?.replace('::ffff:', '') || '',
241
+ port: parseInt(req.socket.localPort?.toString() || '0', 10),
242
+ isTls: !!req.socket.encrypted,
243
+ headers: req.headers as Record<string, string>,
244
+ timestamp: Date.now(),
245
+ connectionId: `${Date.now()}-${Math.floor(Math.random() * 10000)}`,
246
+ };
247
+
248
+ for (const [key, value] of Object.entries(route.headers.request)) {
249
+ // Skip if header already exists and we're not overriding
250
+ if (req.headers[key.toLowerCase()] && !value.startsWith('!')) {
251
+ continue;
252
+ }
253
+
254
+ // Handle special delete directive (!delete)
255
+ if (value === '!delete') {
256
+ delete req.headers[key.toLowerCase()];
257
+ logger.debug(`Deleted request header: ${key}`);
258
+ continue;
259
+ }
260
+
261
+ // Handle forced override (!value)
262
+ let finalValue: string;
263
+ if (value.startsWith('!')) {
264
+ // Keep the ! but resolve any templates in the rest
265
+ const templateValue = value.substring(1);
266
+ finalValue = '!' + TemplateUtils.resolveTemplateVariables(templateValue, routeContext);
267
+ } else {
268
+ // Resolve templates in the entire value
269
+ finalValue = TemplateUtils.resolveTemplateVariables(value, routeContext);
270
+ }
271
+
272
+ // Set the header
273
+ req.headers[key.toLowerCase()] = finalValue;
274
+ logger.debug(`Modified request header: ${key}=${finalValue}`);
275
+ }
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Apply header modifications from route configuration to response headers
281
+ * Implements Phase 5.1: Route-based header manipulation for responses
282
+ */
283
+ private static applyResponseHeaderModifications(
284
+ route: IRouteConfig,
285
+ res: plugins.http.ServerResponse,
286
+ logger: ILogger,
287
+ routeContext?: IRouteContext
288
+ ): void {
289
+ // Check if route has response header modifications
290
+ if (!route.headers?.response) {
291
+ return;
292
+ }
293
+
294
+ // Apply response header modifications
295
+ for (const [key, value] of Object.entries(route.headers.response)) {
296
+ // Skip if header already exists and we're not overriding
297
+ if (res.hasHeader(key) && !value.startsWith('!')) {
298
+ continue;
299
+ }
300
+
301
+ // Handle special delete directive (!delete)
302
+ if (value === '!delete') {
303
+ res.removeHeader(key);
304
+ logger.debug(`Deleted response header: ${key}`);
305
+ continue;
306
+ }
307
+
308
+ // Handle forced override (!value)
309
+ let finalValue: string;
310
+ if (value.startsWith('!') && value !== '!delete') {
311
+ // Keep the ! but resolve any templates in the rest
312
+ const templateValue = value.substring(1);
313
+ finalValue = routeContext
314
+ ? '!' + TemplateUtils.resolveTemplateVariables(templateValue, routeContext)
315
+ : '!' + templateValue;
316
+ } else {
317
+ // Resolve templates in the entire value
318
+ finalValue = routeContext
319
+ ? TemplateUtils.resolveTemplateVariables(value, routeContext)
320
+ : value;
321
+ }
322
+
323
+ // Set the header
324
+ res.setHeader(key, finalValue);
325
+ logger.debug(`Modified response header: ${key}=${finalValue}`);
326
+ }
327
+ }
328
+
329
+ // Template resolution is now handled by the TemplateUtils class
330
+ }
@@ -0,0 +1,255 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type { IHttpRouteContext } from '../../core/models/route-context.js';
3
+ import type { ILogger } from './models/types.js';
4
+ import type { IMetricsTracker } from './request-handler.js';
5
+
6
+ /**
7
+ * HTTP/2 Request Handler Helper - handles HTTP/2 streams with specific destinations
8
+ * This is a helper class for the main RequestHandler
9
+ */
10
+ export class Http2RequestHandler {
11
+ /**
12
+ * Handle HTTP/2 stream with direct HTTP/2 backend
13
+ */
14
+ public static async handleHttp2WithHttp2Destination(
15
+ stream: plugins.http2.ServerHttp2Stream,
16
+ headers: plugins.http2.IncomingHttpHeaders,
17
+ destination: { host: string, port: number },
18
+ routeContext: IHttpRouteContext,
19
+ sessions: Map<string, plugins.http2.ClientHttp2Session>,
20
+ logger: ILogger,
21
+ metricsTracker?: IMetricsTracker | null
22
+ ): Promise<void> {
23
+ const key = `${destination.host}:${destination.port}`;
24
+
25
+ // Get or create a client HTTP/2 session
26
+ let session = sessions.get(key);
27
+ if (!session || session.closed || (session as any).destroyed) {
28
+ try {
29
+ // Connect to the backend HTTP/2 server
30
+ session = plugins.http2.connect(`http://${destination.host}:${destination.port}`);
31
+ sessions.set(key, session);
32
+
33
+ // Handle session errors and cleanup
34
+ session.on('error', (err) => {
35
+ logger.error(`HTTP/2 session error to ${key}: ${err.message}`);
36
+ sessions.delete(key);
37
+ });
38
+
39
+ session.on('close', () => {
40
+ logger.debug(`HTTP/2 session closed to ${key}`);
41
+ sessions.delete(key);
42
+ });
43
+ } catch (err) {
44
+ logger.error(`Failed to establish HTTP/2 session to ${key}: ${err.message}`);
45
+ stream.respond({ ':status': 502 });
46
+ stream.end('Bad Gateway: Failed to establish connection to backend');
47
+ if (metricsTracker) metricsTracker.incrementFailedRequests();
48
+ return;
49
+ }
50
+ }
51
+
52
+ try {
53
+ // Build headers for backend HTTP/2 request
54
+ const h2Headers: Record<string, any> = {
55
+ ':method': headers[':method'],
56
+ ':path': headers[':path'],
57
+ ':authority': `${destination.host}:${destination.port}`
58
+ };
59
+
60
+ // Copy other headers, excluding pseudo-headers
61
+ for (const [key, value] of Object.entries(headers)) {
62
+ if (!key.startsWith(':') && typeof value === 'string') {
63
+ h2Headers[key] = value;
64
+ }
65
+ }
66
+
67
+ logger.debug(
68
+ `Proxying HTTP/2 request to ${destination.host}:${destination.port}${headers[':path']}`,
69
+ { method: headers[':method'] }
70
+ );
71
+
72
+ // Create HTTP/2 request stream to the backend
73
+ const h2Stream = session.request(h2Headers);
74
+
75
+ // Pipe client stream to backend stream
76
+ stream.pipe(h2Stream);
77
+
78
+ // Handle responses from the backend
79
+ h2Stream.on('response', (responseHeaders) => {
80
+ // Map status and headers to client response
81
+ const resp: Record<string, any> = {
82
+ ':status': responseHeaders[':status'] as number
83
+ };
84
+
85
+ // Copy non-pseudo headers
86
+ for (const [key, value] of Object.entries(responseHeaders)) {
87
+ if (!key.startsWith(':') && value !== undefined) {
88
+ resp[key] = value;
89
+ }
90
+ }
91
+
92
+ // Send headers to client
93
+ stream.respond(resp);
94
+
95
+ // Pipe backend response to client
96
+ h2Stream.pipe(stream);
97
+
98
+ // Track successful requests
99
+ stream.on('end', () => {
100
+ if (metricsTracker) metricsTracker.incrementRequestsServed();
101
+ logger.debug(
102
+ `HTTP/2 request completed: ${headers[':method']} ${headers[':path']} ${responseHeaders[':status']}`,
103
+ { method: headers[':method'], status: responseHeaders[':status'] }
104
+ );
105
+ });
106
+ });
107
+
108
+ // Handle backend errors
109
+ h2Stream.on('error', (err) => {
110
+ logger.error(`HTTP/2 stream error: ${err.message}`);
111
+
112
+ // Only send error response if headers haven't been sent
113
+ if (!stream.headersSent) {
114
+ stream.respond({ ':status': 502 });
115
+ stream.end(`Bad Gateway: ${err.message}`);
116
+ } else {
117
+ stream.end();
118
+ }
119
+
120
+ if (metricsTracker) metricsTracker.incrementFailedRequests();
121
+ });
122
+
123
+ // Handle client stream errors
124
+ stream.on('error', (err) => {
125
+ logger.debug(`Client HTTP/2 stream error: ${err.message}`);
126
+ h2Stream.destroy();
127
+ if (metricsTracker) metricsTracker.incrementFailedRequests();
128
+ });
129
+
130
+ } catch (err: any) {
131
+ logger.error(`Error handling HTTP/2 request: ${err.message}`);
132
+
133
+ // Only send error response if headers haven't been sent
134
+ if (!stream.headersSent) {
135
+ stream.respond({ ':status': 500 });
136
+ stream.end('Internal Server Error');
137
+ } else {
138
+ stream.end();
139
+ }
140
+
141
+ if (metricsTracker) metricsTracker.incrementFailedRequests();
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Handle HTTP/2 stream with HTTP/1 backend
147
+ */
148
+ public static async handleHttp2WithHttp1Destination(
149
+ stream: plugins.http2.ServerHttp2Stream,
150
+ headers: plugins.http2.IncomingHttpHeaders,
151
+ destination: { host: string, port: number },
152
+ routeContext: IHttpRouteContext,
153
+ logger: ILogger,
154
+ metricsTracker?: IMetricsTracker | null
155
+ ): Promise<void> {
156
+ try {
157
+ // Build headers for HTTP/1 proxy request, excluding HTTP/2 pseudo-headers
158
+ const outboundHeaders: Record<string, string> = {};
159
+ for (const [key, value] of Object.entries(headers)) {
160
+ if (typeof key === 'string' && typeof value === 'string' && !key.startsWith(':')) {
161
+ outboundHeaders[key] = value;
162
+ }
163
+ }
164
+
165
+ // Always rewrite host header to match target
166
+ outboundHeaders.host = `${destination.host}:${destination.port}`;
167
+
168
+ logger.debug(
169
+ `Proxying HTTP/2 request to HTTP/1 backend ${destination.host}:${destination.port}${headers[':path']}`,
170
+ { method: headers[':method'] }
171
+ );
172
+
173
+ // Create HTTP/1 proxy request
174
+ const proxyReq = plugins.http.request(
175
+ {
176
+ hostname: destination.host,
177
+ port: destination.port,
178
+ path: headers[':path'] as string,
179
+ method: headers[':method'] as string,
180
+ headers: outboundHeaders
181
+ },
182
+ (proxyRes) => {
183
+ // Map status and headers back to HTTP/2
184
+ const responseHeaders: Record<string, number | string | string[]> = {
185
+ ':status': proxyRes.statusCode || 500
186
+ };
187
+
188
+ // Copy headers from HTTP/1 response to HTTP/2 response
189
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
190
+ if (value !== undefined) {
191
+ responseHeaders[key] = value as string | string[];
192
+ }
193
+ }
194
+
195
+ // Send headers to client
196
+ stream.respond(responseHeaders);
197
+
198
+ // Pipe HTTP/1 response to HTTP/2 stream
199
+ proxyRes.pipe(stream);
200
+
201
+ // Clean up when client disconnects
202
+ stream.on('close', () => proxyReq.destroy());
203
+ stream.on('error', () => proxyReq.destroy());
204
+
205
+ // Track successful requests
206
+ stream.on('end', () => {
207
+ if (metricsTracker) metricsTracker.incrementRequestsServed();
208
+ logger.debug(
209
+ `HTTP/2 to HTTP/1 request completed: ${headers[':method']} ${headers[':path']} ${proxyRes.statusCode}`,
210
+ { method: headers[':method'], status: proxyRes.statusCode }
211
+ );
212
+ });
213
+ }
214
+ );
215
+
216
+ // Handle proxy request errors
217
+ proxyReq.on('error', (err) => {
218
+ logger.error(`HTTP/1 proxy error: ${err.message}`);
219
+
220
+ // Only send error response if headers haven't been sent
221
+ if (!stream.headersSent) {
222
+ stream.respond({ ':status': 502 });
223
+ stream.end(`Bad Gateway: ${err.message}`);
224
+ } else {
225
+ stream.end();
226
+ }
227
+
228
+ if (metricsTracker) metricsTracker.incrementFailedRequests();
229
+ });
230
+
231
+ // Pipe client stream to proxy request
232
+ stream.pipe(proxyReq);
233
+
234
+ // Handle client stream errors
235
+ stream.on('error', (err) => {
236
+ logger.debug(`Client HTTP/2 stream error: ${err.message}`);
237
+ proxyReq.destroy();
238
+ if (metricsTracker) metricsTracker.incrementFailedRequests();
239
+ });
240
+
241
+ } catch (err: any) {
242
+ logger.error(`Error handling HTTP/2 to HTTP/1 request: ${err.message}`);
243
+
244
+ // Only send error response if headers haven't been sent
245
+ if (!stream.headersSent) {
246
+ stream.respond({ ':status': 500 });
247
+ stream.end('Internal Server Error');
248
+ } else {
249
+ stream.end();
250
+ }
251
+
252
+ if (metricsTracker) metricsTracker.incrementFailedRequests();
253
+ }
254
+ }
255
+ }