@push.rocks/smartproxy 19.5.19 → 19.5.21
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.
- package/dist_ts/core/models/index.d.ts +2 -0
- package/dist_ts/core/models/index.js +3 -1
- package/dist_ts/core/models/socket-types.d.ts +14 -0
- package/dist_ts/core/models/socket-types.js +15 -0
- package/dist_ts/core/models/wrapped-socket.d.ts +34 -0
- package/dist_ts/core/models/wrapped-socket.js +82 -0
- package/dist_ts/core/routing/index.d.ts +11 -0
- package/dist_ts/core/routing/index.js +17 -0
- package/dist_ts/core/routing/matchers/domain.d.ts +34 -0
- package/dist_ts/core/routing/matchers/domain.js +91 -0
- package/dist_ts/core/routing/matchers/header.d.ts +32 -0
- package/dist_ts/core/routing/matchers/header.js +94 -0
- package/dist_ts/core/routing/matchers/index.d.ts +18 -0
- package/dist_ts/core/routing/matchers/index.js +20 -0
- package/dist_ts/core/routing/matchers/ip.d.ts +53 -0
- package/dist_ts/core/routing/matchers/ip.js +169 -0
- package/dist_ts/core/routing/matchers/path.d.ts +44 -0
- package/dist_ts/core/routing/matchers/path.js +148 -0
- package/dist_ts/core/routing/route-manager.d.ts +88 -0
- package/dist_ts/core/routing/route-manager.js +342 -0
- package/dist_ts/core/routing/route-utils.d.ts +28 -0
- package/dist_ts/core/routing/route-utils.js +67 -0
- package/dist_ts/core/routing/specificity.d.ts +30 -0
- package/dist_ts/core/routing/specificity.js +115 -0
- package/dist_ts/core/routing/types.d.ts +41 -0
- package/dist_ts/core/routing/types.js +5 -0
- package/dist_ts/core/utils/index.d.ts +1 -2
- package/dist_ts/core/utils/index.js +2 -3
- package/dist_ts/core/utils/proxy-protocol.d.ts +45 -0
- package/dist_ts/core/utils/proxy-protocol.js +201 -0
- package/dist_ts/core/utils/route-manager.d.ts +0 -30
- package/dist_ts/core/utils/route-manager.js +6 -47
- package/dist_ts/core/utils/route-utils.d.ts +2 -68
- package/dist_ts/core/utils/route-utils.js +21 -218
- package/dist_ts/core/utils/security-utils.js +4 -4
- package/dist_ts/index.d.ts +2 -5
- package/dist_ts/index.js +5 -11
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +0 -1
- package/dist_ts/proxies/http-proxy/http-proxy.js +15 -60
- package/dist_ts/proxies/http-proxy/models/types.d.ts +0 -90
- package/dist_ts/proxies/http-proxy/models/types.js +1 -242
- package/dist_ts/proxies/http-proxy/request-handler.d.ts +3 -5
- package/dist_ts/proxies/http-proxy/request-handler.js +20 -171
- package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +2 -5
- package/dist_ts/proxies/http-proxy/websocket-handler.js +15 -23
- package/dist_ts/proxies/index.d.ts +2 -2
- package/dist_ts/proxies/index.js +4 -3
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +3 -1
- package/dist_ts/proxies/smart-proxy/connection-manager.js +17 -7
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +2 -1
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +5 -2
- package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/index.js +2 -2
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +7 -2
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +155 -35
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +15 -4
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +10 -43
- package/dist_ts/routing/router/http-router.d.ts +89 -0
- package/dist_ts/routing/router/http-router.js +205 -0
- package/dist_ts/routing/router/index.d.ts +2 -5
- package/dist_ts/routing/router/index.js +3 -4
- package/package.json +1 -1
- package/readme.delete.md +187 -0
- package/readme.hints.md +196 -1
- package/readme.plan.md +625 -0
- package/readme.proxy-chain-summary.md +112 -0
- package/readme.proxy-protocol-example.md +462 -0
- package/readme.proxy-protocol.md +415 -0
- package/readme.routing.md +341 -0
- package/ts/core/models/index.ts +2 -0
- package/ts/core/models/socket-types.ts +21 -0
- package/ts/core/models/wrapped-socket.ts +99 -0
- package/ts/core/routing/index.ts +21 -0
- package/ts/core/routing/matchers/domain.ts +119 -0
- package/ts/core/routing/matchers/header.ts +120 -0
- package/ts/core/routing/matchers/index.ts +22 -0
- package/ts/core/routing/matchers/ip.ts +207 -0
- package/ts/core/routing/matchers/path.ts +184 -0
- package/ts/core/{utils → routing}/route-manager.ts +7 -57
- package/ts/core/routing/route-utils.ts +88 -0
- package/ts/core/routing/specificity.ts +141 -0
- package/ts/core/routing/types.ts +49 -0
- package/ts/core/utils/index.ts +1 -2
- package/ts/core/utils/proxy-protocol.ts +246 -0
- package/ts/core/utils/security-utils.ts +3 -7
- package/ts/index.ts +4 -14
- package/ts/proxies/http-proxy/http-proxy.ts +13 -68
- package/ts/proxies/http-proxy/models/types.ts +0 -324
- package/ts/proxies/http-proxy/request-handler.ts +15 -186
- package/ts/proxies/http-proxy/websocket-handler.ts +15 -26
- package/ts/proxies/index.ts +3 -2
- package/ts/proxies/smart-proxy/connection-manager.ts +17 -7
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +6 -2
- package/ts/proxies/smart-proxy/index.ts +1 -1
- package/ts/proxies/smart-proxy/models/interfaces.ts +9 -2
- package/ts/proxies/smart-proxy/models/route-types.ts +3 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +173 -42
- package/ts/proxies/smart-proxy/smart-proxy.ts +15 -3
- package/ts/proxies/smart-proxy/utils/route-utils.ts +11 -49
- package/ts/routing/router/http-router.ts +266 -0
- package/ts/routing/router/index.ts +3 -8
- package/readme.problems.md +0 -170
- package/ts/core/utils/route-utils.ts +0 -312
- package/ts/proxies/smart-proxy/route-manager.ts +0 -554
- package/ts/routing/router/proxy-router.ts +0 -437
- package/ts/routing/router/route-router.ts +0 -482
|
@@ -4,11 +4,9 @@ import {
|
|
|
4
4
|
type IHttpProxyOptions,
|
|
5
5
|
type ILogger,
|
|
6
6
|
createLogger,
|
|
7
|
-
type IReverseProxyConfig,
|
|
8
|
-
RouteManager
|
|
9
7
|
} from './models/types.js';
|
|
8
|
+
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
10
9
|
import { ConnectionPool } from './connection-pool.js';
|
|
11
|
-
import { ProxyRouter } from '../../routing/router/index.js';
|
|
12
10
|
import { ContextCreator } from './context-creator.js';
|
|
13
11
|
import { HttpRequestHandler } from './http-request-handler.js';
|
|
14
12
|
import { Http2RequestHandler } from './http2-request-handler.js';
|
|
@@ -48,10 +46,9 @@ export class RequestHandler {
|
|
|
48
46
|
constructor(
|
|
49
47
|
private options: IHttpProxyOptions,
|
|
50
48
|
private connectionPool: ConnectionPool,
|
|
51
|
-
private legacyRouter: ProxyRouter, // Legacy router for backward compatibility
|
|
52
49
|
private routeManager?: RouteManager,
|
|
53
50
|
private functionCache?: any, // FunctionCache - using any to avoid circular dependency
|
|
54
|
-
private router?: any //
|
|
51
|
+
private router?: any // HttpRouter - using any to avoid circular dependency
|
|
55
52
|
) {
|
|
56
53
|
this.logger = createLogger(options.logLevel || 'info');
|
|
57
54
|
this.securityManager = new SecurityManager(this.logger);
|
|
@@ -373,7 +370,8 @@ export class RequestHandler {
|
|
|
373
370
|
tlsVersion: req.socket.getTLSVersion?.() || undefined
|
|
374
371
|
});
|
|
375
372
|
|
|
376
|
-
|
|
373
|
+
const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
|
|
374
|
+
matchingRoute = matchResult?.route || null;
|
|
377
375
|
} catch (err) {
|
|
378
376
|
this.logger.error('Error finding matching route', err);
|
|
379
377
|
}
|
|
@@ -581,86 +579,11 @@ export class RequestHandler {
|
|
|
581
579
|
}
|
|
582
580
|
}
|
|
583
581
|
|
|
584
|
-
//
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
|
603
|
-
let proxyConfig: IReverseProxyConfig | undefined;
|
|
604
|
-
try {
|
|
605
|
-
proxyConfig = this.legacyRouter.routeReq(req);
|
|
606
|
-
} catch (err) {
|
|
607
|
-
this.logger.error('Error routing request with legacy router', err);
|
|
608
|
-
res.statusCode = 500;
|
|
609
|
-
res.end('Internal Server Error');
|
|
610
|
-
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
611
|
-
return;
|
|
612
|
-
}
|
|
613
|
-
if (!proxyConfig) {
|
|
614
|
-
this.logger.warn(`No proxy configuration for host: ${req.headers.host}`);
|
|
615
|
-
res.statusCode = 404;
|
|
616
|
-
res.end('Not Found: No proxy configuration for this host');
|
|
617
|
-
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
// Determine protocol to backend (per-domain override or global)
|
|
621
|
-
const backendProto = proxyConfig.backendProtocol || this.options.backendProtocol;
|
|
622
|
-
if (backendProto === 'http2') {
|
|
623
|
-
const destination = this.connectionPool.getNextTarget(
|
|
624
|
-
proxyConfig.destinationIps,
|
|
625
|
-
proxyConfig.destinationPorts[0]
|
|
626
|
-
);
|
|
627
|
-
const key = `${destination.host}:${destination.port}`;
|
|
628
|
-
let session = this.h2Sessions.get(key);
|
|
629
|
-
if (!session || session.closed || (session as any).destroyed) {
|
|
630
|
-
session = plugins.http2.connect(`http://${destination.host}:${destination.port}`);
|
|
631
|
-
this.h2Sessions.set(key, session);
|
|
632
|
-
session.on('error', () => this.h2Sessions.delete(key));
|
|
633
|
-
session.on('close', () => this.h2Sessions.delete(key));
|
|
634
|
-
}
|
|
635
|
-
// Build headers for HTTP/2 request
|
|
636
|
-
const hdrs: Record<string, any> = {
|
|
637
|
-
':method': req.method,
|
|
638
|
-
':path': req.url,
|
|
639
|
-
':authority': `${destination.host}:${destination.port}`
|
|
640
|
-
};
|
|
641
|
-
for (const [hk, hv] of Object.entries(req.headers)) {
|
|
642
|
-
if (typeof hv === 'string') hdrs[hk] = hv;
|
|
643
|
-
}
|
|
644
|
-
const h2Stream = session.request(hdrs);
|
|
645
|
-
req.pipe(h2Stream);
|
|
646
|
-
h2Stream.on('response', (hdrs2: any) => {
|
|
647
|
-
const status = (hdrs2[':status'] as number) || 502;
|
|
648
|
-
res.statusCode = status;
|
|
649
|
-
// Copy headers from HTTP/2 response to HTTP/1 response
|
|
650
|
-
for (const [hk, hv] of Object.entries(hdrs2)) {
|
|
651
|
-
if (!hk.startsWith(':') && hv != null) {
|
|
652
|
-
res.setHeader(hk, hv as string | string[]);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
h2Stream.pipe(res);
|
|
656
|
-
});
|
|
657
|
-
h2Stream.on('error', (err) => {
|
|
658
|
-
res.statusCode = 502;
|
|
659
|
-
res.end(`Bad Gateway: ${err.message}`);
|
|
660
|
-
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
661
|
-
});
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
582
|
+
// If no route was found, return 404
|
|
583
|
+
this.logger.warn(`No route configuration for host: ${req.headers.host}`);
|
|
584
|
+
res.statusCode = 404;
|
|
585
|
+
res.end('Not Found: No route configuration for this host');
|
|
586
|
+
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
664
587
|
}
|
|
665
588
|
|
|
666
589
|
/**
|
|
@@ -688,7 +611,8 @@ export class RequestHandler {
|
|
|
688
611
|
let matchingRoute: IRouteConfig | null = null;
|
|
689
612
|
if (this.routeManager) {
|
|
690
613
|
try {
|
|
691
|
-
|
|
614
|
+
const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
|
|
615
|
+
matchingRoute = matchResult?.route || null;
|
|
692
616
|
} catch (err) {
|
|
693
617
|
this.logger.error('Error finding matching route for HTTP/2 request', err);
|
|
694
618
|
}
|
|
@@ -812,104 +736,9 @@ export class RequestHandler {
|
|
|
812
736
|
const method = headers[':method'] || 'GET';
|
|
813
737
|
const path = headers[':path'] || '/';
|
|
814
738
|
|
|
815
|
-
//
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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);
|
|
841
|
-
if (!proxyConfig) {
|
|
842
|
-
stream.respond({ ':status': 404 });
|
|
843
|
-
stream.end('Not Found');
|
|
844
|
-
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
const destination = this.connectionPool.getNextTarget(proxyConfig.destinationIps, proxyConfig.destinationPorts[0]);
|
|
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
|
-
);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
try {
|
|
862
|
-
// Determine host for routing
|
|
863
|
-
const authority = headers[':authority'] as string || '';
|
|
864
|
-
const host = authority.split(':')[0];
|
|
865
|
-
// Fake request object for routing
|
|
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);
|
|
887
|
-
if (!proxyConfig) {
|
|
888
|
-
stream.respond({ ':status': 404 });
|
|
889
|
-
stream.end('Not Found');
|
|
890
|
-
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// Select backend target
|
|
895
|
-
const destination = this.connectionPool.getNextTarget(
|
|
896
|
-
proxyConfig.destinationIps,
|
|
897
|
-
proxyConfig.destinationPorts[0]
|
|
898
|
-
);
|
|
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
|
|
908
|
-
);
|
|
909
|
-
} catch (err: any) {
|
|
910
|
-
stream.respond({ ':status': 500 });
|
|
911
|
-
stream.end('Internal Server Error');
|
|
912
|
-
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
913
|
-
}
|
|
739
|
+
// No route was found
|
|
740
|
+
stream.respond({ ':status': 404 });
|
|
741
|
+
stream.end('Not Found: No route configuration for this request');
|
|
742
|
+
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
914
743
|
}
|
|
915
744
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import '../../core/models/socket-augmentation.js';
|
|
3
|
-
import { type IHttpProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger
|
|
3
|
+
import { type IHttpProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger } from './models/types.js';
|
|
4
4
|
import { ConnectionPool } from './connection-pool.js';
|
|
5
|
-
import {
|
|
5
|
+
import { HttpRouter } from '../../routing/router/index.js';
|
|
6
6
|
import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
|
|
7
7
|
import type { IRouteContext } from '../../core/models/route-context.js';
|
|
8
8
|
import { toBaseContext } from '../../core/models/route-context.js';
|
|
@@ -19,21 +19,20 @@ export class WebSocketHandler {
|
|
|
19
19
|
private wsServer: plugins.ws.WebSocketServer | null = null;
|
|
20
20
|
private logger: ILogger;
|
|
21
21
|
private contextCreator: ContextCreator = new ContextCreator();
|
|
22
|
-
private
|
|
22
|
+
private router: HttpRouter | null = null;
|
|
23
23
|
private securityManager: SecurityManager;
|
|
24
24
|
|
|
25
25
|
constructor(
|
|
26
26
|
private options: IHttpProxyOptions,
|
|
27
27
|
private connectionPool: ConnectionPool,
|
|
28
|
-
private
|
|
29
|
-
private routes: IRouteConfig[] = [] // Routes for modern router
|
|
28
|
+
private routes: IRouteConfig[] = []
|
|
30
29
|
) {
|
|
31
30
|
this.logger = createLogger(options.logLevel || 'info');
|
|
32
31
|
this.securityManager = new SecurityManager(this.logger, routes);
|
|
33
32
|
|
|
34
|
-
// Initialize
|
|
33
|
+
// Initialize router if we have routes
|
|
35
34
|
if (routes.length > 0) {
|
|
36
|
-
this.
|
|
35
|
+
this.router = new HttpRouter(routes, this.logger);
|
|
37
36
|
}
|
|
38
37
|
}
|
|
39
38
|
|
|
@@ -44,10 +43,10 @@ export class WebSocketHandler {
|
|
|
44
43
|
this.routes = routes;
|
|
45
44
|
|
|
46
45
|
// Initialize or update the route router
|
|
47
|
-
if (!this.
|
|
48
|
-
this.
|
|
46
|
+
if (!this.router) {
|
|
47
|
+
this.router = new HttpRouter(routes, this.logger);
|
|
49
48
|
} else {
|
|
50
|
-
this.
|
|
49
|
+
this.router.setRoutes(routes);
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
// Update the security manager
|
|
@@ -139,8 +138,8 @@ export class WebSocketHandler {
|
|
|
139
138
|
|
|
140
139
|
// Try modern router first if available
|
|
141
140
|
let route: IRouteConfig | undefined;
|
|
142
|
-
if (this.
|
|
143
|
-
route = this.
|
|
141
|
+
if (this.router) {
|
|
142
|
+
route = this.router.routeReq(req);
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
// Define destination variables
|
|
@@ -227,20 +226,10 @@ export class WebSocketHandler {
|
|
|
227
226
|
return;
|
|
228
227
|
}
|
|
229
228
|
} else {
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.logger.warn(`No proxy configuration for WebSocket host: ${req.headers.host}`);
|
|
235
|
-
wsIncoming.close(1008, 'No proxy configuration for this host');
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Get destination target using round-robin if multiple targets
|
|
240
|
-
destination = this.connectionPool.getNextTarget(
|
|
241
|
-
proxyConfig.destinationIps,
|
|
242
|
-
proxyConfig.destinationPorts[0]
|
|
243
|
-
);
|
|
229
|
+
// No route found
|
|
230
|
+
this.logger.warn(`No route configuration for WebSocket host: ${req.headers.host}`);
|
|
231
|
+
wsIncoming.close(1008, 'No route configuration for this host');
|
|
232
|
+
return;
|
|
244
233
|
}
|
|
245
234
|
|
|
246
235
|
// Build target URL with potential path rewriting
|
package/ts/proxies/index.ts
CHANGED
|
@@ -7,11 +7,12 @@ export { HttpProxy, CertificateManager, ConnectionPool, RequestHandler, WebSocke
|
|
|
7
7
|
export type { IMetricsTracker, MetricsTracker } from './http-proxy/index.js';
|
|
8
8
|
// Export http-proxy models except IAcmeOptions
|
|
9
9
|
export type { IHttpProxyOptions, ICertificateEntry, ILogger } from './http-proxy/models/types.js';
|
|
10
|
-
|
|
10
|
+
// RouteManager has been unified - use SharedRouteManager from core/routing
|
|
11
|
+
export { SharedRouteManager as HttpProxyRouteManager } from '../core/routing/route-manager.js';
|
|
11
12
|
|
|
12
13
|
// Export SmartProxy with selective imports to avoid conflicts
|
|
13
14
|
export { SmartProxy, ConnectionManager, SecurityManager, TimeoutManager, TlsManager, HttpProxyBridge, RouteConnectionHandler } from './smart-proxy/index.js';
|
|
14
|
-
export {
|
|
15
|
+
export { SharedRouteManager as SmartProxyRouteManager } from '../core/routing/route-manager.js';
|
|
15
16
|
export * from './smart-proxy/utils/index.js';
|
|
16
17
|
// Export smart-proxy models except IAcmeOptions
|
|
17
18
|
export type { ISmartProxyOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteContext } from './smart-proxy/models/index.js';
|
|
@@ -5,6 +5,7 @@ import { TimeoutManager } from './timeout-manager.js';
|
|
|
5
5
|
import { logger } from '../../core/utils/logger.js';
|
|
6
6
|
import { LifecycleComponent } from '../../core/utils/lifecycle-component.js';
|
|
7
7
|
import { cleanupSocket } from '../../core/utils/socket-utils.js';
|
|
8
|
+
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Manages connection lifecycle, tracking, and cleanup with performance optimizations
|
|
@@ -53,8 +54,9 @@ export class ConnectionManager extends LifecycleComponent {
|
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Create and track a new connection
|
|
57
|
+
* Accepts either a regular net.Socket or a WrappedSocket for transparent PROXY protocol support
|
|
56
58
|
*/
|
|
57
|
-
public createConnection(socket: plugins.net.Socket): IConnectionRecord | null {
|
|
59
|
+
public createConnection(socket: plugins.net.Socket | WrappedSocket): IConnectionRecord | null {
|
|
58
60
|
// Enforce connection limit
|
|
59
61
|
if (this.connectionRecords.size >= this.maxConnections) {
|
|
60
62
|
logger.log('warn', `Connection limit reached (${this.maxConnections}). Rejecting new connection.`, {
|
|
@@ -68,6 +70,7 @@ export class ConnectionManager extends LifecycleComponent {
|
|
|
68
70
|
|
|
69
71
|
const connectionId = this.generateConnectionId();
|
|
70
72
|
const remoteIP = socket.remoteAddress || '';
|
|
73
|
+
const remotePort = socket.remotePort || 0;
|
|
71
74
|
const localPort = socket.localPort || 0;
|
|
72
75
|
const now = Date.now();
|
|
73
76
|
|
|
@@ -83,6 +86,7 @@ export class ConnectionManager extends LifecycleComponent {
|
|
|
83
86
|
bytesReceived: 0,
|
|
84
87
|
bytesSent: 0,
|
|
85
88
|
remoteIP,
|
|
89
|
+
remotePort,
|
|
86
90
|
localPort,
|
|
87
91
|
isTLS: false,
|
|
88
92
|
tlsHandshakeComplete: false,
|
|
@@ -282,22 +286,26 @@ export class ConnectionManager extends LifecycleComponent {
|
|
|
282
286
|
const cleanupPromises: Promise<void>[] = [];
|
|
283
287
|
|
|
284
288
|
if (record.incoming) {
|
|
289
|
+
// Extract underlying socket if it's a WrappedSocket
|
|
290
|
+
const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
|
|
285
291
|
if (!record.incoming.writable || record.incoming.destroyed) {
|
|
286
292
|
// Socket is not active, clean up immediately
|
|
287
|
-
cleanupPromises.push(cleanupSocket(
|
|
293
|
+
cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { immediate: true }));
|
|
288
294
|
} else {
|
|
289
295
|
// Socket is still active, allow graceful cleanup
|
|
290
|
-
cleanupPromises.push(cleanupSocket(
|
|
296
|
+
cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { allowDrain: true, gracePeriod: 5000 }));
|
|
291
297
|
}
|
|
292
298
|
}
|
|
293
299
|
|
|
294
300
|
if (record.outgoing) {
|
|
301
|
+
// Extract underlying socket if it's a WrappedSocket
|
|
302
|
+
const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
|
|
295
303
|
if (!record.outgoing.writable || record.outgoing.destroyed) {
|
|
296
304
|
// Socket is not active, clean up immediately
|
|
297
|
-
cleanupPromises.push(cleanupSocket(
|
|
305
|
+
cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { immediate: true }));
|
|
298
306
|
} else {
|
|
299
307
|
// Socket is still active, allow graceful cleanup
|
|
300
|
-
cleanupPromises.push(cleanupSocket(
|
|
308
|
+
cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { allowDrain: true, gracePeriod: 5000 }));
|
|
301
309
|
}
|
|
302
310
|
}
|
|
303
311
|
|
|
@@ -570,11 +578,13 @@ export class ConnectionManager extends LifecycleComponent {
|
|
|
570
578
|
const shutdownPromises: Promise<void>[] = [];
|
|
571
579
|
|
|
572
580
|
if (record.incoming) {
|
|
573
|
-
|
|
581
|
+
const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
|
|
582
|
+
shutdownPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming-shutdown`, { immediate: true }));
|
|
574
583
|
}
|
|
575
584
|
|
|
576
585
|
if (record.outgoing) {
|
|
577
|
-
|
|
586
|
+
const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
|
|
587
|
+
shutdownPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing-shutdown`, { immediate: true }));
|
|
578
588
|
}
|
|
579
589
|
|
|
580
590
|
// Don't wait for shutdown cleanup in this batch processing
|
|
@@ -3,6 +3,7 @@ import { HttpProxy } from '../http-proxy/index.js';
|
|
|
3
3
|
import { setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
|
4
4
|
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
|
|
5
5
|
import type { IRouteConfig } from './models/route-types.js';
|
|
6
|
+
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
|
|
6
7
|
|
|
7
8
|
export class HttpProxyBridge {
|
|
8
9
|
private httpProxy: HttpProxy | null = null;
|
|
@@ -98,7 +99,7 @@ export class HttpProxyBridge {
|
|
|
98
99
|
*/
|
|
99
100
|
public async forwardToHttpProxy(
|
|
100
101
|
connectionId: string,
|
|
101
|
-
socket: plugins.net.Socket,
|
|
102
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
102
103
|
record: IConnectionRecord,
|
|
103
104
|
initialChunk: Buffer,
|
|
104
105
|
httpProxyPort: number,
|
|
@@ -125,7 +126,10 @@ export class HttpProxyBridge {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
// Use centralized bidirectional forwarding
|
|
128
|
-
|
|
129
|
+
// Extract underlying socket if it's a WrappedSocket
|
|
130
|
+
const underlyingSocket = socket instanceof WrappedSocket ? socket.socket : socket;
|
|
131
|
+
|
|
132
|
+
setupBidirectionalForwarding(underlyingSocket, proxySocket, {
|
|
129
133
|
onClientData: (chunk) => {
|
|
130
134
|
// Update stats if needed
|
|
131
135
|
if (record) {
|
|
@@ -17,7 +17,7 @@ export { TlsManager } from './tls-manager.js';
|
|
|
17
17
|
export { HttpProxyBridge } from './http-proxy-bridge.js';
|
|
18
18
|
|
|
19
19
|
// Export route-based components
|
|
20
|
-
export { RouteManager } from '
|
|
20
|
+
export { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
21
21
|
export { RouteConnectionHandler } from './route-connection-handler.js';
|
|
22
22
|
export { NFTablesManager } from './nftables-manager.js';
|
|
23
23
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as plugins from '../../../plugins.js';
|
|
2
|
+
import type { WrappedSocket } from '../../../core/models/wrapped-socket.js';
|
|
2
3
|
// Certificate types removed - define IAcmeOptions locally
|
|
3
4
|
export interface IAcmeOptions {
|
|
4
5
|
enabled?: boolean;
|
|
@@ -34,6 +35,11 @@ export interface ISmartProxyOptions {
|
|
|
34
35
|
// Port configuration
|
|
35
36
|
preserveSourceIP?: boolean; // Preserve client IP when forwarding
|
|
36
37
|
|
|
38
|
+
// PROXY protocol configuration
|
|
39
|
+
proxyIPs?: string[]; // List of trusted proxy IPs that can send PROXY protocol
|
|
40
|
+
acceptProxyProtocol?: boolean; // Global option to accept PROXY protocol (defaults based on proxyIPs)
|
|
41
|
+
sendProxyProtocol?: boolean; // Global option to send PROXY protocol to all targets
|
|
42
|
+
|
|
37
43
|
// Global/default settings
|
|
38
44
|
defaults?: {
|
|
39
45
|
target?: {
|
|
@@ -128,8 +134,8 @@ export interface ISmartProxyOptions {
|
|
|
128
134
|
*/
|
|
129
135
|
export interface IConnectionRecord {
|
|
130
136
|
id: string; // Unique connection identifier
|
|
131
|
-
incoming: plugins.net.Socket;
|
|
132
|
-
outgoing: plugins.net.Socket | null;
|
|
137
|
+
incoming: plugins.net.Socket | WrappedSocket;
|
|
138
|
+
outgoing: plugins.net.Socket | WrappedSocket | null;
|
|
133
139
|
incomingStartTime: number;
|
|
134
140
|
outgoingStartTime?: number;
|
|
135
141
|
outgoingClosedTime?: number;
|
|
@@ -145,6 +151,7 @@ export interface IConnectionRecord {
|
|
|
145
151
|
bytesReceived: number; // Total bytes received
|
|
146
152
|
bytesSent: number; // Total bytes sent
|
|
147
153
|
remoteIP: string; // Remote IP (cached for logging after socket close)
|
|
154
|
+
remotePort: number; // Remote port (cached for logging after socket close)
|
|
148
155
|
localPort: number; // Local port (cached for logging)
|
|
149
156
|
isTLS: boolean; // Whether this connection is a TLS connection
|
|
150
157
|
tlsHandshakeComplete: boolean; // Whether the TLS handshake is complete
|