@push.rocks/smartproxy 6.0.0 → 7.0.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/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/redirect/classes.redirect.d.ts +96 -0
- package/dist_ts/redirect/classes.redirect.js +194 -0
- package/package.json +1 -1
- package/readme.md +64 -68
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +1 -1
- package/ts/redirect/classes.redirect.ts +295 -0
- package/ts/classes.sslredirect.ts +0 -32
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '
|
|
6
|
+
version: '7.0.1',
|
|
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
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLG1PQUFtTztDQUNqUCxDQUFBIn0=
|
package/dist_ts/index.d.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';
|
package/dist_ts/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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';
|
|
8
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
8
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDJDQUEyQyxDQUFDO0FBQzFELGNBQWMsMkNBQTJDLENBQUM7QUFDMUQsY0FBYywwQ0FBMEMsQ0FBQztBQUN6RCxjQUFjLGdDQUFnQyxDQUFDO0FBQy9DLGNBQWMsb0NBQW9DLENBQUM7QUFDbkQsY0FBYyx1Q0FBdUMsQ0FBQztBQUN0RCxjQUFjLHVDQUF1QyxDQUFDIn0=
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export interface RedirectRule {
|
|
2
|
+
/**
|
|
3
|
+
* Optional protocol to match (http or https). If not specified, matches both.
|
|
4
|
+
*/
|
|
5
|
+
fromProtocol?: 'http' | 'https';
|
|
6
|
+
/**
|
|
7
|
+
* Optional hostname pattern to match. Can use * as wildcard.
|
|
8
|
+
* If not specified, matches all hosts.
|
|
9
|
+
*/
|
|
10
|
+
fromHost?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional path prefix to match. If not specified, matches all paths.
|
|
13
|
+
*/
|
|
14
|
+
fromPath?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Target protocol for the redirect (http or https)
|
|
17
|
+
*/
|
|
18
|
+
toProtocol: 'http' | 'https';
|
|
19
|
+
/**
|
|
20
|
+
* Target hostname for the redirect. Can use $1, $2, etc. to reference
|
|
21
|
+
* captured groups from wildcard matches in fromHost.
|
|
22
|
+
*/
|
|
23
|
+
toHost: string;
|
|
24
|
+
/**
|
|
25
|
+
* Optional target path prefix. If not specified, keeps original path.
|
|
26
|
+
* Can use $path to reference the original path.
|
|
27
|
+
*/
|
|
28
|
+
toPath?: string;
|
|
29
|
+
/**
|
|
30
|
+
* HTTP status code for the redirect (301 for permanent, 302 for temporary)
|
|
31
|
+
*/
|
|
32
|
+
statusCode?: 301 | 302 | 307 | 308;
|
|
33
|
+
}
|
|
34
|
+
export declare class Redirect {
|
|
35
|
+
private httpServer?;
|
|
36
|
+
private httpsServer?;
|
|
37
|
+
private rules;
|
|
38
|
+
private httpPort;
|
|
39
|
+
private httpsPort;
|
|
40
|
+
private sslOptions?;
|
|
41
|
+
/**
|
|
42
|
+
* Create a new Redirect instance
|
|
43
|
+
* @param options Configuration options
|
|
44
|
+
*/
|
|
45
|
+
constructor(options?: {
|
|
46
|
+
httpPort?: number;
|
|
47
|
+
httpsPort?: number;
|
|
48
|
+
sslOptions?: {
|
|
49
|
+
key: Buffer;
|
|
50
|
+
cert: Buffer;
|
|
51
|
+
};
|
|
52
|
+
rules?: RedirectRule[];
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Add a redirect rule
|
|
56
|
+
*/
|
|
57
|
+
addRule(rule: RedirectRule): void;
|
|
58
|
+
/**
|
|
59
|
+
* Remove all redirect rules
|
|
60
|
+
*/
|
|
61
|
+
clearRules(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Set SSL options for HTTPS redirects
|
|
64
|
+
*/
|
|
65
|
+
setSslOptions(options: {
|
|
66
|
+
key: Buffer;
|
|
67
|
+
cert: Buffer;
|
|
68
|
+
}): void;
|
|
69
|
+
/**
|
|
70
|
+
* Process a request according to the configured rules
|
|
71
|
+
*/
|
|
72
|
+
private handleRequest;
|
|
73
|
+
/**
|
|
74
|
+
* Find a matching redirect rule for the given request
|
|
75
|
+
*/
|
|
76
|
+
private findMatchingRule;
|
|
77
|
+
/**
|
|
78
|
+
* Build the target URL for a redirect
|
|
79
|
+
*/
|
|
80
|
+
private buildTargetUrl;
|
|
81
|
+
/**
|
|
82
|
+
* Start the redirect server(s)
|
|
83
|
+
*/
|
|
84
|
+
start(): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Stop the redirect server(s)
|
|
87
|
+
*/
|
|
88
|
+
stop(): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
export declare class SslRedirect {
|
|
91
|
+
private redirect;
|
|
92
|
+
port: number;
|
|
93
|
+
constructor(portArg: number);
|
|
94
|
+
start(): Promise<void>;
|
|
95
|
+
stop(): Promise<void>;
|
|
96
|
+
}
|
|
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZWRpcmVjdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3JlZGlyZWN0L2NsYXNzZXMucmVkaXJlY3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUEwQ3pDLE1BQU0sT0FBTyxRQUFRO0lBV25COzs7T0FHRztJQUNILFlBQVksVUFRUixFQUFFO1FBcEJFLFVBQUssR0FBbUIsRUFBRSxDQUFDO1FBQzNCLGFBQVEsR0FBVyxFQUFFLENBQUM7UUFDdEIsY0FBUyxHQUFXLEdBQUcsQ0FBQztRQW1COUIsSUFBSSxPQUFPLENBQUMsUUFBUTtZQUFFLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUN2RCxJQUFJLE9BQU8sQ0FBQyxTQUFTO1lBQUUsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDO1FBQzFELElBQUksT0FBTyxDQUFDLFVBQVU7WUFBRSxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUM7UUFDN0QsSUFBSSxPQUFPLENBQUMsS0FBSztZQUFFLElBQUksQ0FBQyxLQUFLLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztJQUNoRCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxPQUFPLENBQUMsSUFBa0I7UUFDL0IsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDeEIsQ0FBQztJQUVEOztPQUVHO0lBQ0ksVUFBVTtRQUNmLElBQUksQ0FBQyxLQUFLLEdBQUcsRUFBRSxDQUFDO0lBQ2xCLENBQUM7SUFFRDs7T0FFRztJQUNJLGFBQWEsQ0FBQyxPQUFzQztRQUN6RCxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQztJQUM1QixDQUFDO0lBRUQ7O09BRUc7SUFDSyxhQUFhLENBQ25CLE9BQXFDLEVBQ3JDLFFBQXFDLEVBQ3JDLFFBQTBCO1FBRTFC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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.1",
|
|
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
|
@@ -15,7 +15,7 @@ flowchart TB
|
|
|
15
15
|
direction TB
|
|
16
16
|
HTTP80[HTTP Port 80\nSslRedirect]
|
|
17
17
|
HTTPS443[HTTPS Port 443\nNetworkProxy]
|
|
18
|
-
|
|
18
|
+
SmartProxy[SmartProxy\nwith SNI routing]
|
|
19
19
|
NfTables[NfTablesProxy]
|
|
20
20
|
Router[ProxyRouter]
|
|
21
21
|
ACME[Port80Handler\nACME/Let's Encrypt]
|
|
@@ -31,16 +31,16 @@ flowchart TB
|
|
|
31
31
|
Client -->|HTTP Request| HTTP80
|
|
32
32
|
HTTP80 -->|Redirect| Client
|
|
33
33
|
Client -->|HTTPS Request| HTTPS443
|
|
34
|
-
Client -->|TLS/TCP|
|
|
34
|
+
Client -->|TLS/TCP| SmartProxy
|
|
35
35
|
|
|
36
36
|
HTTPS443 -->|Route Request| Router
|
|
37
37
|
Router -->|Proxy Request| Service1
|
|
38
38
|
Router -->|Proxy Request| Service2
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
SmartProxy -->|Direct TCP| Service2
|
|
41
|
+
SmartProxy -->|Direct TCP| Service3
|
|
42
42
|
|
|
43
|
-
NfTables -.->|Low-level forwarding|
|
|
43
|
+
NfTables -.->|Low-level forwarding| SmartProxy
|
|
44
44
|
|
|
45
45
|
HTTP80 -.->|Challenge Response| ACME
|
|
46
46
|
ACME -.->|Generate/Manage| Certs
|
|
@@ -51,7 +51,7 @@ flowchart TB
|
|
|
51
51
|
classDef client fill:#dfd,stroke:#333,stroke-width:2px;
|
|
52
52
|
|
|
53
53
|
class Client client;
|
|
54
|
-
class HTTP80,HTTPS443,
|
|
54
|
+
class HTTP80,HTTPS443,SmartProxy,NfTables,Router,ACME component;
|
|
55
55
|
class Service1,Service2,Service3 backend;
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -98,49 +98,49 @@ sequenceDiagram
|
|
|
98
98
|
end
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
###
|
|
101
|
+
### SNI-based Connection Handling
|
|
102
102
|
This diagram illustrates how TCP connections with SNI (Server Name Indication) are processed and forwarded:
|
|
103
103
|
|
|
104
104
|
```mermaid
|
|
105
105
|
sequenceDiagram
|
|
106
106
|
participant Client
|
|
107
|
-
participant
|
|
107
|
+
participant SmartProxy
|
|
108
108
|
participant Backend
|
|
109
109
|
|
|
110
|
-
Client->>
|
|
110
|
+
Client->>SmartProxy: TLS Connection
|
|
111
111
|
|
|
112
112
|
alt SNI Enabled
|
|
113
|
-
|
|
114
|
-
Client->>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
SmartProxy->>Client: Accept Connection
|
|
114
|
+
Client->>SmartProxy: TLS ClientHello with SNI
|
|
115
|
+
SmartProxy->>SmartProxy: Extract SNI Hostname
|
|
116
|
+
SmartProxy->>SmartProxy: Match Domain Config
|
|
117
|
+
SmartProxy->>SmartProxy: Validate Client IP
|
|
118
118
|
|
|
119
119
|
alt IP Allowed
|
|
120
|
-
|
|
121
|
-
Note over
|
|
120
|
+
SmartProxy->>Backend: Forward Connection
|
|
121
|
+
Note over SmartProxy,Backend: Bidirectional Data Flow
|
|
122
122
|
else IP Rejected
|
|
123
|
-
|
|
123
|
+
SmartProxy->>Client: Close Connection
|
|
124
124
|
end
|
|
125
125
|
else Port-based Routing
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
126
|
+
SmartProxy->>SmartProxy: Match Port Range
|
|
127
|
+
SmartProxy->>SmartProxy: Find Domain Config
|
|
128
|
+
SmartProxy->>SmartProxy: Validate Client IP
|
|
129
129
|
|
|
130
130
|
alt IP Allowed
|
|
131
|
-
|
|
132
|
-
Note over
|
|
131
|
+
SmartProxy->>Backend: Forward Connection
|
|
132
|
+
Note over SmartProxy,Backend: Bidirectional Data Flow
|
|
133
133
|
else IP Rejected
|
|
134
|
-
|
|
134
|
+
SmartProxy->>Client: Close Connection
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
loop Connection Active
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
SmartProxy-->>SmartProxy: Monitor Activity
|
|
140
|
+
SmartProxy-->>SmartProxy: Check Max Lifetime
|
|
141
141
|
alt Inactivity or Max Lifetime Exceeded
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
SmartProxy->>Client: Close Connection
|
|
143
|
+
SmartProxy->>Backend: Close Connection
|
|
144
144
|
end
|
|
145
145
|
end
|
|
146
146
|
```
|
|
@@ -192,7 +192,7 @@ sequenceDiagram
|
|
|
192
192
|
|
|
193
193
|
- **HTTPS Reverse Proxy** - Route traffic to backend services based on hostname with TLS termination
|
|
194
194
|
- **WebSocket Support** - Full WebSocket proxying with heartbeat monitoring
|
|
195
|
-
- **TCP
|
|
195
|
+
- **TCP Connection Handling** - Advanced connection handling with SNI inspection and domain-based routing
|
|
196
196
|
- **Enhanced TLS Handling** - Robust TLS handshake processing with improved certificate error handling
|
|
197
197
|
- **HTTP to HTTPS Redirection** - Automatically redirect HTTP requests to HTTPS
|
|
198
198
|
- **Let's Encrypt Integration** - Automatic certificate management using ACME protocol
|
|
@@ -224,15 +224,16 @@ const proxy = new NetworkProxy({
|
|
|
224
224
|
const proxyConfigs = [
|
|
225
225
|
{
|
|
226
226
|
hostName: 'example.com',
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
destinationIps: ['127.0.0.1'],
|
|
228
|
+
destinationPorts: [3000],
|
|
229
229
|
publicKey: 'your-cert-content',
|
|
230
|
-
privateKey: 'your-key-content'
|
|
230
|
+
privateKey: 'your-key-content',
|
|
231
|
+
rewriteHostHeader: true
|
|
231
232
|
},
|
|
232
233
|
{
|
|
233
234
|
hostName: 'api.example.com',
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
destinationIps: ['127.0.0.1'],
|
|
236
|
+
destinationPorts: [4000],
|
|
236
237
|
publicKey: 'your-cert-content',
|
|
237
238
|
privateKey: 'your-key-content',
|
|
238
239
|
// Optional basic auth
|
|
@@ -266,13 +267,13 @@ const redirector = new SslRedirect(80);
|
|
|
266
267
|
redirector.start();
|
|
267
268
|
```
|
|
268
269
|
|
|
269
|
-
### TCP
|
|
270
|
+
### TCP Connection Handling with Domain-based Routing
|
|
270
271
|
|
|
271
272
|
```typescript
|
|
272
|
-
import {
|
|
273
|
+
import { SmartProxy } from '@push.rocks/smartproxy';
|
|
273
274
|
|
|
274
|
-
// Configure
|
|
275
|
-
const
|
|
275
|
+
// Configure SmartProxy with domain-based routing
|
|
276
|
+
const smartProxy = new SmartProxy({
|
|
276
277
|
fromPort: 443,
|
|
277
278
|
toPort: 8443,
|
|
278
279
|
targetIP: 'localhost', // Default target host
|
|
@@ -312,7 +313,7 @@ const portProxy = new PortProxy({
|
|
|
312
313
|
preserveSourceIP: true
|
|
313
314
|
});
|
|
314
315
|
|
|
315
|
-
|
|
316
|
+
smartProxy.start();
|
|
316
317
|
```
|
|
317
318
|
|
|
318
319
|
### NfTables Port Forwarding
|
|
@@ -376,7 +377,13 @@ await basicProxy.start();
|
|
|
376
377
|
import { Port80Handler } from '@push.rocks/smartproxy';
|
|
377
378
|
|
|
378
379
|
// Create an ACME handler for Let's Encrypt
|
|
379
|
-
const acmeHandler = new Port80Handler(
|
|
380
|
+
const acmeHandler = new Port80Handler({
|
|
381
|
+
port: 80,
|
|
382
|
+
contactEmail: 'admin@example.com',
|
|
383
|
+
useProduction: true, // Use Let's Encrypt production servers (default is staging)
|
|
384
|
+
renewThresholdDays: 30, // Renew certificates 30 days before expiry
|
|
385
|
+
httpsRedirectPort: 443 // Redirect HTTP to HTTPS on this port
|
|
386
|
+
});
|
|
380
387
|
|
|
381
388
|
// Add domains to manage certificates for
|
|
382
389
|
acmeHandler.addDomain({
|
|
@@ -407,8 +414,14 @@ acmeHandler.addDomain({
|
|
|
407
414
|
| Option | Description | Default |
|
|
408
415
|
|----------------|---------------------------------------------------|---------|
|
|
409
416
|
| `port` | Port to listen on for HTTPS connections | - |
|
|
417
|
+
| `maxConnections` | Maximum concurrent connections | 10000 |
|
|
418
|
+
| `keepAliveTimeout` | Keep-alive timeout in milliseconds | 60000 |
|
|
419
|
+
| `headersTimeout` | Headers timeout in milliseconds | 60000 |
|
|
420
|
+
| `logLevel` | Logging level ('error', 'warn', 'info', 'debug') | 'info' |
|
|
421
|
+
| `cors` | CORS configuration object | - |
|
|
422
|
+
| `rewriteHostHeader` | Whether to rewrite the Host header | false |
|
|
410
423
|
|
|
411
|
-
###
|
|
424
|
+
### SmartProxy Settings
|
|
412
425
|
|
|
413
426
|
| Option | Description | Default |
|
|
414
427
|
|---------------------------|--------------------------------------------------------|-------------|
|
|
@@ -460,28 +473,11 @@ acmeHandler.addDomain({
|
|
|
460
473
|
| `qos` | Quality of Service options (object) | - |
|
|
461
474
|
| `netProxyIntegration` | NetworkProxy integration options (object) | - |
|
|
462
475
|
|
|
463
|
-
#### NfTablesProxy QoS Options
|
|
464
|
-
|
|
465
|
-
| Option | Description | Default |
|
|
466
|
-
|----------------------|---------------------------------------------------|---------|
|
|
467
|
-
| `enabled` | Enable Quality of Service features | false |
|
|
468
|
-
| `maxRate` | Maximum bandwidth rate (e.g. "10mbps") | - |
|
|
469
|
-
| `priority` | Traffic priority (1-10, 1 is highest) | - |
|
|
470
|
-
| `markConnections` | Mark connections for easier management | false |
|
|
471
|
-
|
|
472
|
-
#### NfTablesProxy NetworkProxy Integration Options
|
|
473
|
-
|
|
474
|
-
| Option | Description | Default |
|
|
475
|
-
|----------------------|---------------------------------------------------|---------|
|
|
476
|
-
| `enabled` | Enable NetworkProxy integration | false |
|
|
477
|
-
| `redirectLocalhost` | Redirect localhost traffic to NetworkProxy | false |
|
|
478
|
-
| `sslTerminationPort` | Port where NetworkProxy handles SSL termination | - |
|
|
479
|
-
|
|
480
476
|
## Advanced Features
|
|
481
477
|
|
|
482
478
|
### TLS Handshake Optimization
|
|
483
479
|
|
|
484
|
-
The enhanced `
|
|
480
|
+
The enhanced `SmartProxy` implementation includes significant improvements for TLS handshake handling:
|
|
485
481
|
|
|
486
482
|
- Robust SNI extraction with improved error handling
|
|
487
483
|
- Increased buffer size for complex TLS handshakes (10MB)
|
|
@@ -492,7 +488,7 @@ The enhanced `PortProxy` implementation includes significant improvements for TL
|
|
|
492
488
|
|
|
493
489
|
```typescript
|
|
494
490
|
// Example configuration to solve Chrome certificate errors
|
|
495
|
-
const portProxy = new
|
|
491
|
+
const portProxy = new SmartProxy({
|
|
496
492
|
// ... other settings
|
|
497
493
|
initialDataTimeout: 60000, // Give browser more time for handshake
|
|
498
494
|
maxPendingDataSize: 10 * 1024 * 1024, // Larger buffer for complex handshakes
|
|
@@ -502,7 +498,7 @@ const portProxy = new PortProxy({
|
|
|
502
498
|
|
|
503
499
|
### Connection Management and Monitoring
|
|
504
500
|
|
|
505
|
-
The `
|
|
501
|
+
The `SmartProxy` class includes built-in connection tracking and monitoring:
|
|
506
502
|
|
|
507
503
|
- Automatic cleanup of idle connections with configurable timeouts
|
|
508
504
|
- Timeouts for connections that exceed maximum lifetime
|
|
@@ -521,7 +517,7 @@ The `NetworkProxy` class provides WebSocket support with:
|
|
|
521
517
|
|
|
522
518
|
### SNI-based Routing
|
|
523
519
|
|
|
524
|
-
The `
|
|
520
|
+
The `SmartProxy` class can inspect the SNI (Server Name Indication) field in TLS handshakes to route connections based on the requested domain:
|
|
525
521
|
|
|
526
522
|
- Multiple backend targets per domain
|
|
527
523
|
- Round-robin load balancing
|
|
@@ -530,7 +526,7 @@ The `PortProxy` class can inspect the SNI (Server Name Indication) field in TLS
|
|
|
530
526
|
|
|
531
527
|
### Enhanced NfTables Management
|
|
532
528
|
|
|
533
|
-
The `NfTablesProxy` class offers advanced capabilities
|
|
529
|
+
The `NfTablesProxy` class offers advanced capabilities:
|
|
534
530
|
|
|
535
531
|
- Support for multiple port ranges and individual ports
|
|
536
532
|
- More efficient IP filtering using nftables sets
|
|
@@ -544,7 +540,7 @@ The `NfTablesProxy` class offers advanced capabilities compared to the previous
|
|
|
544
540
|
|
|
545
541
|
### Port80Handler with Glob Pattern Support
|
|
546
542
|
|
|
547
|
-
The `Port80Handler` class
|
|
543
|
+
The `Port80Handler` class includes support for glob pattern domain matching:
|
|
548
544
|
|
|
549
545
|
- Supports wildcard domains like `*.example.com` for HTTP request routing
|
|
550
546
|
- Detects glob patterns and skips certificate issuance for them
|
|
@@ -566,7 +562,7 @@ If you experience certificate errors in browsers, especially in Chrome, try thes
|
|
|
566
562
|
|
|
567
563
|
```typescript
|
|
568
564
|
// Configuration to fix Chrome certificate errors
|
|
569
|
-
const
|
|
565
|
+
const smartProxy = new SmartProxy({
|
|
570
566
|
// ... other settings
|
|
571
567
|
initialDataTimeout: 60000,
|
|
572
568
|
maxPendingDataSize: 10 * 1024 * 1024,
|
|
@@ -585,14 +581,14 @@ For improved connection stability in high-traffic environments:
|
|
|
585
581
|
4. **Monitor Connection Statistics**: Enable detailed logging to track termination reasons
|
|
586
582
|
5. **Fine-tune Inactivity Checks**: Adjust `inactivityCheckInterval` based on your traffic patterns
|
|
587
583
|
|
|
588
|
-
###
|
|
584
|
+
### NfTables Troubleshooting
|
|
589
585
|
|
|
590
|
-
If you're experiencing issues with
|
|
586
|
+
If you're experiencing issues with NfTablesProxy:
|
|
591
587
|
|
|
592
588
|
1. **Enable Detailed Logging**: Set `enableLogging: true` to see all rule operations
|
|
593
589
|
2. **Force Clean Slate**: Use `forceCleanSlate: true` to remove any lingering rules
|
|
594
|
-
3. **Use
|
|
595
|
-
4. **Check Permissions**: Ensure your process has sufficient permissions to modify
|
|
590
|
+
3. **Use IP Sets**: Enable `useIPSets: true` for cleaner rule management
|
|
591
|
+
4. **Check Permissions**: Ensure your process has sufficient permissions to modify nftables
|
|
596
592
|
5. **Verify IPv6 Support**: If using `ipv6Support: true`, ensure ip6tables is available
|
|
597
593
|
|
|
598
594
|
## License and Legal Information
|
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.0.1',
|
|
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';
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
|
|
3
|
+
export interface RedirectRule {
|
|
4
|
+
/**
|
|
5
|
+
* Optional protocol to match (http or https). If not specified, matches both.
|
|
6
|
+
*/
|
|
7
|
+
fromProtocol?: 'http' | 'https';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Optional hostname pattern to match. Can use * as wildcard.
|
|
11
|
+
* If not specified, matches all hosts.
|
|
12
|
+
*/
|
|
13
|
+
fromHost?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Optional path prefix to match. If not specified, matches all paths.
|
|
17
|
+
*/
|
|
18
|
+
fromPath?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Target protocol for the redirect (http or https)
|
|
22
|
+
*/
|
|
23
|
+
toProtocol: 'http' | 'https';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Target hostname for the redirect. Can use $1, $2, etc. to reference
|
|
27
|
+
* captured groups from wildcard matches in fromHost.
|
|
28
|
+
*/
|
|
29
|
+
toHost: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Optional target path prefix. If not specified, keeps original path.
|
|
33
|
+
* Can use $path to reference the original path.
|
|
34
|
+
*/
|
|
35
|
+
toPath?: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* HTTP status code for the redirect (301 for permanent, 302 for temporary)
|
|
39
|
+
*/
|
|
40
|
+
statusCode?: 301 | 302 | 307 | 308;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class Redirect {
|
|
44
|
+
private httpServer?: plugins.http.Server;
|
|
45
|
+
private httpsServer?: plugins.https.Server;
|
|
46
|
+
private rules: RedirectRule[] = [];
|
|
47
|
+
private httpPort: number = 80;
|
|
48
|
+
private httpsPort: number = 443;
|
|
49
|
+
private sslOptions?: {
|
|
50
|
+
key: Buffer;
|
|
51
|
+
cert: Buffer;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a new Redirect instance
|
|
56
|
+
* @param options Configuration options
|
|
57
|
+
*/
|
|
58
|
+
constructor(options: {
|
|
59
|
+
httpPort?: number;
|
|
60
|
+
httpsPort?: number;
|
|
61
|
+
sslOptions?: {
|
|
62
|
+
key: Buffer;
|
|
63
|
+
cert: Buffer;
|
|
64
|
+
};
|
|
65
|
+
rules?: RedirectRule[];
|
|
66
|
+
} = {}) {
|
|
67
|
+
if (options.httpPort) this.httpPort = options.httpPort;
|
|
68
|
+
if (options.httpsPort) this.httpsPort = options.httpsPort;
|
|
69
|
+
if (options.sslOptions) this.sslOptions = options.sslOptions;
|
|
70
|
+
if (options.rules) this.rules = options.rules;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Add a redirect rule
|
|
75
|
+
*/
|
|
76
|
+
public addRule(rule: RedirectRule): void {
|
|
77
|
+
this.rules.push(rule);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Remove all redirect rules
|
|
82
|
+
*/
|
|
83
|
+
public clearRules(): void {
|
|
84
|
+
this.rules = [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Set SSL options for HTTPS redirects
|
|
89
|
+
*/
|
|
90
|
+
public setSslOptions(options: { key: Buffer; cert: Buffer }): void {
|
|
91
|
+
this.sslOptions = options;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Process a request according to the configured rules
|
|
96
|
+
*/
|
|
97
|
+
private handleRequest(
|
|
98
|
+
request: plugins.http.IncomingMessage,
|
|
99
|
+
response: plugins.http.ServerResponse,
|
|
100
|
+
protocol: 'http' | 'https'
|
|
101
|
+
): void {
|
|
102
|
+
const requestUrl = new URL(
|
|
103
|
+
request.url || '/',
|
|
104
|
+
`${protocol}://${request.headers.host || 'localhost'}`
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const host = requestUrl.hostname;
|
|
108
|
+
const path = requestUrl.pathname + requestUrl.search;
|
|
109
|
+
|
|
110
|
+
// Find matching rule
|
|
111
|
+
const matchedRule = this.findMatchingRule(protocol, host, path);
|
|
112
|
+
|
|
113
|
+
if (matchedRule) {
|
|
114
|
+
const targetUrl = this.buildTargetUrl(matchedRule, host, path);
|
|
115
|
+
|
|
116
|
+
console.log(`Redirecting ${protocol}://${host}${path} to ${targetUrl}`);
|
|
117
|
+
|
|
118
|
+
response.writeHead(matchedRule.statusCode || 302, {
|
|
119
|
+
Location: targetUrl,
|
|
120
|
+
});
|
|
121
|
+
response.end();
|
|
122
|
+
} else {
|
|
123
|
+
// No matching rule, send 404
|
|
124
|
+
response.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
125
|
+
response.end('Not Found');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Find a matching redirect rule for the given request
|
|
131
|
+
*/
|
|
132
|
+
private findMatchingRule(
|
|
133
|
+
protocol: 'http' | 'https',
|
|
134
|
+
host: string,
|
|
135
|
+
path: string
|
|
136
|
+
): RedirectRule | undefined {
|
|
137
|
+
return this.rules.find((rule) => {
|
|
138
|
+
// Check protocol match
|
|
139
|
+
if (rule.fromProtocol && rule.fromProtocol !== protocol) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check host match
|
|
144
|
+
if (rule.fromHost) {
|
|
145
|
+
const pattern = rule.fromHost.replace(/\*/g, '(.*)');
|
|
146
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
147
|
+
if (!regex.test(host)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Check path match
|
|
153
|
+
if (rule.fromPath && !path.startsWith(rule.fromPath)) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Build the target URL for a redirect
|
|
163
|
+
*/
|
|
164
|
+
private buildTargetUrl(rule: RedirectRule, originalHost: string, originalPath: string): string {
|
|
165
|
+
let targetHost = rule.toHost;
|
|
166
|
+
|
|
167
|
+
// Replace wildcards in host
|
|
168
|
+
if (rule.fromHost && rule.fromHost.includes('*')) {
|
|
169
|
+
const pattern = rule.fromHost.replace(/\*/g, '(.*)');
|
|
170
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
171
|
+
const matches = originalHost.match(regex);
|
|
172
|
+
|
|
173
|
+
if (matches) {
|
|
174
|
+
for (let i = 1; i < matches.length; i++) {
|
|
175
|
+
targetHost = targetHost.replace(`$${i}`, matches[i]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Build target path
|
|
181
|
+
let targetPath = originalPath;
|
|
182
|
+
if (rule.toPath) {
|
|
183
|
+
if (rule.toPath.includes('$path')) {
|
|
184
|
+
// Replace $path with original path, optionally removing the fromPath prefix
|
|
185
|
+
const pathSuffix = rule.fromPath ?
|
|
186
|
+
originalPath.substring(rule.fromPath.length) :
|
|
187
|
+
originalPath;
|
|
188
|
+
|
|
189
|
+
targetPath = rule.toPath.replace('$path', pathSuffix);
|
|
190
|
+
} else {
|
|
191
|
+
targetPath = rule.toPath;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return `${rule.toProtocol}://${targetHost}${targetPath}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Start the redirect server(s)
|
|
200
|
+
*/
|
|
201
|
+
public async start(): Promise<void> {
|
|
202
|
+
const tasks = [];
|
|
203
|
+
|
|
204
|
+
// Create and start HTTP server if we have a port
|
|
205
|
+
if (this.httpPort) {
|
|
206
|
+
this.httpServer = plugins.http.createServer((req, res) =>
|
|
207
|
+
this.handleRequest(req, res, 'http')
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const httpStartPromise = new Promise<void>((resolve) => {
|
|
211
|
+
this.httpServer?.listen(this.httpPort, () => {
|
|
212
|
+
console.log(`HTTP redirect server started on port ${this.httpPort}`);
|
|
213
|
+
resolve();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
tasks.push(httpStartPromise);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Create and start HTTPS server if we have SSL options and a port
|
|
221
|
+
if (this.httpsPort && this.sslOptions) {
|
|
222
|
+
this.httpsServer = plugins.https.createServer(this.sslOptions, (req, res) =>
|
|
223
|
+
this.handleRequest(req, res, 'https')
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const httpsStartPromise = new Promise<void>((resolve) => {
|
|
227
|
+
this.httpsServer?.listen(this.httpsPort, () => {
|
|
228
|
+
console.log(`HTTPS redirect server started on port ${this.httpsPort}`);
|
|
229
|
+
resolve();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
tasks.push(httpsStartPromise);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Wait for all servers to start
|
|
237
|
+
await Promise.all(tasks);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Stop the redirect server(s)
|
|
242
|
+
*/
|
|
243
|
+
public async stop(): Promise<void> {
|
|
244
|
+
const tasks = [];
|
|
245
|
+
|
|
246
|
+
if (this.httpServer) {
|
|
247
|
+
const httpStopPromise = new Promise<void>((resolve) => {
|
|
248
|
+
this.httpServer?.close(() => {
|
|
249
|
+
console.log('HTTP redirect server stopped');
|
|
250
|
+
resolve();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
tasks.push(httpStopPromise);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (this.httpsServer) {
|
|
257
|
+
const httpsStopPromise = new Promise<void>((resolve) => {
|
|
258
|
+
this.httpsServer?.close(() => {
|
|
259
|
+
console.log('HTTPS redirect server stopped');
|
|
260
|
+
resolve();
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
tasks.push(httpsStopPromise);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await Promise.all(tasks);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// For backward compatibility
|
|
271
|
+
export class SslRedirect {
|
|
272
|
+
private redirect: Redirect;
|
|
273
|
+
port: number;
|
|
274
|
+
|
|
275
|
+
constructor(portArg: number) {
|
|
276
|
+
this.port = portArg;
|
|
277
|
+
this.redirect = new Redirect({
|
|
278
|
+
httpPort: portArg,
|
|
279
|
+
rules: [{
|
|
280
|
+
fromProtocol: 'http',
|
|
281
|
+
toProtocol: 'https',
|
|
282
|
+
toHost: '$1',
|
|
283
|
+
statusCode: 302
|
|
284
|
+
}]
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public async start() {
|
|
289
|
+
await this.redirect.start();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
public async stop() {
|
|
293
|
+
await this.redirect.stop();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import * as plugins from './plugins.js';
|
|
2
|
-
|
|
3
|
-
export class SslRedirect {
|
|
4
|
-
httpServer: plugins.http.Server;
|
|
5
|
-
port: number;
|
|
6
|
-
constructor(portArg: number) {
|
|
7
|
-
this.port = portArg;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
public async start() {
|
|
11
|
-
this.httpServer = plugins.http.createServer((request, response) => {
|
|
12
|
-
const requestUrl = new URL(request.url, `http://${request.headers.host}`);
|
|
13
|
-
const completeUrlWithoutProtocol = `${requestUrl.host}${requestUrl.pathname}${requestUrl.search}`;
|
|
14
|
-
const redirectUrl = `https://${completeUrlWithoutProtocol}`;
|
|
15
|
-
console.log(`Got http request for http://${completeUrlWithoutProtocol}`);
|
|
16
|
-
console.log(`Redirecting to ${redirectUrl}`);
|
|
17
|
-
response.writeHead(302, {
|
|
18
|
-
Location: redirectUrl,
|
|
19
|
-
});
|
|
20
|
-
response.end();
|
|
21
|
-
});
|
|
22
|
-
this.httpServer.listen(this.port);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public async stop() {
|
|
26
|
-
const done = plugins.smartpromise.defer();
|
|
27
|
-
this.httpServer.close(() => {
|
|
28
|
-
done.resolve();
|
|
29
|
-
});
|
|
30
|
-
await done.promise;
|
|
31
|
-
}
|
|
32
|
-
}
|