@push.rocks/smartproxy 6.0.1 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/index.js +2 -2
- package/dist_ts/networkproxy/classes.np.networkproxy.d.ts +1 -1
- package/dist_ts/networkproxy/classes.np.networkproxy.js +21 -13
- package/dist_ts/networkproxy/classes.np.requesthandler.d.ts +5 -0
- package/dist_ts/networkproxy/classes.np.requesthandler.js +184 -1
- package/dist_ts/networkproxy/classes.np.types.d.ts +1 -0
- package/dist_ts/networkproxy/classes.np.types.js +1 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/redirect/classes.redirect.d.ts +96 -0
- package/dist_ts/redirect/classes.redirect.js +194 -0
- package/package.json +1 -1
- package/readme.md +21 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +1 -1
- package/ts/networkproxy/classes.np.networkproxy.ts +24 -20
- package/ts/networkproxy/classes.np.requesthandler.ts +181 -0
- package/ts/networkproxy/classes.np.types.ts +2 -0
- package/ts/plugins.ts +2 -1
- package/ts/redirect/classes.redirect.ts +295 -0
- package/ts/classes.sslredirect.ts +0 -32
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
export class Redirect {
|
|
3
|
+
/**
|
|
4
|
+
* Create a new Redirect instance
|
|
5
|
+
* @param options Configuration options
|
|
6
|
+
*/
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.rules = [];
|
|
9
|
+
this.httpPort = 80;
|
|
10
|
+
this.httpsPort = 443;
|
|
11
|
+
if (options.httpPort)
|
|
12
|
+
this.httpPort = options.httpPort;
|
|
13
|
+
if (options.httpsPort)
|
|
14
|
+
this.httpsPort = options.httpsPort;
|
|
15
|
+
if (options.sslOptions)
|
|
16
|
+
this.sslOptions = options.sslOptions;
|
|
17
|
+
if (options.rules)
|
|
18
|
+
this.rules = options.rules;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Add a redirect rule
|
|
22
|
+
*/
|
|
23
|
+
addRule(rule) {
|
|
24
|
+
this.rules.push(rule);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Remove all redirect rules
|
|
28
|
+
*/
|
|
29
|
+
clearRules() {
|
|
30
|
+
this.rules = [];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Set SSL options for HTTPS redirects
|
|
34
|
+
*/
|
|
35
|
+
setSslOptions(options) {
|
|
36
|
+
this.sslOptions = options;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Process a request according to the configured rules
|
|
40
|
+
*/
|
|
41
|
+
handleRequest(request, response, protocol) {
|
|
42
|
+
const requestUrl = new URL(request.url || '/', `${protocol}://${request.headers.host || 'localhost'}`);
|
|
43
|
+
const host = requestUrl.hostname;
|
|
44
|
+
const path = requestUrl.pathname + requestUrl.search;
|
|
45
|
+
// Find matching rule
|
|
46
|
+
const matchedRule = this.findMatchingRule(protocol, host, path);
|
|
47
|
+
if (matchedRule) {
|
|
48
|
+
const targetUrl = this.buildTargetUrl(matchedRule, host, path);
|
|
49
|
+
console.log(`Redirecting ${protocol}://${host}${path} to ${targetUrl}`);
|
|
50
|
+
response.writeHead(matchedRule.statusCode || 302, {
|
|
51
|
+
Location: targetUrl,
|
|
52
|
+
});
|
|
53
|
+
response.end();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// No matching rule, send 404
|
|
57
|
+
response.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
58
|
+
response.end('Not Found');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Find a matching redirect rule for the given request
|
|
63
|
+
*/
|
|
64
|
+
findMatchingRule(protocol, host, path) {
|
|
65
|
+
return this.rules.find((rule) => {
|
|
66
|
+
// Check protocol match
|
|
67
|
+
if (rule.fromProtocol && rule.fromProtocol !== protocol) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
// Check host match
|
|
71
|
+
if (rule.fromHost) {
|
|
72
|
+
const pattern = rule.fromHost.replace(/\*/g, '(.*)');
|
|
73
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
74
|
+
if (!regex.test(host)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Check path match
|
|
79
|
+
if (rule.fromPath && !path.startsWith(rule.fromPath)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Build the target URL for a redirect
|
|
87
|
+
*/
|
|
88
|
+
buildTargetUrl(rule, originalHost, originalPath) {
|
|
89
|
+
let targetHost = rule.toHost;
|
|
90
|
+
// Replace wildcards in host
|
|
91
|
+
if (rule.fromHost && rule.fromHost.includes('*')) {
|
|
92
|
+
const pattern = rule.fromHost.replace(/\*/g, '(.*)');
|
|
93
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
94
|
+
const matches = originalHost.match(regex);
|
|
95
|
+
if (matches) {
|
|
96
|
+
for (let i = 1; i < matches.length; i++) {
|
|
97
|
+
targetHost = targetHost.replace(`$${i}`, matches[i]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Build target path
|
|
102
|
+
let targetPath = originalPath;
|
|
103
|
+
if (rule.toPath) {
|
|
104
|
+
if (rule.toPath.includes('$path')) {
|
|
105
|
+
// Replace $path with original path, optionally removing the fromPath prefix
|
|
106
|
+
const pathSuffix = rule.fromPath ?
|
|
107
|
+
originalPath.substring(rule.fromPath.length) :
|
|
108
|
+
originalPath;
|
|
109
|
+
targetPath = rule.toPath.replace('$path', pathSuffix);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
targetPath = rule.toPath;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return `${rule.toProtocol}://${targetHost}${targetPath}`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Start the redirect server(s)
|
|
119
|
+
*/
|
|
120
|
+
async start() {
|
|
121
|
+
const tasks = [];
|
|
122
|
+
// Create and start HTTP server if we have a port
|
|
123
|
+
if (this.httpPort) {
|
|
124
|
+
this.httpServer = plugins.http.createServer((req, res) => this.handleRequest(req, res, 'http'));
|
|
125
|
+
const httpStartPromise = new Promise((resolve) => {
|
|
126
|
+
this.httpServer?.listen(this.httpPort, () => {
|
|
127
|
+
console.log(`HTTP redirect server started on port ${this.httpPort}`);
|
|
128
|
+
resolve();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
tasks.push(httpStartPromise);
|
|
132
|
+
}
|
|
133
|
+
// Create and start HTTPS server if we have SSL options and a port
|
|
134
|
+
if (this.httpsPort && this.sslOptions) {
|
|
135
|
+
this.httpsServer = plugins.https.createServer(this.sslOptions, (req, res) => this.handleRequest(req, res, 'https'));
|
|
136
|
+
const httpsStartPromise = new Promise((resolve) => {
|
|
137
|
+
this.httpsServer?.listen(this.httpsPort, () => {
|
|
138
|
+
console.log(`HTTPS redirect server started on port ${this.httpsPort}`);
|
|
139
|
+
resolve();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
tasks.push(httpsStartPromise);
|
|
143
|
+
}
|
|
144
|
+
// Wait for all servers to start
|
|
145
|
+
await Promise.all(tasks);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Stop the redirect server(s)
|
|
149
|
+
*/
|
|
150
|
+
async stop() {
|
|
151
|
+
const tasks = [];
|
|
152
|
+
if (this.httpServer) {
|
|
153
|
+
const httpStopPromise = new Promise((resolve) => {
|
|
154
|
+
this.httpServer?.close(() => {
|
|
155
|
+
console.log('HTTP redirect server stopped');
|
|
156
|
+
resolve();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
tasks.push(httpStopPromise);
|
|
160
|
+
}
|
|
161
|
+
if (this.httpsServer) {
|
|
162
|
+
const httpsStopPromise = new Promise((resolve) => {
|
|
163
|
+
this.httpsServer?.close(() => {
|
|
164
|
+
console.log('HTTPS redirect server stopped');
|
|
165
|
+
resolve();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
tasks.push(httpsStopPromise);
|
|
169
|
+
}
|
|
170
|
+
await Promise.all(tasks);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// For backward compatibility
|
|
174
|
+
export class SslRedirect {
|
|
175
|
+
constructor(portArg) {
|
|
176
|
+
this.port = portArg;
|
|
177
|
+
this.redirect = new Redirect({
|
|
178
|
+
httpPort: portArg,
|
|
179
|
+
rules: [{
|
|
180
|
+
fromProtocol: 'http',
|
|
181
|
+
toProtocol: 'https',
|
|
182
|
+
toHost: '$1',
|
|
183
|
+
statusCode: 302
|
|
184
|
+
}]
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async start() {
|
|
188
|
+
await this.redirect.start();
|
|
189
|
+
}
|
|
190
|
+
async stop() {
|
|
191
|
+
await this.redirect.stop();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZWRpcmVjdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3JlZGlyZWN0L2NsYXNzZXMucmVkaXJlY3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUEwQ3pDLE1BQU0sT0FBTyxRQUFRO0lBV25COzs7T0FHRztJQUNILFlBQVksVUFRUixFQUFFO1FBcEJFLFVBQUssR0FBbUIsRUFBRSxDQUFDO1FBQzNCLGFBQVEsR0FBVyxFQUFFLENBQUM7UUFDdEIsY0FBUyxHQUFXLEdBQUcsQ0FBQztRQW1COUIsSUFBSSxPQUFPLENBQUMsUUFBUTtZQUFFLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUN2RCxJQUFJLE9BQU8sQ0FBQyxTQUFTO1lBQUUsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDO1FBQzFELElBQUksT0FBTyxDQUFDLFVBQVU7WUFBRSxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUM7UUFDN0QsSUFBSSxPQUFPLENBQUMsS0FBSztZQUFFLElBQUksQ0FBQyxLQUFLLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztJQUNoRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxPQUFPLENBQUMsSUFBa0I7UUFDL0IsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDeEIsQ0FBQztJQUVEOztPQUVHO0lBQ0ksVUFBVTtRQUNmLElBQUksQ0FBQyxLQUFLLEdBQUcsRUFBRSxDQUFDO0lBQ2xCLENBQUM7SUFFRDs7T0FFRztJQUNJLGFBQWEsQ0FBQyxPQUFzQztRQUN6RCxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQztJQUM1QixDQUFDO0lBRUQ7O09BRUc7SUFDSyxhQUFhLENBQ25CLE9BQXFDLEVBQ3JDLFFBQXFDLEVBQ3JDLFFBQTBCO1FBRTFCLE1BQU0sVUFBVSxHQUFHLElBQUksR0FBRyxDQUN4QixPQUFPLENBQUMsR0FBRyxJQUFJLEdBQUcsRUFDbEIsR0FBRyxRQUFRLE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLElBQUksV0FBVyxFQUFFLENBQ3ZELENBQUM7UUFFRixNQUFNLElBQUksR0FBRyxVQUFVLENBQUMsUUFBUSxDQUFDO1FBQ2pDLE1BQU0sSUFBSSxHQUFHLFVBQVUsQ0FBQyxRQUFRLEdBQUcsVUFBVSxDQUFDLE1BQU0sQ0FBQztRQUVyRCxxQkFBcUI7UUFDckIsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFFaEUsSUFBSSxXQUFXLEVBQUUsQ0FBQztZQUNoQixNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLFdBQVcsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7WUFFL0QsT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLFFBQVEsTUFBTSxJQUFJLEdBQUcsSUFBSSxPQUFPLFNBQVMsRUFBRSxDQUFDLENBQUM7WUFFeEUsUUFBUSxDQUFDLFNBQVMsQ0FBQyxXQUFXLENBQUMsVUFBVSxJQUFJLEdBQUcsRUFBRTtnQkFDaEQsUUFBUSxFQUFFLFNBQVM7YUFDcEIsQ0FBQyxDQUFDO1lBQ0gsUUFBUSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ2pCLENBQUM7YUFBTSxDQUFDO1lBQ04sNkJBQTZCO1lBQzdCLFFBQVEsQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFLEVBQUUsY0FBYyxFQUFFLFlBQVksRUFBRSxDQUFDLENBQUM7WUFDMUQsUUFBUSxDQUFDLEdBQUcsQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUM1QixDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ssZ0JBQWdCLENBQ3RCLFFBQTBCLEVBQzFCLElBQVksRUFDWixJQUFZO1FBRVosT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFO1lBQzlCLHVCQUF1QjtZQUN2QixJQUFJLElBQUksQ0FBQyxZQUFZLElBQUksSUFBSSxDQUFDLFlBQVksS0FBSyxRQUFRLEVBQUUsQ0FBQztnQkFDeEQsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1lBRUQsbUJBQW1CO1lBQ25CLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO2dCQUNsQixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7Z0JBQ3JELE1BQU0sS0FBSyxHQUFHLElBQUksTUFBTSxDQUFDLElBQUksT0FBTyxHQUFHLENBQUMsQ0FBQztnQkFDekMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztvQkFDdEIsT0FBTyxLQUFLLENBQUM7Z0JBQ2YsQ0FBQztZQUNILENBQUM7WUFFRCxtQkFBbUI7WUFDbkIsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztnQkFDckQsT0FBTyxLQUFLLENBQUM7WUFDZixDQUFDO1lBRUQsT0FBTyxJQUFJLENBQUM7UUFDZCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNLLGNBQWMsQ0FBQyxJQUFrQixFQUFFLFlBQW9CLEVBQUUsWUFBb0I7UUFDbkYsSUFBSSxVQUFVLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUU3Qiw0QkFBNEI7UUFDNUIsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDakQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1lBQ3JELE1BQU0sS0FBSyxHQUFHLElBQUksTUFBTSxDQUFDLElBQUksT0FBTyxHQUFHLENBQUMsQ0FBQztZQUN6QyxNQUFNLE9BQU8sR0FBRyxZQUFZLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBRTFDLElBQUksT0FBTyxFQUFFLENBQUM7Z0JBQ1osS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztvQkFDeEMsVUFBVSxHQUFHLFVBQVUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDdkQsQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDO1FBRUQsb0JBQW9CO1FBQ3BCLElBQUksVUFBVSxHQUFHLFlBQVksQ0FBQztRQUM5QixJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNoQixJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7Z0JBQ2xDLDRFQUE0RTtnQkFDNUUsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO29CQUNoQyxZQUFZLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztvQkFDOUMsWUFBWSxDQUFDO2dCQUVmLFVBQVUsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsVUFBVSxDQUFDLENBQUM7WUFDeEQsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLFVBQVUsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDO1lBQzNCLENBQUM7UUFDSCxDQUFDO1FBRUQsT0FBTyxHQUFHLElBQUksQ0FBQyxVQUFVLE1BQU0sVUFBVSxHQUFHLFVBQVUsRUFBRSxDQUFDO0lBQzNELENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxLQUFLO1FBQ2hCLE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQztRQUVqQixpREFBaUQ7UUFDakQsSUFBSSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDbEIsSUFBSSxDQUFDLFVBQVUsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsRUFBRSxDQUN2RCxJQUFJLENBQUMsYUFBYSxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsTUFBTSxDQUFDLENBQ3JDLENBQUM7WUFFRixNQUFNLGdCQUFnQixHQUFHLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLEVBQUU7Z0JBQ3JELElBQUksQ0FBQyxVQUFVLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsR0FBRyxFQUFFO29CQUMxQyxPQUFPLENBQUMsR0FBRyxDQUFDLHdDQUF3QyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztvQkFDckUsT0FBTyxFQUFFLENBQUM7Z0JBQ1osQ0FBQyxDQUFDLENBQUM7WUFDTCxDQUFDLENBQUMsQ0FBQztZQUVILEtBQUssQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztRQUMvQixDQUFDO1FBRUQsa0VBQWtFO1FBQ2xFLElBQUksSUFBSSxDQUFDLFNBQVMsSUFBSSxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDdEMsSUFBSSxDQUFDLFdBQVcsR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxFQUFFLENBQzFFLElBQUksQ0FBQyxhQUFhLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FDdEMsQ0FBQztZQUVGLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxPQUFPLENBQU8sQ0FBQyxPQUFPLEVBQUUsRUFBRTtnQkFDdEQsSUFBSSxDQUFDLFdBQVcsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxHQUFHLEVBQUU7b0JBQzVDLE9BQU8sQ0FBQyxHQUFHLENBQUMseUNBQXlDLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDO29CQUN2RSxPQUFPLEVBQUUsQ0FBQztnQkFDWixDQUFDLENBQUMsQ0FBQztZQUNMLENBQUMsQ0FBQyxDQUFDO1lBRUgsS0FBSyxDQUFDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1FBQ2hDLENBQUM7UUFFRCxnQ0FBZ0M7UUFDaEMsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNCLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxJQUFJO1FBQ2YsTUFBTSxLQUFLLEdBQUcsRUFBRSxDQUFDO1FBRWpCLElBQUksSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1lBQ3BCLE1BQU0sZUFBZSxHQUFHLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLEVBQUU7Z0JBQ3BELElBQUksQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLEdBQUcsRUFBRTtvQkFDMUIsT0FBTyxDQUFDLEdBQUcsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO29CQUM1QyxPQUFPLEVBQUUsQ0FBQztnQkFDWixDQUFDLENBQUMsQ0FBQztZQUNMLENBQUMsQ0FBQyxDQUFDO1lBQ0gsS0FBSyxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUM5QixDQUFDO1FBRUQsSUFBSSxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDckIsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLE9BQU8sQ0FBTyxDQUFDLE9BQU8sRUFBRSxFQUFFO2dCQUNyRCxJQUFJLENBQUMsV0FBVyxFQUFFLEtBQUssQ0FBQyxHQUFHLEVBQUU7b0JBQzNCLE9BQU8sQ0FBQyxHQUFHLENBQUMsK0JBQStCLENBQUMsQ0FBQztvQkFDN0MsT0FBTyxFQUFFLENBQUM7Z0JBQ1osQ0FBQyxDQUFDLENBQUM7WUFDTCxDQUFDLENBQUMsQ0FBQztZQUNILEtBQUssQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztRQUMvQixDQUFDO1FBRUQsTUFBTSxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzNCLENBQUM7Q0FDRjtBQUVELDZCQUE2QjtBQUM3QixNQUFNLE9BQU8sV0FBVztJQUl0QixZQUFZLE9BQWU7UUFDekIsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUM7UUFDcEIsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLFFBQVEsQ0FBQztZQUMzQixRQUFRLEVBQUUsT0FBTztZQUNqQixLQUFLLEVBQUUsQ0FBQztvQkFDTixZQUFZLEVBQUUsTUFBTTtvQkFDcEIsVUFBVSxFQUFFLE9BQU87b0JBQ25CLE1BQU0sRUFBRSxJQUFJO29CQUNaLFVBQVUsRUFBRSxHQUFHO2lCQUNoQixDQUFDO1NBQ0gsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVNLEtBQUssQ0FBQyxLQUFLO1FBQ2hCLE1BQU0sSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUM5QixDQUFDO0lBRU0sS0FBSyxDQUFDLElBQUk7UUFDZixNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDN0IsQ0FBQztDQUNGIn0=
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
package/readme.md
CHANGED
|
@@ -197,7 +197,27 @@ sequenceDiagram
|
|
|
197
197
|
- **HTTP to HTTPS Redirection** - Automatically redirect HTTP requests to HTTPS
|
|
198
198
|
- **Let's Encrypt Integration** - Automatic certificate management using ACME protocol
|
|
199
199
|
- **IP Filtering** - Control access with IP allow/block lists using glob patterns
|
|
200
|
-
- **NfTables Integration** - Direct manipulation of nftables for advanced low-level port forwarding
|
|
200
|
+
- **NfTables Integration** - Direct manipulation of nftables for advanced low-level port forwarding
|
|
201
|
+
|
|
202
|
+
## Configuration Options
|
|
203
|
+
|
|
204
|
+
### backendProtocol
|
|
205
|
+
|
|
206
|
+
Type: 'http1' | 'http2' (default: 'http1')
|
|
207
|
+
|
|
208
|
+
Controls the protocol used when proxying requests to backend services. By default, the proxy uses HTTP/1.x (`http.request`). Setting `backendProtocol: 'http2'` establishes HTTP/2 client sessions (`http2.connect`) to your backends for full end-to-end HTTP/2 support (assuming your backend servers support HTTP/2).
|
|
209
|
+
|
|
210
|
+
Example:
|
|
211
|
+
```js
|
|
212
|
+
import { NetworkProxy } from '@push.rocks/smartproxy';
|
|
213
|
+
|
|
214
|
+
const proxy = new NetworkProxy({
|
|
215
|
+
port: 8443,
|
|
216
|
+
backendProtocol: 'http2',
|
|
217
|
+
// other options...
|
|
218
|
+
});
|
|
219
|
+
proxy.start();
|
|
220
|
+
```
|
|
201
221
|
- **Basic Authentication** - Support for basic auth on proxied routes
|
|
202
222
|
- **Connection Management** - Intelligent connection tracking and cleanup with configurable timeouts
|
|
203
223
|
- **Browser Compatibility** - Optimized for modern browsers with fixes for common TLS handshake issues
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '
|
|
6
|
+
version: '7.1.0',
|
|
7
7
|
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
|
|
8
8
|
}
|
package/ts/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from './nfttablesproxy/classes.nftablesproxy.js';
|
|
2
2
|
export * from './networkproxy/classes.np.networkproxy.js';
|
|
3
3
|
export * from './port80handler/classes.port80handler.js';
|
|
4
|
-
export * from './classes.
|
|
4
|
+
export * from './redirect/classes.redirect.js';
|
|
5
5
|
export * from './smartproxy/classes.smartproxy.js';
|
|
6
6
|
export * from './smartproxy/classes.pp.snihandler.js';
|
|
7
7
|
export * from './smartproxy/classes.pp.interfaces.js';
|
|
@@ -16,8 +16,8 @@ export class NetworkProxy implements IMetricsTracker {
|
|
|
16
16
|
public options: INetworkProxyOptions;
|
|
17
17
|
public proxyConfigs: IReverseProxyConfig[] = [];
|
|
18
18
|
|
|
19
|
-
// Server instances
|
|
20
|
-
public httpsServer:
|
|
19
|
+
// Server instances (HTTP/2 with HTTP/1 fallback)
|
|
20
|
+
public httpsServer: any;
|
|
21
21
|
|
|
22
22
|
// Core components
|
|
23
23
|
private certificateManager: CertificateManager;
|
|
@@ -66,6 +66,8 @@ export class NetworkProxy implements IMetricsTracker {
|
|
|
66
66
|
connectionPoolSize: optionsArg.connectionPoolSize || 50,
|
|
67
67
|
portProxyIntegration: optionsArg.portProxyIntegration || false,
|
|
68
68
|
useExternalPort80Handler: optionsArg.useExternalPort80Handler || false,
|
|
69
|
+
// Backend protocol (http1 or http2)
|
|
70
|
+
backendProtocol: optionsArg.backendProtocol || 'http1',
|
|
69
71
|
// Default ACME options
|
|
70
72
|
acme: {
|
|
71
73
|
enabled: optionsArg.acme?.enabled || false,
|
|
@@ -185,33 +187,35 @@ export class NetworkProxy implements IMetricsTracker {
|
|
|
185
187
|
await this.certificateManager.initializePort80Handler();
|
|
186
188
|
}
|
|
187
189
|
|
|
188
|
-
// Create
|
|
189
|
-
this.httpsServer = plugins.
|
|
190
|
+
// Create HTTP/2 server with HTTP/1 fallback
|
|
191
|
+
this.httpsServer = plugins.http2.createSecureServer(
|
|
190
192
|
{
|
|
191
193
|
key: this.certificateManager.getDefaultCertificates().key,
|
|
192
194
|
cert: this.certificateManager.getDefaultCertificates().cert,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
allowHTTP1: true,
|
|
196
|
+
ALPNProtocols: ['h2', 'http/1.1']
|
|
197
|
+
}
|
|
196
198
|
);
|
|
197
199
|
|
|
198
|
-
//
|
|
199
|
-
this.httpsServer.keepAliveTimeout = this.options.keepAliveTimeout;
|
|
200
|
-
this.httpsServer.headersTimeout = this.options.headersTimeout;
|
|
201
|
-
|
|
202
|
-
// Setup connection tracking
|
|
200
|
+
// Track raw TCP connections for metrics and limits
|
|
203
201
|
this.setupConnectionTracking();
|
|
204
|
-
|
|
205
|
-
//
|
|
202
|
+
|
|
203
|
+
// Handle incoming HTTP/2 streams
|
|
204
|
+
this.httpsServer.on('stream', (stream: any, headers: any) => {
|
|
205
|
+
this.requestHandler.handleHttp2(stream, headers);
|
|
206
|
+
});
|
|
207
|
+
// Handle HTTP/1.x fallback requests
|
|
208
|
+
this.httpsServer.on('request', (req: any, res: any) => {
|
|
209
|
+
this.requestHandler.handleRequest(req, res);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Share server with certificate manager for dynamic contexts
|
|
206
213
|
this.certificateManager.setHttpsServer(this.httpsServer);
|
|
207
|
-
|
|
208
|
-
// Setup WebSocket support
|
|
214
|
+
// Setup WebSocket support on HTTP/1 fallback
|
|
209
215
|
this.webSocketHandler.initialize(this.httpsServer);
|
|
210
|
-
|
|
211
|
-
// Start metrics collection
|
|
216
|
+
// Start metrics logging
|
|
212
217
|
this.setupMetricsCollection();
|
|
213
|
-
|
|
214
|
-
// Setup connection pool cleanup interval
|
|
218
|
+
// Start periodic connection pool cleanup
|
|
215
219
|
this.connectionPoolCleanupInterval = this.connectionPool.setupPeriodicCleanup();
|
|
216
220
|
|
|
217
221
|
// Start the server
|
|
@@ -18,6 +18,8 @@ export class RequestHandler {
|
|
|
18
18
|
private defaultHeaders: { [key: string]: string } = {};
|
|
19
19
|
private logger: ILogger;
|
|
20
20
|
private metricsTracker: IMetricsTracker | null = null;
|
|
21
|
+
// HTTP/2 client sessions for backend proxying
|
|
22
|
+
private h2Sessions: Map<string, plugins.http2.ClientHttp2Session> = new Map();
|
|
21
23
|
|
|
22
24
|
constructor(
|
|
23
25
|
private options: INetworkProxyOptions,
|
|
@@ -130,6 +132,70 @@ export class RequestHandler {
|
|
|
130
132
|
|
|
131
133
|
// Apply default headers
|
|
132
134
|
this.applyDefaultHeaders(res);
|
|
135
|
+
// If configured to proxy to backends over HTTP/2, use HTTP/2 client sessions
|
|
136
|
+
if (this.options.backendProtocol === 'http2') {
|
|
137
|
+
// Route and validate config
|
|
138
|
+
const proxyConfig = this.router.routeReq(req);
|
|
139
|
+
if (!proxyConfig) {
|
|
140
|
+
this.logger.warn(`No proxy configuration for host: ${req.headers.host}`);
|
|
141
|
+
res.statusCode = 404;
|
|
142
|
+
res.end('Not Found: No proxy configuration for this host');
|
|
143
|
+
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
// Determine backend target
|
|
147
|
+
const destination = this.connectionPool.getNextTarget(
|
|
148
|
+
proxyConfig.destinationIps,
|
|
149
|
+
proxyConfig.destinationPorts[0]
|
|
150
|
+
);
|
|
151
|
+
// Obtain or create HTTP/2 session
|
|
152
|
+
const key = `${destination.host}:${destination.port}`;
|
|
153
|
+
let session = this.h2Sessions.get(key);
|
|
154
|
+
if (!session || session.closed || (session as any).destroyed) {
|
|
155
|
+
session = plugins.http2.connect(`http://${destination.host}:${destination.port}`);
|
|
156
|
+
this.h2Sessions.set(key, session);
|
|
157
|
+
session.on('error', () => this.h2Sessions.delete(key));
|
|
158
|
+
session.on('close', () => this.h2Sessions.delete(key));
|
|
159
|
+
}
|
|
160
|
+
// Build headers for HTTP/2 request
|
|
161
|
+
const h2Headers: Record<string, string> = {
|
|
162
|
+
':method': req.method || 'GET',
|
|
163
|
+
':path': req.url || '/',
|
|
164
|
+
':authority': `${destination.host}:${destination.port}`
|
|
165
|
+
};
|
|
166
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
167
|
+
if (typeof v === 'string' && !k.startsWith(':')) {
|
|
168
|
+
h2Headers[k] = v;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Open HTTP/2 stream
|
|
172
|
+
const h2Stream = session.request(h2Headers);
|
|
173
|
+
// Pipe client request body to backend
|
|
174
|
+
req.pipe(h2Stream);
|
|
175
|
+
// Handle backend response
|
|
176
|
+
h2Stream.on('response', (headers, flags) => {
|
|
177
|
+
const status = headers[':status'] as number || 502;
|
|
178
|
+
// Map headers
|
|
179
|
+
for (const [hk, hv] of Object.entries(headers)) {
|
|
180
|
+
if (!hk.startsWith(':') && hv) {
|
|
181
|
+
res.setHeader(hk, hv as string);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
res.statusCode = status;
|
|
185
|
+
h2Stream.pipe(res);
|
|
186
|
+
});
|
|
187
|
+
h2Stream.on('error', (err) => {
|
|
188
|
+
this.logger.error(`HTTP/2 proxy error: ${err.message}`);
|
|
189
|
+
if (!res.headersSent) {
|
|
190
|
+
res.statusCode = 502;
|
|
191
|
+
res.end(`Bad Gateway: ${err.message}`);
|
|
192
|
+
} else {
|
|
193
|
+
res.end();
|
|
194
|
+
}
|
|
195
|
+
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
133
199
|
|
|
134
200
|
try {
|
|
135
201
|
// Find target based on hostname
|
|
@@ -275,4 +341,119 @@ export class RequestHandler {
|
|
|
275
341
|
}
|
|
276
342
|
}
|
|
277
343
|
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Handle HTTP/2 stream requests by proxying to HTTP/1 backends
|
|
347
|
+
*/
|
|
348
|
+
public async handleHttp2(stream: any, headers: any): Promise<void> {
|
|
349
|
+
const startTime = Date.now();
|
|
350
|
+
const method = headers[':method'] || 'GET';
|
|
351
|
+
const path = headers[':path'] || '/';
|
|
352
|
+
// If configured to proxy to backends over HTTP/2, use HTTP/2 client sessions
|
|
353
|
+
if (this.options.backendProtocol === 'http2') {
|
|
354
|
+
const authority = headers[':authority'] as string || '';
|
|
355
|
+
const host = authority.split(':')[0];
|
|
356
|
+
const fakeReq: any = { headers: { host }, method: headers[':method'], url: headers[':path'], socket: (stream.session as any).socket };
|
|
357
|
+
const proxyConfig = this.router.routeReq(fakeReq);
|
|
358
|
+
if (!proxyConfig) {
|
|
359
|
+
stream.respond({ ':status': 404 });
|
|
360
|
+
stream.end('Not Found');
|
|
361
|
+
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const destination = this.connectionPool.getNextTarget(proxyConfig.destinationIps, proxyConfig.destinationPorts[0]);
|
|
365
|
+
const key = `${destination.host}:${destination.port}`;
|
|
366
|
+
let session = this.h2Sessions.get(key);
|
|
367
|
+
if (!session || session.closed || (session as any).destroyed) {
|
|
368
|
+
session = plugins.http2.connect(`http://${destination.host}:${destination.port}`);
|
|
369
|
+
this.h2Sessions.set(key, session);
|
|
370
|
+
session.on('error', () => this.h2Sessions.delete(key));
|
|
371
|
+
session.on('close', () => this.h2Sessions.delete(key));
|
|
372
|
+
}
|
|
373
|
+
// Build headers for backend HTTP/2 request
|
|
374
|
+
const h2Headers: Record<string, any> = {
|
|
375
|
+
':method': headers[':method'],
|
|
376
|
+
':path': headers[':path'],
|
|
377
|
+
':authority': `${destination.host}:${destination.port}`
|
|
378
|
+
};
|
|
379
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
380
|
+
if (!k.startsWith(':') && typeof v === 'string') {
|
|
381
|
+
h2Headers[k] = v;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const h2Stream2 = session.request(h2Headers);
|
|
385
|
+
stream.pipe(h2Stream2);
|
|
386
|
+
h2Stream2.on('response', (hdrs: any) => {
|
|
387
|
+
// Map status and headers to client
|
|
388
|
+
const resp: Record<string, any> = { ':status': hdrs[':status'] as number };
|
|
389
|
+
for (const [hk, hv] of Object.entries(hdrs)) {
|
|
390
|
+
if (!hk.startsWith(':') && hv) resp[hk] = hv;
|
|
391
|
+
}
|
|
392
|
+
stream.respond(resp);
|
|
393
|
+
h2Stream2.pipe(stream);
|
|
394
|
+
});
|
|
395
|
+
h2Stream2.on('error', (err) => {
|
|
396
|
+
stream.respond({ ':status': 502 });
|
|
397
|
+
stream.end(`Bad Gateway: ${err.message}`);
|
|
398
|
+
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
399
|
+
});
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
// Determine host for routing
|
|
404
|
+
const authority = headers[':authority'] as string || '';
|
|
405
|
+
const host = authority.split(':')[0];
|
|
406
|
+
// Fake request object for routing
|
|
407
|
+
const fakeReq: any = { headers: { host }, method, url: path, socket: (stream.session as any).socket };
|
|
408
|
+
const proxyConfig = this.router.routeReq(fakeReq as any);
|
|
409
|
+
if (!proxyConfig) {
|
|
410
|
+
stream.respond({ ':status': 404 });
|
|
411
|
+
stream.end('Not Found');
|
|
412
|
+
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
// Select backend target
|
|
416
|
+
const destination = this.connectionPool.getNextTarget(
|
|
417
|
+
proxyConfig.destinationIps,
|
|
418
|
+
proxyConfig.destinationPorts[0]
|
|
419
|
+
);
|
|
420
|
+
// Build headers for HTTP/1 proxy
|
|
421
|
+
const outboundHeaders: Record<string,string> = {};
|
|
422
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
423
|
+
if (typeof key === 'string' && typeof value === 'string' && !key.startsWith(':')) {
|
|
424
|
+
outboundHeaders[key] = value;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (outboundHeaders.host && (proxyConfig as any).rewriteHostHeader) {
|
|
428
|
+
outboundHeaders.host = `${destination.host}:${destination.port}`;
|
|
429
|
+
}
|
|
430
|
+
// Create HTTP/1 proxy request
|
|
431
|
+
const proxyReq = plugins.http.request(
|
|
432
|
+
{ hostname: destination.host, port: destination.port, path, method, headers: outboundHeaders },
|
|
433
|
+
(proxyRes) => {
|
|
434
|
+
// Map status and headers back to HTTP/2
|
|
435
|
+
const responseHeaders: Record<string, number|string|string[]> = {};
|
|
436
|
+
for (const [k, v] of Object.entries(proxyRes.headers)) {
|
|
437
|
+
if (v !== undefined) responseHeaders[k] = v;
|
|
438
|
+
}
|
|
439
|
+
stream.respond({ ':status': proxyRes.statusCode || 500, ...responseHeaders });
|
|
440
|
+
proxyRes.pipe(stream);
|
|
441
|
+
stream.on('close', () => proxyReq.destroy());
|
|
442
|
+
stream.on('error', () => proxyReq.destroy());
|
|
443
|
+
if (this.metricsTracker) stream.on('end', () => this.metricsTracker.incrementRequestsServed());
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
proxyReq.on('error', (err) => {
|
|
447
|
+
stream.respond({ ':status': 502 });
|
|
448
|
+
stream.end(`Bad Gateway: ${err.message}`);
|
|
449
|
+
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
450
|
+
});
|
|
451
|
+
// Pipe client stream to backend
|
|
452
|
+
stream.pipe(proxyReq);
|
|
453
|
+
} catch (err: any) {
|
|
454
|
+
stream.respond({ ':status': 500 });
|
|
455
|
+
stream.end('Internal Server Error');
|
|
456
|
+
if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
278
459
|
}
|
|
@@ -20,6 +20,8 @@ export interface INetworkProxyOptions {
|
|
|
20
20
|
connectionPoolSize?: number; // Maximum connections to maintain in the pool to each backend
|
|
21
21
|
portProxyIntegration?: boolean; // Flag to indicate this proxy is used by PortProxy
|
|
22
22
|
useExternalPort80Handler?: boolean; // Flag to indicate using external Port80Handler
|
|
23
|
+
// Protocol to use when proxying to backends: HTTP/1.x or HTTP/2
|
|
24
|
+
backendProtocol?: 'http1' | 'http2';
|
|
23
25
|
|
|
24
26
|
// ACME certificate management options
|
|
25
27
|
acme?: {
|
package/ts/plugins.ts
CHANGED
|
@@ -5,9 +5,10 @@ import * as https from 'https';
|
|
|
5
5
|
import * as net from 'net';
|
|
6
6
|
import * as tls from 'tls';
|
|
7
7
|
import * as url from 'url';
|
|
8
|
+
import * as http2 from 'http2';
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
export { EventEmitter, http, https, net, tls, url };
|
|
11
|
+
export { EventEmitter, http, https, net, tls, url, http2 };
|
|
11
12
|
|
|
12
13
|
// tsclass scope
|
|
13
14
|
import * as tsclass from '@tsclass/tsclass';
|