@push.rocks/smartproxy 22.4.2 → 23.0.0

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 (101) hide show
  1. package/changelog.md +36 -0
  2. package/dist_rust/rustproxy +0 -0
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/index.d.ts +1 -6
  5. package/dist_ts/index.js +3 -11
  6. package/dist_ts/protocols/common/fragment-handler.js +5 -1
  7. package/dist_ts/proxies/index.d.ts +1 -6
  8. package/dist_ts/proxies/index.js +2 -8
  9. package/dist_ts/proxies/smart-proxy/index.d.ts +5 -10
  10. package/dist_ts/proxies/smart-proxy/index.js +7 -13
  11. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -2
  12. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  13. package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
  14. package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
  15. package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
  16. package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
  17. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
  18. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
  19. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
  20. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
  21. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
  22. package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -621
  23. package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
  24. package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
  25. package/dist_ts/routing/index.d.ts +1 -1
  26. package/dist_ts/routing/index.js +3 -3
  27. package/dist_ts/routing/models/http-types.d.ts +119 -4
  28. package/dist_ts/routing/models/http-types.js +93 -5
  29. package/package.json +1 -1
  30. package/readme.md +444 -219
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/index.ts +4 -15
  33. package/ts/protocols/common/fragment-handler.ts +4 -0
  34. package/ts/proxies/index.ts +1 -12
  35. package/ts/proxies/smart-proxy/index.ts +6 -13
  36. package/ts/proxies/smart-proxy/models/interfaces.ts +6 -4
  37. package/ts/proxies/smart-proxy/models/route-types.ts +0 -2
  38. package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
  39. package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
  40. package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
  41. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
  42. package/ts/proxies/smart-proxy/smart-proxy.ts +282 -798
  43. package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
  44. package/ts/routing/index.ts +2 -2
  45. package/ts/routing/models/http-types.ts +147 -4
  46. package/dist_ts/proxies/nftables-proxy/index.d.ts +0 -6
  47. package/dist_ts/proxies/nftables-proxy/index.js +0 -7
  48. package/dist_ts/proxies/nftables-proxy/models/errors.d.ts +0 -15
  49. package/dist_ts/proxies/nftables-proxy/models/errors.js +0 -28
  50. package/dist_ts/proxies/nftables-proxy/models/index.d.ts +0 -5
  51. package/dist_ts/proxies/nftables-proxy/models/index.js +0 -6
  52. package/dist_ts/proxies/nftables-proxy/models/interfaces.d.ts +0 -75
  53. package/dist_ts/proxies/nftables-proxy/models/interfaces.js +0 -5
  54. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +0 -124
  55. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +0 -1374
  56. package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +0 -9
  57. package/dist_ts/proxies/nftables-proxy/utils/index.js +0 -12
  58. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +0 -66
  59. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +0 -131
  60. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +0 -39
  61. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +0 -112
  62. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +0 -59
  63. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +0 -130
  64. package/ts/proxies/http-proxy/connection-pool.ts +0 -228
  65. package/ts/proxies/http-proxy/context-creator.ts +0 -145
  66. package/ts/proxies/http-proxy/default-certificates.ts +0 -150
  67. package/ts/proxies/http-proxy/function-cache.ts +0 -279
  68. package/ts/proxies/http-proxy/handlers/index.ts +0 -5
  69. package/ts/proxies/http-proxy/http-proxy.ts +0 -669
  70. package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
  71. package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
  72. package/ts/proxies/http-proxy/index.ts +0 -18
  73. package/ts/proxies/http-proxy/models/http-types.ts +0 -148
  74. package/ts/proxies/http-proxy/models/index.ts +0 -5
  75. package/ts/proxies/http-proxy/models/types.ts +0 -125
  76. package/ts/proxies/http-proxy/request-handler.ts +0 -878
  77. package/ts/proxies/http-proxy/security-manager.ts +0 -413
  78. package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
  79. package/ts/proxies/nftables-proxy/index.ts +0 -6
  80. package/ts/proxies/nftables-proxy/models/errors.ts +0 -30
  81. package/ts/proxies/nftables-proxy/models/index.ts +0 -5
  82. package/ts/proxies/nftables-proxy/models/interfaces.ts +0 -94
  83. package/ts/proxies/nftables-proxy/nftables-proxy.ts +0 -1754
  84. package/ts/proxies/nftables-proxy/utils/index.ts +0 -38
  85. package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +0 -162
  86. package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +0 -125
  87. package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +0 -156
  88. package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
  89. package/ts/proxies/smart-proxy/cert-store.ts +0 -92
  90. package/ts/proxies/smart-proxy/certificate-manager.ts +0 -895
  91. package/ts/proxies/smart-proxy/connection-manager.ts +0 -809
  92. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -213
  93. package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
  94. package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
  95. package/ts/proxies/smart-proxy/port-manager.ts +0 -358
  96. package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1712
  97. package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
  98. package/ts/proxies/smart-proxy/security-manager.ts +0 -269
  99. package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
  100. package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
  101. package/ts/proxies/smart-proxy/tls-manager.ts +0 -171
