@push.rocks/smartproxy 23.0.0 → 23.1.1
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/changelog.md +17 -0
- package/dist_rust/{rustproxy → rustproxy_linux_amd64} +0 -0
- package/dist_rust/rustproxy_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +9 -21
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +84 -212
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +2 -3
- package/npmextra.json +3 -0
- package/package.json +13 -11
- package/readme.md +41 -11
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/plugins.ts +2 -0
- package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +103 -233
- package/ts/proxies/smart-proxy/smart-proxy.ts +1 -2
- package/dist_ts/common/eventUtils.d.ts +0 -14
- package/dist_ts/common/eventUtils.js +0 -20
- package/dist_ts/common/types.d.ts +0 -82
- package/dist_ts/common/types.js +0 -15
- package/dist_ts/core/utils/event-system.d.ts +0 -200
- package/dist_ts/core/utils/event-system.js +0 -224
- package/dist_ts/core/utils/event-utils.d.ts +0 -15
- package/dist_ts/core/utils/event-utils.js +0 -11
- package/dist_ts/core/utils/route-manager.d.ts +0 -88
- package/dist_ts/core/utils/route-manager.js +0 -342
- package/dist_ts/core/utils/route-utils.d.ts +0 -28
- package/dist_ts/core/utils/route-utils.js +0 -67
- package/dist_ts/detection/detectors/http-detector-v2.d.ts +0 -33
- package/dist_ts/detection/detectors/http-detector-v2.js +0 -87
- package/dist_ts/detection/detectors/tls-detector-v2.d.ts +0 -33
- package/dist_ts/detection/detectors/tls-detector-v2.js +0 -80
- package/dist_ts/detection/protocol-detector-v2.d.ts +0 -46
- package/dist_ts/detection/protocol-detector-v2.js +0 -116
- package/dist_ts/forwarding/config/forwarding-types.d.ts +0 -42
- package/dist_ts/forwarding/config/forwarding-types.js +0 -18
- package/dist_ts/forwarding/config/index.d.ts +0 -9
- package/dist_ts/forwarding/config/index.js +0 -10
- package/dist_ts/forwarding/factory/forwarding-factory.d.ts +0 -25
- package/dist_ts/forwarding/factory/forwarding-factory.js +0 -172
- package/dist_ts/forwarding/factory/index.d.ts +0 -4
- package/dist_ts/forwarding/factory/index.js +0 -5
- package/dist_ts/forwarding/handlers/base-handler.d.ts +0 -62
- package/dist_ts/forwarding/handlers/base-handler.js +0 -121
- package/dist_ts/forwarding/handlers/http-handler.d.ts +0 -30
- package/dist_ts/forwarding/handlers/http-handler.js +0 -143
- package/dist_ts/forwarding/handlers/https-passthrough-handler.d.ts +0 -29
- package/dist_ts/forwarding/handlers/https-passthrough-handler.js +0 -156
- package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.d.ts +0 -36
- package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.js +0 -276
- package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.d.ts +0 -35
- package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.js +0 -261
- package/dist_ts/forwarding/handlers/index.d.ts +0 -8
- package/dist_ts/forwarding/handlers/index.js +0 -9
- package/dist_ts/forwarding/index.d.ts +0 -13
- package/dist_ts/forwarding/index.js +0 -16
- package/dist_ts/http/index.d.ts +0 -5
- package/dist_ts/http/index.js +0 -8
- package/dist_ts/http/models/http-types.d.ts +0 -6
- package/dist_ts/http/models/http-types.js +0 -7
- package/dist_ts/http/router/index.d.ts +0 -8
- package/dist_ts/http/router/index.js +0 -7
- package/dist_ts/http/router/proxy-router.d.ts +0 -115
- package/dist_ts/http/router/proxy-router.js +0 -325
- package/dist_ts/http/router/route-router.d.ts +0 -108
- package/dist_ts/http/router/route-router.js +0 -393
- package/dist_ts/protocols/tls/constants.d.ts +0 -122
- package/dist_ts/protocols/tls/constants.js +0 -135
- package/dist_ts/protocols/tls/parser.d.ts +0 -53
- package/dist_ts/protocols/tls/parser.js +0 -294
- package/dist_ts/protocols/tls/types.d.ts +0 -65
- package/dist_ts/protocols/tls/types.js +0 -5
- package/dist_ts/proxies/http-proxy/certificate-manager.d.ts +0 -95
- package/dist_ts/proxies/http-proxy/certificate-manager.js +0 -214
- package/dist_ts/proxies/http-proxy/connection-pool.d.ts +0 -47
- package/dist_ts/proxies/http-proxy/connection-pool.js +0 -195
- package/dist_ts/proxies/http-proxy/context-creator.d.ts +0 -34
- package/dist_ts/proxies/http-proxy/context-creator.js +0 -108
- package/dist_ts/proxies/http-proxy/default-certificates.d.ts +0 -54
- package/dist_ts/proxies/http-proxy/default-certificates.js +0 -127
- package/dist_ts/proxies/http-proxy/function-cache.d.ts +0 -95
- package/dist_ts/proxies/http-proxy/function-cache.js +0 -215
- package/dist_ts/proxies/http-proxy/handlers/index.d.ts +0 -4
- package/dist_ts/proxies/http-proxy/handlers/index.js +0 -6
- package/dist_ts/proxies/http-proxy/handlers/redirect-handler.d.ts +0 -18
- package/dist_ts/proxies/http-proxy/handlers/redirect-handler.js +0 -78
- package/dist_ts/proxies/http-proxy/handlers/static-handler.d.ts +0 -19
- package/dist_ts/proxies/http-proxy/handlers/static-handler.js +0 -211
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +0 -117
- package/dist_ts/proxies/http-proxy/http-proxy.js +0 -521
- package/dist_ts/proxies/http-proxy/http-request-handler.d.ts +0 -40
- package/dist_ts/proxies/http-proxy/http-request-handler.js +0 -257
- package/dist_ts/proxies/http-proxy/http2-request-handler.d.ts +0 -24
- package/dist_ts/proxies/http-proxy/http2-request-handler.js +0 -201
- package/dist_ts/proxies/http-proxy/index.d.ts +0 -14
- package/dist_ts/proxies/http-proxy/index.js +0 -16
- package/dist_ts/proxies/http-proxy/models/http-types.d.ts +0 -117
- package/dist_ts/proxies/http-proxy/models/http-types.js +0 -92
- package/dist_ts/proxies/http-proxy/models/index.d.ts +0 -5
- package/dist_ts/proxies/http-proxy/models/index.js +0 -6
- package/dist_ts/proxies/http-proxy/models/types.d.ts +0 -75
- package/dist_ts/proxies/http-proxy/models/types.js +0 -35
- package/dist_ts/proxies/http-proxy/request-handler.d.ts +0 -97
- package/dist_ts/proxies/http-proxy/request-handler.js +0 -737
- package/dist_ts/proxies/http-proxy/security-manager.d.ts +0 -98
- package/dist_ts/proxies/http-proxy/security-manager.js +0 -341
- package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +0 -50
- package/dist_ts/proxies/http-proxy/websocket-handler.js +0 -505
- package/dist_ts/proxies/smart-proxy/acme-state-manager.d.ts +0 -42
- package/dist_ts/proxies/smart-proxy/acme-state-manager.js +0 -101
- package/dist_ts/proxies/smart-proxy/cert-store.d.ts +0 -10
- package/dist_ts/proxies/smart-proxy/cert-store.js +0 -72
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +0 -164
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +0 -745
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +0 -128
- package/dist_ts/proxies/smart-proxy/connection-manager.js +0 -689
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +0 -43
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +0 -180
- package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +0 -98
- package/dist_ts/proxies/smart-proxy/metrics-collector.js +0 -355
- package/dist_ts/proxies/smart-proxy/nftables-manager.d.ts +0 -82
- package/dist_ts/proxies/smart-proxy/nftables-manager.js +0 -237
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +0 -117
- package/dist_ts/proxies/smart-proxy/port-manager.js +0 -318
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +0 -60
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +0 -1407
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +0 -112
- package/dist_ts/proxies/smart-proxy/route-manager.js +0 -453
- package/dist_ts/proxies/smart-proxy/route-orchestrator.d.ts +0 -56
- package/dist_ts/proxies/smart-proxy/route-orchestrator.js +0 -204
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +0 -23
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +0 -104
- package/dist_ts/proxies/smart-proxy/security-manager.d.ts +0 -74
- package/dist_ts/proxies/smart-proxy/security-manager.js +0 -227
- package/dist_ts/proxies/smart-proxy/throughput-tracker.d.ts +0 -36
- package/dist_ts/proxies/smart-proxy/throughput-tracker.js +0 -115
- package/dist_ts/proxies/smart-proxy/timeout-manager.d.ts +0 -48
- package/dist_ts/proxies/smart-proxy/timeout-manager.js +0 -158
- package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +0 -50
- package/dist_ts/proxies/smart-proxy/tls-manager.js +0 -110
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -161
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +0 -282
- package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +0 -73
- package/dist_ts/proxies/smart-proxy/utils/route-validators.js +0 -259
- package/dist_ts/routing/router/proxy-router.d.ts +0 -115
- package/dist_ts/routing/router/proxy-router.js +0 -325
- package/dist_ts/routing/router/route-router.d.ts +0 -108
- package/dist_ts/routing/router/route-router.js +0 -393
- package/dist_ts/tls/alerts/index.d.ts +0 -4
- package/dist_ts/tls/alerts/index.js +0 -5
- package/dist_ts/tls/alerts/tls-alert.d.ts +0 -150
- package/dist_ts/tls/alerts/tls-alert.js +0 -226
- package/dist_ts/tls/sni/client-hello-parser.d.ts +0 -100
- package/dist_ts/tls/sni/client-hello-parser.js +0 -464
- package/dist_ts/tls/sni/sni-extraction.d.ts +0 -58
- package/dist_ts/tls/sni/sni-extraction.js +0 -275
- package/dist_ts/tls/utils/index.d.ts +0 -4
- package/dist_ts/tls/utils/index.js +0 -5
- package/dist_ts/tls/utils/tls-utils.d.ts +0 -49
- package/dist_ts/tls/utils/tls-utils.js +0 -75
- package/ts/proxies/smart-proxy/rust-binary-locator.ts +0 -112
|
@@ -1,737 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../../plugins.js';
|
|
2
|
-
import '../../core/models/socket-augmentation.js';
|
|
3
|
-
import { createLogger, } from './models/types.js';
|
|
4
|
-
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
5
|
-
import { ConnectionPool } from './connection-pool.js';
|
|
6
|
-
import { ContextCreator } from './context-creator.js';
|
|
7
|
-
import { HttpRequestHandler } from './http-request-handler.js';
|
|
8
|
-
import { Http2RequestHandler } from './http2-request-handler.js';
|
|
9
|
-
import { toBaseContext } from '../../core/models/route-context.js';
|
|
10
|
-
import { TemplateUtils } from '../../core/utils/template-utils.js';
|
|
11
|
-
import { SecurityManager } from './security-manager.js';
|
|
12
|
-
/**
|
|
13
|
-
* Handles HTTP request processing and proxying
|
|
14
|
-
*/
|
|
15
|
-
export class RequestHandler {
|
|
16
|
-
constructor(options, connectionPool, routeManager, functionCache, // FunctionCache - using any to avoid circular dependency
|
|
17
|
-
router // HttpRouter - using any to avoid circular dependency
|
|
18
|
-
) {
|
|
19
|
-
this.options = options;
|
|
20
|
-
this.connectionPool = connectionPool;
|
|
21
|
-
this.routeManager = routeManager;
|
|
22
|
-
this.functionCache = functionCache;
|
|
23
|
-
this.router = router;
|
|
24
|
-
this.defaultHeaders = {};
|
|
25
|
-
this.metricsTracker = null;
|
|
26
|
-
// HTTP/2 client sessions for backend proxying
|
|
27
|
-
this.h2Sessions = new Map();
|
|
28
|
-
// Context creator for route contexts
|
|
29
|
-
this.contextCreator = new ContextCreator();
|
|
30
|
-
// Rate limit cleanup interval
|
|
31
|
-
this.rateLimitCleanupInterval = null;
|
|
32
|
-
this.logger = createLogger(options.logLevel || 'info');
|
|
33
|
-
this.securityManager = new SecurityManager(this.logger);
|
|
34
|
-
// Schedule rate limit cleanup every minute
|
|
35
|
-
this.rateLimitCleanupInterval = setInterval(() => {
|
|
36
|
-
this.securityManager.cleanupExpiredRateLimits();
|
|
37
|
-
}, 60000);
|
|
38
|
-
// Make sure the interval doesn't keep the process alive
|
|
39
|
-
if (this.rateLimitCleanupInterval.unref) {
|
|
40
|
-
this.rateLimitCleanupInterval.unref();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Set the route manager instance
|
|
45
|
-
*/
|
|
46
|
-
setRouteManager(routeManager) {
|
|
47
|
-
this.routeManager = routeManager;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Set the metrics tracker instance
|
|
51
|
-
*/
|
|
52
|
-
setMetricsTracker(tracker) {
|
|
53
|
-
this.metricsTracker = tracker;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Set default headers to be included in all responses
|
|
57
|
-
*/
|
|
58
|
-
setDefaultHeaders(headers) {
|
|
59
|
-
this.defaultHeaders = {
|
|
60
|
-
...this.defaultHeaders,
|
|
61
|
-
...headers
|
|
62
|
-
};
|
|
63
|
-
this.logger.info('Updated default response headers');
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Get all default headers
|
|
67
|
-
*/
|
|
68
|
-
getDefaultHeaders() {
|
|
69
|
-
return { ...this.defaultHeaders };
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Select the appropriate target from the targets array based on sub-matching criteria
|
|
73
|
-
*/
|
|
74
|
-
selectTarget(targets, context) {
|
|
75
|
-
// Sort targets by priority (higher first)
|
|
76
|
-
const sortedTargets = [...targets].sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
77
|
-
// Find the first matching target
|
|
78
|
-
for (const target of sortedTargets) {
|
|
79
|
-
if (!target.match) {
|
|
80
|
-
// No match criteria means this is a default/fallback target
|
|
81
|
-
return target;
|
|
82
|
-
}
|
|
83
|
-
// Check port match
|
|
84
|
-
if (target.match.ports && !target.match.ports.includes(context.port)) {
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
// Check path match (supports wildcards)
|
|
88
|
-
if (target.match.path && context.path) {
|
|
89
|
-
const pathPattern = target.match.path.replace(/\*/g, '.*');
|
|
90
|
-
const pathRegex = new RegExp(`^${pathPattern}$`);
|
|
91
|
-
if (!pathRegex.test(context.path)) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// Check method match
|
|
96
|
-
if (target.match.method && context.method && !target.match.method.includes(context.method)) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
// Check headers match
|
|
100
|
-
if (target.match.headers && context.headers) {
|
|
101
|
-
let headersMatch = true;
|
|
102
|
-
for (const [key, pattern] of Object.entries(target.match.headers)) {
|
|
103
|
-
const headerValue = context.headers[key.toLowerCase()];
|
|
104
|
-
if (!headerValue) {
|
|
105
|
-
headersMatch = false;
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
if (pattern instanceof RegExp) {
|
|
109
|
-
if (!pattern.test(headerValue)) {
|
|
110
|
-
headersMatch = false;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
else if (headerValue !== pattern) {
|
|
115
|
-
headersMatch = false;
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (!headersMatch) {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// All criteria matched
|
|
124
|
-
return target;
|
|
125
|
-
}
|
|
126
|
-
// No matching target found, return the first target without match criteria (default)
|
|
127
|
-
return sortedTargets.find(t => !t.match) || null;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Apply CORS headers to response if configured
|
|
131
|
-
* Implements Phase 5.5: Context-aware CORS handling
|
|
132
|
-
*
|
|
133
|
-
* @param res The server response to apply headers to
|
|
134
|
-
* @param req The incoming request
|
|
135
|
-
* @param route Optional route config with CORS settings
|
|
136
|
-
*/
|
|
137
|
-
applyCorsHeaders(res, req, route) {
|
|
138
|
-
// Use route-specific CORS config if available, otherwise use global config
|
|
139
|
-
let corsConfig = null;
|
|
140
|
-
// Route CORS config takes precedence if enabled
|
|
141
|
-
if (route?.headers?.cors?.enabled) {
|
|
142
|
-
corsConfig = route.headers.cors;
|
|
143
|
-
this.logger.debug(`Using route-specific CORS config for ${route.name || 'unnamed route'}`);
|
|
144
|
-
}
|
|
145
|
-
// Fall back to global CORS config if available
|
|
146
|
-
else if (this.options.cors) {
|
|
147
|
-
corsConfig = this.options.cors;
|
|
148
|
-
this.logger.debug('Using global CORS config');
|
|
149
|
-
}
|
|
150
|
-
// If no CORS config available, skip
|
|
151
|
-
if (!corsConfig) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
// Get origin from request
|
|
155
|
-
const origin = req.headers.origin;
|
|
156
|
-
// Apply Allow-Origin (with dynamic validation if needed)
|
|
157
|
-
if (corsConfig.allowOrigin) {
|
|
158
|
-
// Handle multiple origins in array format
|
|
159
|
-
if (Array.isArray(corsConfig.allowOrigin)) {
|
|
160
|
-
if (origin && corsConfig.allowOrigin.includes(origin)) {
|
|
161
|
-
// Match found, set specific origin
|
|
162
|
-
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
163
|
-
res.setHeader('Vary', 'Origin'); // Important for caching
|
|
164
|
-
}
|
|
165
|
-
else if (corsConfig.allowOrigin.includes('*')) {
|
|
166
|
-
// Wildcard match
|
|
167
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// Handle single origin or wildcard
|
|
171
|
-
else if (corsConfig.allowOrigin === '*') {
|
|
172
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
173
|
-
}
|
|
174
|
-
// Match single origin against request
|
|
175
|
-
else if (origin && corsConfig.allowOrigin === origin) {
|
|
176
|
-
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
177
|
-
res.setHeader('Vary', 'Origin');
|
|
178
|
-
}
|
|
179
|
-
// Use template variables if present
|
|
180
|
-
else if (origin && corsConfig.allowOrigin.includes('{')) {
|
|
181
|
-
const resolvedOrigin = TemplateUtils.resolveTemplateVariables(corsConfig.allowOrigin, { domain: req.headers.host });
|
|
182
|
-
if (resolvedOrigin === origin || resolvedOrigin === '*') {
|
|
183
|
-
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
184
|
-
res.setHeader('Vary', 'Origin');
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
// Apply other CORS headers
|
|
189
|
-
if (corsConfig.allowMethods) {
|
|
190
|
-
res.setHeader('Access-Control-Allow-Methods', corsConfig.allowMethods);
|
|
191
|
-
}
|
|
192
|
-
if (corsConfig.allowHeaders) {
|
|
193
|
-
res.setHeader('Access-Control-Allow-Headers', corsConfig.allowHeaders);
|
|
194
|
-
}
|
|
195
|
-
if (corsConfig.allowCredentials) {
|
|
196
|
-
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
197
|
-
}
|
|
198
|
-
if (corsConfig.exposeHeaders) {
|
|
199
|
-
res.setHeader('Access-Control-Expose-Headers', corsConfig.exposeHeaders);
|
|
200
|
-
}
|
|
201
|
-
if (corsConfig.maxAge) {
|
|
202
|
-
res.setHeader('Access-Control-Max-Age', corsConfig.maxAge.toString());
|
|
203
|
-
}
|
|
204
|
-
// Handle CORS preflight requests if enabled (default: true)
|
|
205
|
-
if (req.method === 'OPTIONS' && corsConfig.preflight !== false) {
|
|
206
|
-
res.statusCode = 204; // No content
|
|
207
|
-
res.end();
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// First implementation of applyRouteHeaderModifications moved to the second implementation below
|
|
212
|
-
/**
|
|
213
|
-
* Apply default headers to response
|
|
214
|
-
*/
|
|
215
|
-
applyDefaultHeaders(res) {
|
|
216
|
-
// Apply default headers
|
|
217
|
-
for (const [key, value] of Object.entries(this.defaultHeaders)) {
|
|
218
|
-
if (!res.hasHeader(key)) {
|
|
219
|
-
res.setHeader(key, value);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
// Add server identifier if not already set
|
|
223
|
-
if (!res.hasHeader('Server')) {
|
|
224
|
-
res.setHeader('Server', 'NetworkProxy');
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Apply URL rewriting based on route configuration
|
|
229
|
-
* Implements Phase 5.2: URL rewriting using route context
|
|
230
|
-
*
|
|
231
|
-
* @param req The request with the URL to rewrite
|
|
232
|
-
* @param route The route configuration containing rewrite rules
|
|
233
|
-
* @param routeContext Context for template variable resolution
|
|
234
|
-
* @returns True if URL was rewritten, false otherwise
|
|
235
|
-
*/
|
|
236
|
-
applyUrlRewriting(req, route, routeContext) {
|
|
237
|
-
// Check if route has URL rewriting configuration
|
|
238
|
-
if (!route.action.advanced?.urlRewrite) {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
const rewriteConfig = route.action.advanced.urlRewrite;
|
|
242
|
-
// Store original URL for logging
|
|
243
|
-
const originalUrl = req.url;
|
|
244
|
-
if (rewriteConfig.pattern && rewriteConfig.target) {
|
|
245
|
-
try {
|
|
246
|
-
// Create a RegExp from the pattern
|
|
247
|
-
const regex = new RegExp(rewriteConfig.pattern, rewriteConfig.flags || '');
|
|
248
|
-
// Apply rewriting with template variable resolution
|
|
249
|
-
let target = rewriteConfig.target;
|
|
250
|
-
// Replace template variables in target with values from context
|
|
251
|
-
target = TemplateUtils.resolveTemplateVariables(target, routeContext);
|
|
252
|
-
// If onlyRewritePath is set, split URL into path and query parts
|
|
253
|
-
if (rewriteConfig.onlyRewritePath && req.url) {
|
|
254
|
-
const [path, query] = req.url.split('?');
|
|
255
|
-
const rewrittenPath = path.replace(regex, target);
|
|
256
|
-
req.url = query ? `${rewrittenPath}?${query}` : rewrittenPath;
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
// Perform the replacement on the entire URL
|
|
260
|
-
req.url = req.url?.replace(regex, target);
|
|
261
|
-
}
|
|
262
|
-
this.logger.debug(`URL rewritten: ${originalUrl} -> ${req.url}`);
|
|
263
|
-
return true;
|
|
264
|
-
}
|
|
265
|
-
catch (err) {
|
|
266
|
-
this.logger.error(`Error in URL rewriting: ${err}`);
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return false;
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Apply header modifications from route configuration
|
|
274
|
-
* Implements Phase 5.1: Route-based header manipulation
|
|
275
|
-
*/
|
|
276
|
-
applyRouteHeaderModifications(route, req, res) {
|
|
277
|
-
// Check if route has header modifications
|
|
278
|
-
if (!route.headers) {
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
// Apply request header modifications (these will be sent to the backend)
|
|
282
|
-
if (route.headers.request && req.headers) {
|
|
283
|
-
for (const [key, value] of Object.entries(route.headers.request)) {
|
|
284
|
-
// Skip if header already exists and we're not overriding
|
|
285
|
-
if (req.headers[key.toLowerCase()] && !value.startsWith('!')) {
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
// Handle special delete directive (!delete)
|
|
289
|
-
if (value === '!delete') {
|
|
290
|
-
delete req.headers[key.toLowerCase()];
|
|
291
|
-
this.logger.debug(`Deleted request header: ${key}`);
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
// Handle forced override (!value)
|
|
295
|
-
let finalValue;
|
|
296
|
-
if (value.startsWith('!') && value !== '!delete') {
|
|
297
|
-
// Keep the ! but resolve any templates in the rest
|
|
298
|
-
const templateValue = value.substring(1);
|
|
299
|
-
finalValue = '!' + TemplateUtils.resolveTemplateVariables(templateValue, {});
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
// Resolve templates in the entire value
|
|
303
|
-
finalValue = TemplateUtils.resolveTemplateVariables(value, {});
|
|
304
|
-
}
|
|
305
|
-
// Set the header
|
|
306
|
-
req.headers[key.toLowerCase()] = finalValue;
|
|
307
|
-
this.logger.debug(`Modified request header: ${key}=${finalValue}`);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
// Apply response header modifications (these will be stored for later use)
|
|
311
|
-
if (route.headers.response) {
|
|
312
|
-
for (const [key, value] of Object.entries(route.headers.response)) {
|
|
313
|
-
// Skip if header already exists and we're not overriding
|
|
314
|
-
if (res.hasHeader(key) && !value.startsWith('!')) {
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
|
-
// Handle special delete directive (!delete)
|
|
318
|
-
if (value === '!delete') {
|
|
319
|
-
res.removeHeader(key);
|
|
320
|
-
this.logger.debug(`Deleted response header: ${key}`);
|
|
321
|
-
continue;
|
|
322
|
-
}
|
|
323
|
-
// Handle forced override (!value)
|
|
324
|
-
let finalValue;
|
|
325
|
-
if (value.startsWith('!') && value !== '!delete') {
|
|
326
|
-
// Keep the ! but resolve any templates in the rest
|
|
327
|
-
const templateValue = value.substring(1);
|
|
328
|
-
finalValue = '!' + TemplateUtils.resolveTemplateVariables(templateValue, {});
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
// Resolve templates in the entire value
|
|
332
|
-
finalValue = TemplateUtils.resolveTemplateVariables(value, {});
|
|
333
|
-
}
|
|
334
|
-
// Set the header
|
|
335
|
-
res.setHeader(key, finalValue);
|
|
336
|
-
this.logger.debug(`Modified response header: ${key}=${finalValue}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Handle an HTTP request
|
|
342
|
-
*/
|
|
343
|
-
async handleRequest(req, res) {
|
|
344
|
-
// Record start time for logging
|
|
345
|
-
const startTime = Date.now();
|
|
346
|
-
// Get route before applying CORS (we might need its settings)
|
|
347
|
-
// Try to find a matching route using RouteManager
|
|
348
|
-
let matchingRoute = null;
|
|
349
|
-
if (this.routeManager) {
|
|
350
|
-
try {
|
|
351
|
-
// Create a connection ID for this request
|
|
352
|
-
const connectionId = `http-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
353
|
-
// Create route context for function-based targets
|
|
354
|
-
const routeContext = this.contextCreator.createHttpRouteContext(req, {
|
|
355
|
-
connectionId,
|
|
356
|
-
clientIp: req.socket.remoteAddress?.replace('::ffff:', '') || '0.0.0.0',
|
|
357
|
-
serverIp: req.socket.localAddress?.replace('::ffff:', '') || '0.0.0.0',
|
|
358
|
-
tlsVersion: req.socket.getTLSVersion?.() || undefined
|
|
359
|
-
});
|
|
360
|
-
const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
|
|
361
|
-
matchingRoute = matchResult?.route || null;
|
|
362
|
-
}
|
|
363
|
-
catch (err) {
|
|
364
|
-
this.logger.error('Error finding matching route', err);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
// Apply CORS headers with route-specific settings if available
|
|
368
|
-
this.applyCorsHeaders(res, req, matchingRoute);
|
|
369
|
-
// If this is an OPTIONS request, the response has already been ended in applyCorsHeaders
|
|
370
|
-
// so we should return early to avoid trying to set more headers
|
|
371
|
-
if (req.method === 'OPTIONS') {
|
|
372
|
-
// Increment metrics for OPTIONS requests too
|
|
373
|
-
if (this.metricsTracker) {
|
|
374
|
-
this.metricsTracker.incrementRequestsServed();
|
|
375
|
-
}
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
// Apply default headers
|
|
379
|
-
this.applyDefaultHeaders(res);
|
|
380
|
-
// We already have the connection ID and routeContext from CORS handling
|
|
381
|
-
const connectionId = `http-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
382
|
-
// Create route context for function-based targets (if we don't already have one)
|
|
383
|
-
const routeContext = this.contextCreator.createHttpRouteContext(req, {
|
|
384
|
-
connectionId,
|
|
385
|
-
clientIp: req.socket.remoteAddress?.replace('::ffff:', '') || '0.0.0.0',
|
|
386
|
-
serverIp: req.socket.localAddress?.replace('::ffff:', '') || '0.0.0.0',
|
|
387
|
-
tlsVersion: req.socket.getTLSVersion?.() || undefined
|
|
388
|
-
});
|
|
389
|
-
// Check security restrictions if we have a matching route
|
|
390
|
-
if (matchingRoute) {
|
|
391
|
-
// Check IP filtering and rate limiting
|
|
392
|
-
if (!this.securityManager.isAllowed(matchingRoute, routeContext)) {
|
|
393
|
-
this.logger.warn(`Access denied for ${routeContext.clientIp} to ${matchingRoute.name || 'unnamed'}`);
|
|
394
|
-
res.statusCode = 403;
|
|
395
|
-
res.end('Forbidden: Access denied by security policy');
|
|
396
|
-
if (this.metricsTracker)
|
|
397
|
-
this.metricsTracker.incrementFailedRequests();
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
// Check basic auth
|
|
401
|
-
if (matchingRoute.security?.basicAuth?.enabled) {
|
|
402
|
-
const authHeader = req.headers.authorization;
|
|
403
|
-
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
404
|
-
// No auth header provided - send 401 with WWW-Authenticate header
|
|
405
|
-
res.statusCode = 401;
|
|
406
|
-
const realm = matchingRoute.security.basicAuth.realm || 'Protected Area';
|
|
407
|
-
res.setHeader('WWW-Authenticate', `Basic realm="${realm}", charset="UTF-8"`);
|
|
408
|
-
res.end('Authentication Required');
|
|
409
|
-
if (this.metricsTracker)
|
|
410
|
-
this.metricsTracker.incrementFailedRequests();
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
// Verify credentials
|
|
414
|
-
try {
|
|
415
|
-
const credentials = Buffer.from(authHeader.substring(6), 'base64').toString('utf-8');
|
|
416
|
-
const [username, password] = credentials.split(':');
|
|
417
|
-
if (!this.securityManager.checkBasicAuth(matchingRoute, username, password)) {
|
|
418
|
-
res.statusCode = 401;
|
|
419
|
-
const realm = matchingRoute.security.basicAuth.realm || 'Protected Area';
|
|
420
|
-
res.setHeader('WWW-Authenticate', `Basic realm="${realm}", charset="UTF-8"`);
|
|
421
|
-
res.end('Invalid Credentials');
|
|
422
|
-
if (this.metricsTracker)
|
|
423
|
-
this.metricsTracker.incrementFailedRequests();
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
catch (err) {
|
|
428
|
-
this.logger.error(`Error verifying basic auth: ${err}`);
|
|
429
|
-
res.statusCode = 401;
|
|
430
|
-
res.end('Authentication Error');
|
|
431
|
-
if (this.metricsTracker)
|
|
432
|
-
this.metricsTracker.incrementFailedRequests();
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
// Check JWT auth
|
|
437
|
-
if (matchingRoute.security?.jwtAuth?.enabled) {
|
|
438
|
-
const authHeader = req.headers.authorization;
|
|
439
|
-
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
440
|
-
// No auth header provided - send 401
|
|
441
|
-
res.statusCode = 401;
|
|
442
|
-
res.end('Authentication Required: JWT token missing');
|
|
443
|
-
if (this.metricsTracker)
|
|
444
|
-
this.metricsTracker.incrementFailedRequests();
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
// Verify token
|
|
448
|
-
const token = authHeader.substring(7);
|
|
449
|
-
if (!this.securityManager.verifyJwtToken(matchingRoute, token)) {
|
|
450
|
-
res.statusCode = 401;
|
|
451
|
-
res.end('Invalid or Expired JWT');
|
|
452
|
-
if (this.metricsTracker)
|
|
453
|
-
this.metricsTracker.incrementFailedRequests();
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
// If we found a matching route with forward action, select appropriate target
|
|
459
|
-
if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.targets && matchingRoute.action.targets.length > 0) {
|
|
460
|
-
this.logger.debug(`Found matching route: ${matchingRoute.name || 'unnamed'}`);
|
|
461
|
-
// Select the appropriate target from the targets array
|
|
462
|
-
const selectedTarget = this.selectTarget(matchingRoute.action.targets, {
|
|
463
|
-
port: routeContext.port,
|
|
464
|
-
path: routeContext.path,
|
|
465
|
-
headers: routeContext.headers,
|
|
466
|
-
method: routeContext.method
|
|
467
|
-
});
|
|
468
|
-
if (!selectedTarget) {
|
|
469
|
-
this.logger.error(`No matching target found for route ${matchingRoute.name}`);
|
|
470
|
-
req.socket.end();
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
// Extract target information, resolving functions if needed
|
|
474
|
-
let targetHost;
|
|
475
|
-
let targetPort;
|
|
476
|
-
try {
|
|
477
|
-
// Check function cache for host and resolve or use cached value
|
|
478
|
-
if (typeof selectedTarget.host === 'function') {
|
|
479
|
-
// Generate a function ID for caching (use route name or ID if available)
|
|
480
|
-
const functionId = `host-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
|
|
481
|
-
// Check if we have a cached result
|
|
482
|
-
if (this.functionCache) {
|
|
483
|
-
const cachedHost = this.functionCache.getCachedHost(routeContext, functionId);
|
|
484
|
-
if (cachedHost !== undefined) {
|
|
485
|
-
targetHost = cachedHost;
|
|
486
|
-
this.logger.debug(`Using cached host value for ${functionId}`);
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
489
|
-
// Resolve the function and cache the result
|
|
490
|
-
const resolvedHost = selectedTarget.host(toBaseContext(routeContext));
|
|
491
|
-
targetHost = resolvedHost;
|
|
492
|
-
// Cache the result
|
|
493
|
-
this.functionCache.cacheHost(routeContext, functionId, resolvedHost);
|
|
494
|
-
this.logger.debug(`Resolved and cached function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
// No cache available, just resolve
|
|
499
|
-
const resolvedHost = selectedTarget.host(routeContext);
|
|
500
|
-
targetHost = resolvedHost;
|
|
501
|
-
this.logger.debug(`Resolved function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
else {
|
|
505
|
-
targetHost = selectedTarget.host;
|
|
506
|
-
}
|
|
507
|
-
// Check function cache for port and resolve or use cached value
|
|
508
|
-
if (typeof selectedTarget.port === 'function') {
|
|
509
|
-
// Generate a function ID for caching
|
|
510
|
-
const functionId = `port-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
|
|
511
|
-
// Check if we have a cached result
|
|
512
|
-
if (this.functionCache) {
|
|
513
|
-
const cachedPort = this.functionCache.getCachedPort(routeContext, functionId);
|
|
514
|
-
if (cachedPort !== undefined) {
|
|
515
|
-
targetPort = cachedPort;
|
|
516
|
-
this.logger.debug(`Using cached port value for ${functionId}`);
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
// Resolve the function and cache the result
|
|
520
|
-
const resolvedPort = selectedTarget.port(toBaseContext(routeContext));
|
|
521
|
-
targetPort = resolvedPort;
|
|
522
|
-
// Cache the result
|
|
523
|
-
this.functionCache.cachePort(routeContext, functionId, resolvedPort);
|
|
524
|
-
this.logger.debug(`Resolved and cached function-based port to: ${resolvedPort}`);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
else {
|
|
528
|
-
// No cache available, just resolve
|
|
529
|
-
const resolvedPort = selectedTarget.port(routeContext);
|
|
530
|
-
targetPort = resolvedPort;
|
|
531
|
-
this.logger.debug(`Resolved function-based port to: ${resolvedPort}`);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
targetPort = selectedTarget.port === 'preserve' ? routeContext.port : selectedTarget.port;
|
|
536
|
-
}
|
|
537
|
-
// Select a single host if an array was provided
|
|
538
|
-
const selectedHost = Array.isArray(targetHost)
|
|
539
|
-
? targetHost[Math.floor(Math.random() * targetHost.length)]
|
|
540
|
-
: targetHost;
|
|
541
|
-
// Create a destination for the connection pool
|
|
542
|
-
const destination = {
|
|
543
|
-
host: selectedHost,
|
|
544
|
-
port: targetPort
|
|
545
|
-
};
|
|
546
|
-
// Apply URL rewriting if configured
|
|
547
|
-
this.applyUrlRewriting(req, matchingRoute, routeContext);
|
|
548
|
-
// Apply header modifications if configured
|
|
549
|
-
this.applyRouteHeaderModifications(matchingRoute, req, res);
|
|
550
|
-
// Continue with handling using the resolved destination
|
|
551
|
-
HttpRequestHandler.handleHttpRequestWithDestination(req, res, destination, routeContext, startTime, this.logger, this.metricsTracker, matchingRoute // Pass the route config for additional processing
|
|
552
|
-
);
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
catch (err) {
|
|
556
|
-
this.logger.error(`Error evaluating function-based target: ${err}`);
|
|
557
|
-
res.statusCode = 500;
|
|
558
|
-
res.end('Internal Server Error: Failed to evaluate target functions');
|
|
559
|
-
if (this.metricsTracker)
|
|
560
|
-
this.metricsTracker.incrementFailedRequests();
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
// If no route was found, return 404
|
|
565
|
-
this.logger.warn(`No route configuration for host: ${req.headers.host}`);
|
|
566
|
-
res.statusCode = 404;
|
|
567
|
-
res.end('Not Found: No route configuration for this host');
|
|
568
|
-
if (this.metricsTracker)
|
|
569
|
-
this.metricsTracker.incrementFailedRequests();
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Handle HTTP/2 stream requests with function-based target support
|
|
573
|
-
*/
|
|
574
|
-
async handleHttp2(stream, headers) {
|
|
575
|
-
const startTime = Date.now();
|
|
576
|
-
// Create a connection ID for this HTTP/2 stream
|
|
577
|
-
const connectionId = `http2-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
|
|
578
|
-
// Get client IP and server IP from the socket
|
|
579
|
-
const socket = stream.session?.socket;
|
|
580
|
-
const clientIp = socket?.remoteAddress?.replace('::ffff:', '') || '0.0.0.0';
|
|
581
|
-
const serverIp = socket?.localAddress?.replace('::ffff:', '') || '0.0.0.0';
|
|
582
|
-
// Create route context for function-based targets
|
|
583
|
-
const routeContext = this.contextCreator.createHttp2RouteContext(stream, headers, {
|
|
584
|
-
connectionId,
|
|
585
|
-
clientIp,
|
|
586
|
-
serverIp
|
|
587
|
-
});
|
|
588
|
-
// Try to find a matching route using RouteManager
|
|
589
|
-
let matchingRoute = null;
|
|
590
|
-
if (this.routeManager) {
|
|
591
|
-
try {
|
|
592
|
-
const matchResult = this.routeManager.findMatchingRoute(toBaseContext(routeContext));
|
|
593
|
-
matchingRoute = matchResult?.route || null;
|
|
594
|
-
}
|
|
595
|
-
catch (err) {
|
|
596
|
-
this.logger.error('Error finding matching route for HTTP/2 request', err);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
// If we found a matching route with forward action, select appropriate target
|
|
600
|
-
if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.targets && matchingRoute.action.targets.length > 0) {
|
|
601
|
-
this.logger.debug(`Found matching route for HTTP/2 request: ${matchingRoute.name || 'unnamed'}`);
|
|
602
|
-
// Select the appropriate target from the targets array
|
|
603
|
-
const selectedTarget = this.selectTarget(matchingRoute.action.targets, {
|
|
604
|
-
port: routeContext.port,
|
|
605
|
-
path: routeContext.path,
|
|
606
|
-
headers: routeContext.headers,
|
|
607
|
-
method: routeContext.method
|
|
608
|
-
});
|
|
609
|
-
if (!selectedTarget) {
|
|
610
|
-
this.logger.error(`No matching target found for route ${matchingRoute.name}`);
|
|
611
|
-
stream.respond({ ':status': 502 });
|
|
612
|
-
stream.end();
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
// Extract target information, resolving functions if needed
|
|
616
|
-
let targetHost;
|
|
617
|
-
let targetPort;
|
|
618
|
-
try {
|
|
619
|
-
// Check function cache for host and resolve or use cached value
|
|
620
|
-
if (typeof selectedTarget.host === 'function') {
|
|
621
|
-
// Generate a function ID for caching (use route name or ID if available)
|
|
622
|
-
const functionId = `host-http2-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
|
|
623
|
-
// Check if we have a cached result
|
|
624
|
-
if (this.functionCache) {
|
|
625
|
-
const cachedHost = this.functionCache.getCachedHost(routeContext, functionId);
|
|
626
|
-
if (cachedHost !== undefined) {
|
|
627
|
-
targetHost = cachedHost;
|
|
628
|
-
this.logger.debug(`Using cached host value for HTTP/2: ${functionId}`);
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
// Resolve the function and cache the result
|
|
632
|
-
const resolvedHost = selectedTarget.host(toBaseContext(routeContext));
|
|
633
|
-
targetHost = resolvedHost;
|
|
634
|
-
// Cache the result
|
|
635
|
-
this.functionCache.cacheHost(routeContext, functionId, resolvedHost);
|
|
636
|
-
this.logger.debug(`Resolved and cached HTTP/2 function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
else {
|
|
640
|
-
// No cache available, just resolve
|
|
641
|
-
const resolvedHost = selectedTarget.host(routeContext);
|
|
642
|
-
targetHost = resolvedHost;
|
|
643
|
-
this.logger.debug(`Resolved HTTP/2 function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
else {
|
|
647
|
-
targetHost = selectedTarget.host;
|
|
648
|
-
}
|
|
649
|
-
// Check function cache for port and resolve or use cached value
|
|
650
|
-
if (typeof selectedTarget.port === 'function') {
|
|
651
|
-
// Generate a function ID for caching
|
|
652
|
-
const functionId = `port-http2-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
|
|
653
|
-
// Check if we have a cached result
|
|
654
|
-
if (this.functionCache) {
|
|
655
|
-
const cachedPort = this.functionCache.getCachedPort(routeContext, functionId);
|
|
656
|
-
if (cachedPort !== undefined) {
|
|
657
|
-
targetPort = cachedPort;
|
|
658
|
-
this.logger.debug(`Using cached port value for HTTP/2: ${functionId}`);
|
|
659
|
-
}
|
|
660
|
-
else {
|
|
661
|
-
// Resolve the function and cache the result
|
|
662
|
-
const resolvedPort = selectedTarget.port(toBaseContext(routeContext));
|
|
663
|
-
targetPort = resolvedPort;
|
|
664
|
-
// Cache the result
|
|
665
|
-
this.functionCache.cachePort(routeContext, functionId, resolvedPort);
|
|
666
|
-
this.logger.debug(`Resolved and cached HTTP/2 function-based port to: ${resolvedPort}`);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
else {
|
|
670
|
-
// No cache available, just resolve
|
|
671
|
-
const resolvedPort = selectedTarget.port(routeContext);
|
|
672
|
-
targetPort = resolvedPort;
|
|
673
|
-
this.logger.debug(`Resolved HTTP/2 function-based port to: ${resolvedPort}`);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
else {
|
|
677
|
-
targetPort = selectedTarget.port === 'preserve' ? routeContext.port : selectedTarget.port;
|
|
678
|
-
}
|
|
679
|
-
// Select a single host if an array was provided
|
|
680
|
-
const selectedHost = Array.isArray(targetHost)
|
|
681
|
-
? targetHost[Math.floor(Math.random() * targetHost.length)]
|
|
682
|
-
: targetHost;
|
|
683
|
-
// Create a destination for forwarding
|
|
684
|
-
const destination = {
|
|
685
|
-
host: selectedHost,
|
|
686
|
-
port: targetPort
|
|
687
|
-
};
|
|
688
|
-
// Handle HTTP/2 stream based on backend protocol
|
|
689
|
-
const backendProtocol = matchingRoute.action.options?.backendProtocol || this.options.backendProtocol;
|
|
690
|
-
if (backendProtocol === 'http2') {
|
|
691
|
-
// Forward to HTTP/2 backend
|
|
692
|
-
return Http2RequestHandler.handleHttp2WithHttp2Destination(stream, headers, destination, routeContext, this.h2Sessions, this.logger, this.metricsTracker);
|
|
693
|
-
}
|
|
694
|
-
else {
|
|
695
|
-
// Forward to HTTP/1.1 backend
|
|
696
|
-
return Http2RequestHandler.handleHttp2WithHttp1Destination(stream, headers, destination, routeContext, this.logger, this.metricsTracker);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
catch (err) {
|
|
700
|
-
this.logger.error(`Error evaluating function-based target for HTTP/2: ${err}`);
|
|
701
|
-
stream.respond({ ':status': 500 });
|
|
702
|
-
stream.end('Internal Server Error: Failed to evaluate target functions');
|
|
703
|
-
if (this.metricsTracker)
|
|
704
|
-
this.metricsTracker.incrementFailedRequests();
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
// Fall back to legacy routing if no matching route found
|
|
709
|
-
const method = headers[':method'] || 'GET';
|
|
710
|
-
const path = headers[':path'] || '/';
|
|
711
|
-
// No route was found
|
|
712
|
-
stream.respond({ ':status': 404 });
|
|
713
|
-
stream.end('Not Found: No route configuration for this request');
|
|
714
|
-
if (this.metricsTracker)
|
|
715
|
-
this.metricsTracker.incrementFailedRequests();
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* Cleanup resources and stop intervals
|
|
719
|
-
*/
|
|
720
|
-
destroy() {
|
|
721
|
-
if (this.rateLimitCleanupInterval) {
|
|
722
|
-
clearInterval(this.rateLimitCleanupInterval);
|
|
723
|
-
this.rateLimitCleanupInterval = null;
|
|
724
|
-
}
|
|
725
|
-
// Close all HTTP/2 sessions
|
|
726
|
-
for (const [key, session] of this.h2Sessions) {
|
|
727
|
-
session.close();
|
|
728
|
-
}
|
|
729
|
-
this.h2Sessions.clear();
|
|
730
|
-
// Clear function cache if it has a destroy method
|
|
731
|
-
if (this.functionCache && typeof this.functionCache.destroy === 'function') {
|
|
732
|
-
this.functionCache.destroy();
|
|
733
|
-
}
|
|
734
|
-
this.logger.debug('RequestHandler destroyed');
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVxdWVzdC1oYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvcHJveGllcy9odHRwLXByb3h5L3JlcXVlc3QtaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sMENBQTBDLENBQUM7QUFDbEQsT0FBTyxFQUdMLFlBQVksR0FDYixNQUFNLG1CQUFtQixDQUFDO0FBQzNCLE9BQU8sRUFBRSxrQkFBa0IsSUFBSSxZQUFZLEVBQUUsTUFBTSxxQ0FBcUMsQ0FBQztBQUN6RixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDdEQsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3RELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQy9ELE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBR2pFLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUNuRSxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDbkUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBYXhEOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGNBQWM7SUFnQnpCLFlBQ1UsT0FBMEIsRUFDMUIsY0FBOEIsRUFDOUIsWUFBMkIsRUFDM0IsYUFBbUIsRUFBRSx5REFBeUQ7SUFDOUUsTUFBWSxDQUFDLHNEQUFzRDs7UUFKbkUsWUFBTyxHQUFQLE9BQU8sQ0FBbUI7UUFDMUIsbUJBQWMsR0FBZCxjQUFjLENBQWdCO1FBQzlCLGlCQUFZLEdBQVosWUFBWSxDQUFlO1FBQzNCLGtCQUFhLEdBQWIsYUFBYSxDQUFNO1FBQ25CLFdBQU0sR0FBTixNQUFNLENBQU07UUFwQmQsbUJBQWMsR0FBOEIsRUFBRSxDQUFDO1FBRS9DLG1C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
|