@push.rocks/smartproxy 19.5.19 → 19.5.20
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 +0 -2
- package/dist_ts/core/utils/index.js +1 -3
- 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 +15 -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 +6 -2
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +48 -25
- 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 +189 -1
- package/readme.plan.md +621 -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 +0 -2
- 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 +15 -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 +8 -2
- package/ts/proxies/smart-proxy/route-connection-handler.ts +58 -30
- 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
|
@@ -13,7 +13,6 @@ export interface IAcmeOptions {
|
|
|
13
13
|
skipConfiguredCerts?: boolean;
|
|
14
14
|
}
|
|
15
15
|
import type { IRouteConfig } from '../../smart-proxy/models/route-types.js';
|
|
16
|
-
import type { IRouteContext } from '../../../core/models/route-context.js';
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* Configuration options for HttpProxy
|
|
@@ -34,7 +33,6 @@ export interface IHttpProxyOptions {
|
|
|
34
33
|
// Settings for SmartProxy integration
|
|
35
34
|
connectionPoolSize?: number; // Maximum connections to maintain in the pool to each backend
|
|
36
35
|
portProxyIntegration?: boolean; // Flag to indicate this proxy is used by SmartProxy
|
|
37
|
-
useExternalPort80Handler?: boolean; // @deprecated - use SmartCertManager instead
|
|
38
36
|
// Protocol to use when proxying to backends: HTTP/1.x or HTTP/2
|
|
39
37
|
backendProtocol?: 'http1' | 'http2';
|
|
40
38
|
|
|
@@ -58,329 +56,7 @@ export interface ICertificateEntry {
|
|
|
58
56
|
expires?: Date;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
/**
|
|
62
|
-
* @deprecated Use IRouteConfig instead. This interface will be removed in a future release.
|
|
63
|
-
*
|
|
64
|
-
* IMPORTANT: This is a legacy interface maintained only for backward compatibility.
|
|
65
|
-
* New code should use IRouteConfig for all configuration purposes.
|
|
66
|
-
*
|
|
67
|
-
* @see IRouteConfig for the modern, recommended configuration format
|
|
68
|
-
*/
|
|
69
|
-
export interface IReverseProxyConfig {
|
|
70
|
-
/** Target hostnames/IPs to proxy requests to */
|
|
71
|
-
destinationIps: string[];
|
|
72
|
-
|
|
73
|
-
/** Target ports to proxy requests to */
|
|
74
|
-
destinationPorts: number[];
|
|
75
|
-
|
|
76
|
-
/** Hostname to match for routing */
|
|
77
|
-
hostName: string;
|
|
78
|
-
|
|
79
|
-
/** SSL private key for this host (PEM format) */
|
|
80
|
-
privateKey: string;
|
|
81
|
-
|
|
82
|
-
/** SSL public key/certificate for this host (PEM format) */
|
|
83
|
-
publicKey: string;
|
|
84
|
-
|
|
85
|
-
/** Basic authentication configuration */
|
|
86
|
-
authentication?: {
|
|
87
|
-
type: 'Basic';
|
|
88
|
-
user: string;
|
|
89
|
-
pass: string;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
/** Whether to rewrite the Host header to match the target */
|
|
93
|
-
rewriteHostHeader?: boolean;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Protocol to use when proxying to this backend: 'http1' or 'http2'.
|
|
97
|
-
* Overrides the global backendProtocol option if set.
|
|
98
|
-
*/
|
|
99
|
-
backendProtocol?: 'http1' | 'http2';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Convert a legacy IReverseProxyConfig to the modern IRouteConfig format
|
|
104
|
-
*
|
|
105
|
-
* @deprecated This function is maintained for backward compatibility.
|
|
106
|
-
* New code should create IRouteConfig objects directly.
|
|
107
|
-
*
|
|
108
|
-
* @param legacyConfig The legacy configuration to convert
|
|
109
|
-
* @param proxyPort The port the proxy listens on
|
|
110
|
-
* @returns A modern route configuration equivalent to the legacy config
|
|
111
|
-
*/
|
|
112
|
-
export function convertLegacyConfigToRouteConfig(
|
|
113
|
-
legacyConfig: IReverseProxyConfig,
|
|
114
|
-
proxyPort: number
|
|
115
|
-
): IRouteConfig {
|
|
116
|
-
// Create basic route configuration
|
|
117
|
-
const routeConfig: IRouteConfig = {
|
|
118
|
-
// Match properties
|
|
119
|
-
match: {
|
|
120
|
-
ports: proxyPort,
|
|
121
|
-
domains: legacyConfig.hostName
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
// Action properties
|
|
125
|
-
action: {
|
|
126
|
-
type: 'forward',
|
|
127
|
-
target: {
|
|
128
|
-
host: legacyConfig.destinationIps,
|
|
129
|
-
port: legacyConfig.destinationPorts[0]
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
// TLS mode is always 'terminate' for legacy configs
|
|
133
|
-
tls: {
|
|
134
|
-
mode: 'terminate',
|
|
135
|
-
certificate: {
|
|
136
|
-
key: legacyConfig.privateKey,
|
|
137
|
-
cert: legacyConfig.publicKey
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
|
|
141
|
-
// Advanced options
|
|
142
|
-
advanced: {
|
|
143
|
-
// Rewrite host header if specified
|
|
144
|
-
headers: legacyConfig.rewriteHostHeader ? { 'host': '{domain}' } : {}
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
// Metadata
|
|
149
|
-
name: `Legacy Config - ${legacyConfig.hostName}`,
|
|
150
|
-
priority: 0, // Default priority
|
|
151
|
-
enabled: true
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
// Add authentication if present
|
|
155
|
-
if (legacyConfig.authentication) {
|
|
156
|
-
routeConfig.security = {
|
|
157
|
-
authentication: {
|
|
158
|
-
type: 'basic',
|
|
159
|
-
credentials: [{
|
|
160
|
-
username: legacyConfig.authentication.user,
|
|
161
|
-
password: legacyConfig.authentication.pass
|
|
162
|
-
}]
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Add backend protocol if specified
|
|
168
|
-
if (legacyConfig.backendProtocol) {
|
|
169
|
-
if (!routeConfig.action.options) {
|
|
170
|
-
routeConfig.action.options = {};
|
|
171
|
-
}
|
|
172
|
-
routeConfig.action.options.backendProtocol = legacyConfig.backendProtocol;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return routeConfig;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Route manager for NetworkProxy
|
|
180
|
-
* Handles route matching and configuration
|
|
181
|
-
*/
|
|
182
|
-
export class RouteManager {
|
|
183
|
-
private routes: IRouteConfig[] = [];
|
|
184
|
-
private logger: ILogger;
|
|
185
|
-
|
|
186
|
-
constructor(logger: ILogger) {
|
|
187
|
-
this.logger = logger;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Update the routes configuration
|
|
192
|
-
*/
|
|
193
|
-
public updateRoutes(routes: IRouteConfig[]): void {
|
|
194
|
-
// Sort routes by priority (higher first)
|
|
195
|
-
this.routes = [...routes].sort((a, b) => {
|
|
196
|
-
const priorityA = a.priority ?? 0;
|
|
197
|
-
const priorityB = b.priority ?? 0;
|
|
198
|
-
return priorityB - priorityA;
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
this.logger.info(`Updated RouteManager with ${this.routes.length} routes`);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Get all routes
|
|
206
|
-
*/
|
|
207
|
-
public getRoutes(): IRouteConfig[] {
|
|
208
|
-
return [...this.routes];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Find the first matching route for a context
|
|
213
|
-
*/
|
|
214
|
-
public findMatchingRoute(context: IRouteContext): IRouteConfig | null {
|
|
215
|
-
for (const route of this.routes) {
|
|
216
|
-
if (this.matchesRoute(route, context)) {
|
|
217
|
-
return route;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
59
|
|
|
223
|
-
/**
|
|
224
|
-
* Check if a route matches the given context
|
|
225
|
-
*/
|
|
226
|
-
private matchesRoute(route: IRouteConfig, context: IRouteContext): boolean {
|
|
227
|
-
// Skip disabled routes
|
|
228
|
-
if (route.enabled === false) {
|
|
229
|
-
return false;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Check domain match if specified
|
|
233
|
-
if (route.match.domains && context.domain) {
|
|
234
|
-
const domains = Array.isArray(route.match.domains)
|
|
235
|
-
? route.match.domains
|
|
236
|
-
: [route.match.domains];
|
|
237
|
-
|
|
238
|
-
if (!domains.some(domainPattern => this.matchDomain(domainPattern, context.domain!))) {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Check path match if specified
|
|
244
|
-
if (route.match.path && context.path) {
|
|
245
|
-
if (!this.matchPath(route.match.path, context.path)) {
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Check client IP match if specified
|
|
251
|
-
if (route.match.clientIp && context.clientIp) {
|
|
252
|
-
if (!route.match.clientIp.some(ip => this.matchIp(ip, context.clientIp))) {
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Check TLS version match if specified
|
|
258
|
-
if (route.match.tlsVersion && context.tlsVersion) {
|
|
259
|
-
if (!route.match.tlsVersion.includes(context.tlsVersion)) {
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// All criteria matched
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Match a domain pattern against a domain
|
|
270
|
-
*/
|
|
271
|
-
private matchDomain(pattern: string, domain: string): boolean {
|
|
272
|
-
if (pattern === domain) {
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (pattern.includes('*')) {
|
|
277
|
-
const regexPattern = pattern
|
|
278
|
-
.replace(/\./g, '\\.')
|
|
279
|
-
.replace(/\*/g, '.*');
|
|
280
|
-
|
|
281
|
-
const regex = new RegExp(`^${regexPattern}$`, 'i');
|
|
282
|
-
return regex.test(domain);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Match a path pattern against a path
|
|
290
|
-
*/
|
|
291
|
-
private matchPath(pattern: string, path: string): boolean {
|
|
292
|
-
if (pattern === path) {
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (pattern.endsWith('*')) {
|
|
297
|
-
const prefix = pattern.slice(0, -1);
|
|
298
|
-
return path.startsWith(prefix);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Match an IP pattern against an IP
|
|
306
|
-
* Supports exact matches, wildcard patterns, and CIDR notation
|
|
307
|
-
*/
|
|
308
|
-
private matchIp(pattern: string, ip: string): boolean {
|
|
309
|
-
// Exact match
|
|
310
|
-
if (pattern === ip) {
|
|
311
|
-
return true;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Wildcard matching (e.g., 192.168.0.*)
|
|
315
|
-
if (pattern.includes('*')) {
|
|
316
|
-
const regexPattern = pattern
|
|
317
|
-
.replace(/\./g, '\\.')
|
|
318
|
-
.replace(/\*/g, '.*');
|
|
319
|
-
|
|
320
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
321
|
-
return regex.test(ip);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// CIDR matching (e.g., 192.168.0.0/24)
|
|
325
|
-
if (pattern.includes('/')) {
|
|
326
|
-
try {
|
|
327
|
-
const [subnet, bits] = pattern.split('/');
|
|
328
|
-
|
|
329
|
-
// Convert IP addresses to numeric format for comparison
|
|
330
|
-
const ipBinary = this.ipToBinary(ip);
|
|
331
|
-
const subnetBinary = this.ipToBinary(subnet);
|
|
332
|
-
|
|
333
|
-
if (!ipBinary || !subnetBinary) {
|
|
334
|
-
return false;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Get the subnet mask from CIDR notation
|
|
338
|
-
const mask = parseInt(bits, 10);
|
|
339
|
-
if (isNaN(mask) || mask < 0 || mask > 32) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Check if the first 'mask' bits match between IP and subnet
|
|
344
|
-
return ipBinary.slice(0, mask) === subnetBinary.slice(0, mask);
|
|
345
|
-
} catch (error) {
|
|
346
|
-
// If we encounter any error during CIDR matching, return false
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Convert an IP address to its binary representation
|
|
356
|
-
* @param ip The IP address to convert
|
|
357
|
-
* @returns Binary string representation or null if invalid
|
|
358
|
-
*/
|
|
359
|
-
private ipToBinary(ip: string): string | null {
|
|
360
|
-
// Handle IPv4 addresses only for now
|
|
361
|
-
const parts = ip.split('.');
|
|
362
|
-
|
|
363
|
-
// Validate IP format
|
|
364
|
-
if (parts.length !== 4) {
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Convert each octet to 8-bit binary and concatenate
|
|
369
|
-
try {
|
|
370
|
-
return parts
|
|
371
|
-
.map(part => {
|
|
372
|
-
const num = parseInt(part, 10);
|
|
373
|
-
if (isNaN(num) || num < 0 || num > 255) {
|
|
374
|
-
throw new Error('Invalid IP octet');
|
|
375
|
-
}
|
|
376
|
-
return num.toString(2).padStart(8, '0');
|
|
377
|
-
})
|
|
378
|
-
.join('');
|
|
379
|
-
} catch (error) {
|
|
380
|
-
return null;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
60
|
|
|
385
61
|
/**
|
|
386
62
|
* Interface for connection tracking in the pool
|
|
@@ -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';
|