@@ -1,669 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import {
3
- createLogger,
4
- } from './models/types.js';
5
- import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
6
- import type {
7
- IHttpProxyOptions,
8
- ILogger
9
- } from './models/types.js';
10
- import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
11
- import type { IRouteContext, IHttpRouteContext } from '../../core/models/route-context.js';
12
- import { createBaseRouteContext } from '../../core/models/route-context.js';
13
- import { DefaultCertificateProvider } from './default-certificates.js';
14
- import { ConnectionPool } from './connection-pool.js';
15
- import { RequestHandler, type IMetricsTracker } from './request-handler.js';
16
- import { WebSocketHandler } from './websocket-handler.js';
17
- import { HttpRouter } from '../../routing/router/index.js';
18
- import { cleanupSocket } from '../../core/utils/socket-utils.js';
19
- import { FunctionCache } from './function-cache.js';
20
- import { SecurityManager } from './security-manager.js';
21
- import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
22
-
23
- /**
24
- * HttpProxy provides a reverse proxy with TLS termination, WebSocket support,
25
- * automatic certificate management, and high-performance connection pooling.
26
- * Handles all HTTP/HTTPS traffic including redirects, ACME challenges, and static routes.
27
- */
28
- export class HttpProxy implements IMetricsTracker {
29
- // Provide a minimal JSON representation to avoid circular references during deep equality checks
30
- public toJSON(): any {
31
- return {};
32
- }
33
- // Configuration
34
- public options: IHttpProxyOptions;
35
- public routes: IRouteConfig[] = [];
36
-
37
- // Server instances (HTTP/2 with HTTP/1 fallback)
38
- public httpsServer: plugins.http2.Http2SecureServer;
39
-
40
- // Core components
41
- private defaultCertProvider: DefaultCertificateProvider;
42
- private connectionPool: ConnectionPool;
43
- private requestHandler: RequestHandler;
44
- private webSocketHandler: WebSocketHandler;
45
- private router = new HttpRouter(); // Unified HTTP router
46
- private routeManager: RouteManager;
47
- private functionCache: FunctionCache;
48
- private securityManager: SecurityManager;
49
-
50
- // State tracking
51
- public socketMap = new plugins.lik.ObjectMap<plugins.net.Socket>();
52
- public activeContexts: Set<string> = new Set();
53
- public connectedClients: number = 0;
54
- public startTime: number = 0;
55
- public requestsServed: number = 0;
56
- public failedRequests: number = 0;
57
-
58
- // Tracking for SmartProxy integration
59
- private portProxyConnections: number = 0;
60
- private tlsTerminatedConnections: number = 0;
61
-
62
- // Timers
63
- private metricsInterval: NodeJS.Timeout;
64
- private connectionPoolCleanupInterval: NodeJS.Timeout;
65
-
66
- // Logger
67
- private logger: ILogger;
68
-
69
- /**
70
- * Creates a new HttpProxy instance
71
- */
72
- constructor(optionsArg: IHttpProxyOptions) {
73
- // Set default options
74
- this.options = {
75
- port: optionsArg.port,
76
- maxConnections: optionsArg.maxConnections || 10000,
77
- keepAliveTimeout: optionsArg.keepAliveTimeout || 120000, // 2 minutes
78
- headersTimeout: optionsArg.headersTimeout || 60000, // 1 minute
79
- logLevel: optionsArg.logLevel || 'info',
80
- cors: optionsArg.cors || {
81
- allowOrigin: '*',
82
- allowMethods: 'GET, POST, PUT, DELETE, OPTIONS',
83
- allowHeaders: 'Content-Type, Authorization',
84
- maxAge: 86400
85
- },
86
- // Defaults for SmartProxy integration
87
- connectionPoolSize: optionsArg.connectionPoolSize || 50,
88
- portProxyIntegration: optionsArg.portProxyIntegration || false,
89
- // Backend protocol (http1 or http2)
90
- backendProtocol: optionsArg.backendProtocol || 'http1',
91
- // Default ACME options
92
- acme: {
93
- enabled: optionsArg.acme?.enabled || false,
94
- port: optionsArg.acme?.port || 80,
95
- accountEmail: optionsArg.acme?.accountEmail || 'admin@example.com',
96
- useProduction: optionsArg.acme?.useProduction || false, // Default to staging for safety
97
- renewThresholdDays: optionsArg.acme?.renewThresholdDays || 30,
98
- autoRenew: optionsArg.acme?.autoRenew !== false, // Default to true
99
- certificateStore: optionsArg.acme?.certificateStore || './certs',
100
- skipConfiguredCerts: optionsArg.acme?.skipConfiguredCerts || false
101
- }
102
- };
103
-
104
- // Initialize logger
105
- this.logger = createLogger(this.options.logLevel);
106
-
107
- // Initialize route manager
108
- this.routeManager = new RouteManager({
109
- logger: this.logger,
110
- enableDetailedLogging: this.options.logLevel === 'debug',
111
- routes: []
112
- });
113
-
114
- // Initialize function cache
115
- this.functionCache = new FunctionCache(this.logger, {
116
- maxCacheSize: this.options.functionCacheSize || 1000,
117
- defaultTtl: this.options.functionCacheTtl || 5000
118
- });
119
-
120
- // Initialize security manager
121
- this.securityManager = new SecurityManager(
122
- this.logger,
123
- [],
124
- this.options.maxConnectionsPerIP || 100,
125
- this.options.connectionRateLimitPerMinute || 300
126
- );
127
-
128
- // Initialize other components
129
- this.defaultCertProvider = new DefaultCertificateProvider(this.logger);
130
- this.connectionPool = new ConnectionPool(this.options);
131
- this.requestHandler = new RequestHandler(
132
- this.options,
133
- this.connectionPool,
134
- this.routeManager,
135
- this.functionCache,
136
- this.router
137
- );
138
- this.webSocketHandler = new WebSocketHandler(
139
- this.options,
140
- this.connectionPool,
141
- this.routes // Pass current routes to WebSocketHandler
142
- );
143
-
144
- // Connect request handler to this metrics tracker
145
- this.requestHandler.setMetricsTracker(this);
146
-
147
- // Initialize with any provided routes
148
- if (this.options.routes && this.options.routes.length > 0) {
149
- this.updateRouteConfigs(this.options.routes);
150
- }
151
- }
152
-
153
- /**
154
- * Implements IMetricsTracker interface to increment request counters
155
- */
156
- public incrementRequestsServed(): void {
157
- this.requestsServed++;
158
- }
159
-
160
- /**
161
- * Implements IMetricsTracker interface to increment failed request counters
162
- */
163
- public incrementFailedRequests(): void {
164
- this.failedRequests++;
165
- }
166
-
167
- /**
168
- * Returns the port number this HttpProxy is listening on
169
- * Useful for SmartProxy to determine where to forward connections
170
- */
171
- public getListeningPort(): number {
172
- // If the server is running, get the actual listening port
173
- if (this.httpsServer && this.httpsServer.address()) {
174
- const address = this.httpsServer.address();
175
- if (address && typeof address === 'object' && 'port' in address) {
176
- return address.port;
177
- }
178
- }
179
- // Fallback to configured port
180
- return this.options.port;
181
- }
182
-
183
- /**
184
- * Updates the server capacity settings
185
- * @param maxConnections Maximum number of simultaneous connections
186
- * @param keepAliveTimeout Keep-alive timeout in milliseconds
187
- * @param connectionPoolSize Size of the connection pool per backend
188
- */
189
- public updateCapacity(maxConnections?: number, keepAliveTimeout?: number, connectionPoolSize?: number): void {
190
- if (maxConnections !== undefined) {
191
- this.options.maxConnections = maxConnections;
192
- this.logger.info(`Updated max connections to ${maxConnections}`);
193
- }
194
-
195
- if (keepAliveTimeout !== undefined) {
196
- this.options.keepAliveTimeout = keepAliveTimeout;
197
-
198
- if (this.httpsServer) {
199
- // HTTP/2 servers have setTimeout method for timeout management
200
- this.httpsServer.setTimeout(keepAliveTimeout);
201
- this.logger.info(`Updated server timeout to ${keepAliveTimeout}ms`);
202
- }
203
- }
204
-
205
- if (connectionPoolSize !== undefined) {
206
- this.options.connectionPoolSize = connectionPoolSize;
207
- this.logger.info(`Updated connection pool size to ${connectionPoolSize}`);
208
-
209
- // Clean up excess connections in the pool
210
- this.connectionPool.cleanupConnectionPool();
211
- }
212
- }
213
-
214
- /**
215
- * Returns current server metrics
216
- * Useful for SmartProxy to determine which HttpProxy to use for load balancing
217
- */
218
- public getMetrics(): any {
219
- return {
220
- activeConnections: this.connectedClients,
221
- totalRequests: this.requestsServed,
222
- failedRequests: this.failedRequests,
223
- portProxyConnections: this.portProxyConnections,
224
- tlsTerminatedConnections: this.tlsTerminatedConnections,
225
- connectionPoolSize: this.connectionPool.getPoolStatus(),
226
- uptime: Math.floor((Date.now() - this.startTime) / 1000),
227
- memoryUsage: process.memoryUsage(),
228
- activeWebSockets: this.webSocketHandler.getConnectionInfo().activeConnections,
229
- functionCache: this.functionCache.getStats()
230
- };
231
- }
232
-
233
- /**
234
- * Starts the proxy server
235
- */
236
- public async start(): Promise<void> {
237
- this.startTime = Date.now();
238
-
239
- // Create HTTP/2 server with HTTP/1 fallback
240
- const defaultCerts = this.defaultCertProvider.getDefaultCertificates();
241
- this.httpsServer = plugins.http2.createSecureServer(
242
- {
243
- key: defaultCerts.key,
244
- cert: defaultCerts.cert,
245
- allowHTTP1: true,
246
- ALPNProtocols: ['h2', 'http/1.1']
247
- }
248
- );
249
-
250
- // Track raw TCP connections for metrics and limits
251
- this.setupConnectionTracking();
252
-
253
- // Handle incoming HTTP/2 streams
254
- this.httpsServer.on('stream', (stream: plugins.http2.ServerHttp2Stream, headers: plugins.http2.IncomingHttpHeaders) => {
255
- this.requestHandler.handleHttp2(stream, headers);
256
- });
257
- // Handle HTTP/1.x fallback requests
258
- this.httpsServer.on('request', (req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse) => {
259
- this.requestHandler.handleRequest(req, res);
260
- });
261
-
262
- // Setup WebSocket support on HTTP/1 fallback
263
- this.webSocketHandler.initialize(this.httpsServer as any);
264
- // Start metrics logging
265
- this.setupMetricsCollection();
266
- // Start periodic connection pool cleanup
267
- this.connectionPoolCleanupInterval = this.connectionPool.setupPeriodicCleanup();
268
-
269
- // Start the server
270
- return new Promise((resolve) => {
271
- this.httpsServer.listen(this.options.port, () => {
272
- this.logger.info(`HttpProxy started on port ${this.options.port}`);
273
- resolve();
274
- });
275
- });
276
- }
277
-
278
- /**
279
- * Check if an address is a loopback address (IPv4 or IPv6)
280
- */
281
- private isLoopback(addr?: string): boolean {
282
- if (!addr) return false;
283
- // Check for IPv6 loopback
284
- if (addr === '::1') return true;
285
- // Handle IPv6-mapped IPv4 addresses
286
- if (addr.startsWith('::ffff:')) {
287
- addr = addr.substring(7);
288
- }
289
- // Check for IPv4 loopback range (127.0.0.0/8)
290
- return addr.startsWith('127.');
291
- }
292
-
293
- /**
294
- * Sets up tracking of TCP connections
295
- */
296
- private setupConnectionTracking(): void {
297
- this.httpsServer.on('connection', (connection: plugins.net.Socket) => {
298
- let remoteIP = connection.remoteAddress || '';
299
- const connectionId = Math.random().toString(36).substring(2, 15);
300
- const isFromSmartProxy = this.options.portProxyIntegration && this.isLoopback(connection.remoteAddress);
301
-
302
- // For SmartProxy connections, wait for CLIENT_IP header
303
- if (isFromSmartProxy) {
304
- const MAX_PREFACE = 256; // bytes - prevent DoS
305
- const HEADER_TIMEOUT_MS = 2000; // timeout for header parsing (increased for slow networks)
306
- let headerTimer: NodeJS.Timeout | undefined;
307
- let buffered = Buffer.alloc(0);
308
-
309
- const onData = (chunk: Buffer) => {
310
- buffered = Buffer.concat([buffered, chunk]);
311
-
312
- // Prevent unbounded growth
313
- if (buffered.length > MAX_PREFACE) {
314
- connection.removeListener('data', onData);
315
- if (headerTimer) clearTimeout(headerTimer);
316
- this.logger.warn('Header preface too large, closing connection');
317
- connection.destroy();
318
- return;
319
- }
320
-
321
- const idx = buffered.indexOf('\r\n');
322
- if (idx !== -1) {
323
- const headerLine = buffered.slice(0, idx).toString('utf8');
324
- if (headerLine.startsWith('CLIENT_IP:')) {
325
- remoteIP = headerLine.substring(10).trim();
326
- this.logger.debug(`Extracted client IP from SmartProxy: ${remoteIP}`);
327
- }
328
-
329
- // Clean up listener and timer
330
- connection.removeListener('data', onData);
331
- if (headerTimer) clearTimeout(headerTimer);
332
-
333
- // Put remaining data back onto the stream
334
- const remaining = buffered.slice(idx + 2);
335
- if (remaining.length > 0) {
336
- connection.unshift(remaining);
337
- }
338
-
339
- // Store the real IP on the connection
340
- connection._realRemoteIP = remoteIP;
341
-
342
- // Validate the real IP
343
- const ipValidation = this.securityManager.validateIP(remoteIP);
344
- if (!ipValidation.allowed) {
345
- connectionLogDeduplicator.log(
346
- 'ip-rejected',
347
- 'warn',
348
- `HttpProxy connection rejected (via SmartProxy)`,
349
- { remoteIP, reason: ipValidation.reason, component: 'http-proxy' },
350
- remoteIP
351
- );
352
- connection.destroy();
353
- return;
354
- }
355
-
356
- // Track connection by real IP
357
- this.securityManager.trackConnectionByIP(remoteIP, connectionId);
358
- }
359
- };
360
-
361
- // Set timeout for header parsing
362
- headerTimer = setTimeout(() => {
363
- connection.removeListener('data', onData);
364
- this.logger.warn('Header parsing timeout, closing connection');
365
- connection.destroy();
366
- }, HEADER_TIMEOUT_MS);
367
-
368
- // Unref the timer so it doesn't keep the process alive
369
- if (headerTimer.unref) headerTimer.unref();
370
-
371
- // Use prependListener to get data first
372
- connection.prependListener('data', onData);
373
- } else {
374
- // Direct connection - validate immediately
375
- const ipValidation = this.securityManager.validateIP(remoteIP);
376
- if (!ipValidation.allowed) {
377
- connectionLogDeduplicator.log(
378
- 'ip-rejected',
379
- 'warn',
380
- `HttpProxy connection rejected`,
381
- { remoteIP, reason: ipValidation.reason, component: 'http-proxy' },
382
- remoteIP
383
- );
384
- connection.destroy();
385
- return;
386
- }
387
-
388
- // Track connection by IP
389
- this.securityManager.trackConnectionByIP(remoteIP, connectionId);
390
- }
391
-
392
- // Then check global max connections
393
- if (this.socketMap.getArray().length >= this.options.maxConnections) {
394
- connectionLogDeduplicator.log(
395
- 'connection-rejected',
396
- 'warn',
397
- 'HttpProxy max connections reached',
398
- {
399
- reason: 'global-limit',
400
- currentConnections: this.socketMap.getArray().length,
401
- maxConnections: this.options.maxConnections,
402
- component: 'http-proxy'
403
- },
404
- 'http-proxy-global-limit'
405
- );
406
- connection.destroy();
407
- return;
408
- }
409
-
410
- // Add connection to tracking with metadata
411
- connection._connectionId = connectionId;
412
- connection._remoteIP = remoteIP;
413
- this.socketMap.add(connection);
414
- this.connectedClients = this.socketMap.getArray().length;
415
-
416
- // Check for connection from SmartProxy by inspecting the source port
417
- const localPort = connection.localPort || 0;
418
- const remotePort = connection.remotePort || 0;
419
-
420
- // If this connection is from a SmartProxy
421
- if (isFromSmartProxy) {
422
- this.portProxyConnections++;
423
- this.logger.debug(`New connection from SmartProxy for client ${remoteIP} (local: ${localPort}, remote: ${remotePort})`);
424
- } else {
425
- this.logger.debug(`New direct connection from ${remoteIP} (local: ${localPort}, remote: ${remotePort})`);
426
- }
427
-
428
- // Setup connection cleanup handlers
429
- const cleanupConnection = () => {
430
- if (this.socketMap.checkForObject(connection)) {
431
- this.socketMap.remove(connection);
432
- this.connectedClients = this.socketMap.getArray().length;
433
-
434
- // Remove IP tracking
435
- const connId = connection._connectionId;
436
- const connIP = connection._realRemoteIP || connection._remoteIP;
437
- if (connId && connIP) {
438
- this.securityManager.removeConnectionByIP(connIP, connId);
439
- }
440
-
441
- // If this was a SmartProxy connection, decrement the counter
442
- if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) {
443
- this.portProxyConnections--;
444
- }
445
-
446
- this.logger.debug(`Connection closed from ${connIP || 'unknown'}. ${this.connectedClients} connections remaining`);
447
- }
448
- };
449
-
450
- connection.on('close', cleanupConnection);
451
- connection.on('error', (err) => {
452
- this.logger.debug('Connection error', err);
453
- cleanupConnection();
454
- });
455
- connection.on('end', cleanupConnection);
456
- });
457
-
458
- // Track TLS handshake completions
459
- this.httpsServer.on('secureConnection', (tlsSocket) => {
460
- this.tlsTerminatedConnections++;
461
- this.logger.debug('TLS handshake completed, connection secured');
462
- });
463
- }
464
-
465
- /**
466
- * Sets up metrics collection
467
- */
468
- private setupMetricsCollection(): void {
469
- this.metricsInterval = setInterval(() => {
470
- const uptime = Math.floor((Date.now() - this.startTime) / 1000);
471
- const metrics = {
472
- uptime,
473
- activeConnections: this.connectedClients,
474
- totalRequests: this.requestsServed,
475
- failedRequests: this.failedRequests,
476
- portProxyConnections: this.portProxyConnections,
477
- tlsTerminatedConnections: this.tlsTerminatedConnections,
478
- activeWebSockets: this.webSocketHandler.getConnectionInfo().activeConnections,
479
- memoryUsage: process.memoryUsage(),
480
- activeContexts: Array.from(this.activeContexts),
481
- connectionPool: this.connectionPool.getPoolStatus()
482
- };
483
-
484
- this.logger.debug('Proxy metrics', metrics);
485
- }, 60000); // Log metrics every minute
486
-
487
- // Don't keep process alive just for metrics
488
- if (this.metricsInterval.unref) {
489
- this.metricsInterval.unref();
490
- }
491
- }
492
-
493
- /**
494
- * Updates the route configurations - this is the primary method for configuring HttpProxy
495
- * @param routes The new route configurations to use
496
- */
497
- public async updateRouteConfigs(routes: IRouteConfig[]): Promise<void> {
498
- this.logger.info(`Updating route configurations (${routes.length} routes)`);
499
-
500
- // Update routes in RouteManager, modern router, WebSocketHandler, and SecurityManager
501
- this.routeManager.updateRoutes(routes);
502
- this.router.setRoutes(routes);
503
- this.webSocketHandler.setRoutes(routes);
504
- this.requestHandler.securityManager.setRoutes(routes);
505
- this.routes = routes;
506
-
507
- // Collect all domains and certificates for configuration
508
- const currentHostnames = new Set<string>();
509
- const certificateUpdates = new Map<string, { cert: string, key: string }>();
510
-
511
- // Process each route to extract domain and certificate information
512
- for (const route of routes) {
513
- // Skip non-forward routes or routes without domains
514
- if (route.action.type !== 'forward' || !route.match.domains) {
515
- continue;
516
- }
517
-
518
- // Get domains from route
519
- const domains = Array.isArray(route.match.domains)
520
- ? route.match.domains
521
- : [route.match.domains];
522
-
523
- // Process each domain
524
- for (const domain of domains) {
525
- // Skip wildcard domains for direct host configuration
526
- if (domain.includes('*')) {
527
- continue;
528
- }
529
-
530
- currentHostnames.add(domain);
531
-
532
- // Check if we have a static certificate for this domain
533
- if (route.action.tls?.certificate && route.action.tls.certificate !== 'auto') {
534
- certificateUpdates.set(domain, {
535
- cert: route.action.tls.certificate.cert,
536
- key: route.action.tls.certificate.key
537
- });
538
- }
539
- }
540
- }
541
-
542
- // Update certificate cache with any static certificates
543
- for (const [domain, certData] of certificateUpdates.entries()) {
544
- try {
545
- this.defaultCertProvider.updateCertificate(
546
- domain,
547
- certData.cert,
548
- certData.key
549
- );
550
-
551
- this.activeContexts.add(domain);
552
- } catch (error) {
553
- this.logger.error(`Failed to add SSL context for ${domain}`, error);
554
- }
555
- }
556
-
557
- // Clean up removed contexts
558
- for (const hostname of this.activeContexts) {
559
- if (!currentHostnames.has(hostname)) {
560
- this.logger.info(`Hostname ${hostname} removed from configuration`);
561
- this.activeContexts.delete(hostname);
562
- }
563
- }
564
-
565
- // Update the router with new routes
566
- this.router.setRoutes(routes);
567
-
568
- // Update WebSocket handler with new routes
569
- this.webSocketHandler.setRoutes(routes);
570
-
571
- this.logger.info(`Route configuration updated with ${routes.length} routes`);
572
- }
573
-
574
- // Legacy methods have been removed.
575
- // Please use updateRouteConfigs() directly with modern route-based configuration.
576
-
577
- /**
578
- * Adds default headers to be included in all responses
579
- */
580
- public async addDefaultHeaders(headersArg: { [key: string]: string }): Promise<void> {
581
- this.logger.info('Adding default headers', headersArg);
582
- this.requestHandler.setDefaultHeaders(headersArg);
583
- }
584
-
585
- /**
586
- * Stops the proxy server
587
- */
588
- public async stop(): Promise<void> {
589
- this.logger.info('Stopping HttpProxy server');
590
-
591
- // Clear intervals
592
- if (this.metricsInterval) {
593
- clearInterval(this.metricsInterval);
594
- }
595
-
596
- if (this.connectionPoolCleanupInterval) {
597
- clearInterval(this.connectionPoolCleanupInterval);
598
- }
599
-
600
- // Stop WebSocket handler
601
- this.webSocketHandler.shutdown();
602
-
603
- // Destroy request handler (cleans up intervals and caches)
604
- if (this.requestHandler && typeof this.requestHandler.destroy === 'function') {
605
- this.requestHandler.destroy();
606
- }
607
-
608
- // Close all tracked sockets
609
- const socketCleanupPromises = this.socketMap.getArray().map(socket =>
610
- cleanupSocket(socket, 'http-proxy-stop', { immediate: true })
611
- );
612
- await Promise.all(socketCleanupPromises);
613
-
614
- // Close all connection pool connections
615
- this.connectionPool.closeAllConnections();
616
-
617
- // Certificate management cleanup is handled by SmartCertManager
618
-
619
- // Flush any pending deduplicated logs
620
- connectionLogDeduplicator.flushAll();
621
-
622
- // Close the HTTPS server
623
- return new Promise((resolve) => {
624
- this.httpsServer.close(() => {
625
- this.logger.info('HttpProxy server stopped successfully');
626
- resolve();
627
- });
628
- });
629
- }
630
-
631
- /**
632
- * Requests a new certificate for a domain
633
- * This can be used to manually trigger certificate issuance
634
- * @param domain The domain to request a certificate for
635
- * @returns A promise that resolves when the request is submitted (not when the certificate is issued)
636
- */
637
- public async requestCertificate(domain: string): Promise<boolean> {
638
- this.logger.warn('requestCertificate is deprecated - use SmartCertManager instead');
639
- return false;
640
- }
641
-
642
- /**
643
- * Update certificate for a domain
644
- *
645
- * This method allows direct updates of certificates from external sources
646
- * like Port80Handler or custom certificate providers.
647
- *
648
- * @param domain The domain to update certificate for
649
- * @param certificate The new certificate (public key)
650
- * @param privateKey The new private key
651
- * @param expiryDate Optional expiry date
652
- */
653
- public updateCertificate(
654
- domain: string,
655
- certificate: string,
656
- privateKey: string,
657
- expiryDate?: Date
658
- ): void {
659
- this.logger.info(`Updating certificate for ${domain}`);
660
- this.defaultCertProvider.updateCertificate(domain, certificate, privateKey);
661
- }
662
-
663
- /**
664
- * Gets all route configurations currently in use
665
- */
666
- public getRouteConfigs(): IRouteConfig[] {
667
- return this.routeManager.getRoutes();
668
- }
669
- }