@abtnode/router-provider 1.15.17 → 1.16.0-beta-8ee536d7
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/README.md +1 -1
- package/lib/base.js +14 -0
- package/lib/default/daemon.js +338 -0
- package/lib/default/index.js +252 -0
- package/lib/default/proxy.js +556 -0
- package/lib/index.js +32 -23
- package/lib/nginx/includes/cache +6 -0
- package/lib/nginx/includes/params +1 -10
- package/lib/nginx/includes/proxy +8 -0
- package/lib/nginx/index.js +397 -281
- package/lib/nginx/util.js +77 -67
- package/lib/util.js +214 -0
- package/lib/www/502.html +1 -1
- package/lib/www/5xx.html +1 -1
- package/package.json +29 -19
- package/lib/none/index.js +0 -50
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
/* eslint-disable consistent-return */
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const http = require('http');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const httpProxy = require('http-proxy');
|
|
7
|
+
const validUrl = require('valid-url');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const _ = require('lodash');
|
|
10
|
+
const hash = require('object-hash');
|
|
11
|
+
const LRUCache = require('lru-cache');
|
|
12
|
+
const tls = require('tls');
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line global-require
|
|
15
|
+
const logger = require('@abtnode/logger')('router:default:proxy', { filename: 'engine' });
|
|
16
|
+
|
|
17
|
+
const ensureHttp = (str) => (!str.startsWith('http://') && !str.startsWith('https://') ? `http://${str}` : str);
|
|
18
|
+
const prepareUrl = (url) => {
|
|
19
|
+
url = _.clone(url);
|
|
20
|
+
if (_.isString(url)) {
|
|
21
|
+
url = ensureHttp(url);
|
|
22
|
+
if (!validUrl.isHttpUri(url) && !validUrl.isHttpsUri(url)) {
|
|
23
|
+
throw new Error(`uri is not a valid http uri ${url}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
url = new URL(url);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return url;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const routeCache = new LRUCache({ max: 5000 });
|
|
33
|
+
|
|
34
|
+
module.exports = class ReverseProxy {
|
|
35
|
+
opts = {};
|
|
36
|
+
|
|
37
|
+
constructor(opts = {}) {
|
|
38
|
+
this._defaultResolver.priority = 0;
|
|
39
|
+
|
|
40
|
+
this.opts = opts;
|
|
41
|
+
|
|
42
|
+
if (!this.opts.httpProxy) {
|
|
43
|
+
this.opts.httpProxy = {};
|
|
44
|
+
}
|
|
45
|
+
if (!this.opts.headers) {
|
|
46
|
+
this.opts.headers = [];
|
|
47
|
+
}
|
|
48
|
+
if (!this.opts.onError) {
|
|
49
|
+
this.opts.onError = (err) => console.error(err);
|
|
50
|
+
}
|
|
51
|
+
if (!this.opts.corsHandler) {
|
|
52
|
+
this.opts.corsHandler = () => true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const websocketUpgrade = (req, socket, head) => {
|
|
56
|
+
socket.on('error', (err) => logger.error('WebSockets error', { error: err }));
|
|
57
|
+
const src = this._getSource(req);
|
|
58
|
+
this._getTarget(src, req).then((target) => {
|
|
59
|
+
logger.info('upgrade to websocket', { host: req.headers.host, url: req._url, target });
|
|
60
|
+
if (target) {
|
|
61
|
+
this.proxy.ws(req, socket, head, { target });
|
|
62
|
+
} else {
|
|
63
|
+
this._handleNotFound(req, socket);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
this.resolvers = [this._defaultResolver];
|
|
69
|
+
opts.port = opts.port || 8080;
|
|
70
|
+
|
|
71
|
+
if (opts.resolvers) {
|
|
72
|
+
this.addResolver(opts.resolvers);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Routing table.
|
|
76
|
+
this.routing = {};
|
|
77
|
+
this.certs = {};
|
|
78
|
+
|
|
79
|
+
// Create a proxy server with custom application logic
|
|
80
|
+
this.proxy = httpProxy.createProxyServer({
|
|
81
|
+
xfwd: !!opts.xfwd,
|
|
82
|
+
prependPath: false,
|
|
83
|
+
secure: opts.secure !== false,
|
|
84
|
+
timeout: opts.timeout,
|
|
85
|
+
proxyTimeout: opts.proxyTimeout,
|
|
86
|
+
changeOrigin: false,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
this.proxy.on('proxyReq', (p, req) => {
|
|
90
|
+
if (req.host != null) {
|
|
91
|
+
p.setHeader('host', req.host);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// @link: https://github.com/http-party/node-http-proxy/issues/1401
|
|
96
|
+
this.proxy.on('proxyRes', (proxyRes) => {
|
|
97
|
+
delete proxyRes.headers['x-powered-by'];
|
|
98
|
+
this.opts.headers.forEach((x) => {
|
|
99
|
+
proxyRes.headers[x.key] = x.value;
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Optionally create an https proxy server.
|
|
104
|
+
if (opts.ssl) {
|
|
105
|
+
if (Array.isArray(opts.ssl)) {
|
|
106
|
+
opts.ssl.forEach((sslOpts) => {
|
|
107
|
+
this.setupHttpsProxy(this.proxy, websocketUpgrade, sslOpts);
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
this.setupHttpsProxy(this.proxy, websocketUpgrade, opts.ssl);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Plain HTTP Proxy
|
|
115
|
+
const server = this.setupHttpProxy(this.proxy, websocketUpgrade, opts);
|
|
116
|
+
server.listen(opts.port, opts.host);
|
|
117
|
+
|
|
118
|
+
this.proxy.on('error', opts.onError);
|
|
119
|
+
logger.info('Started reverse proxy server on port %s', opts.port);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
setupHttpProxy(proxy, websocketUpgrade, opts) {
|
|
123
|
+
const server = http.createServer((req, res) => {
|
|
124
|
+
const src = this._getSource(req);
|
|
125
|
+
if (this.opts.corsHandler(req, res) === false) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this._getTarget(src, req, res).then((target) => {
|
|
130
|
+
if (target) {
|
|
131
|
+
if (target.abort) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this._shouldRedirectToHttps(this.certs, src, target, this)) {
|
|
136
|
+
this._redirectToHttps(req, res, target, opts.ssl);
|
|
137
|
+
} else {
|
|
138
|
+
if (opts.internal && target.host) {
|
|
139
|
+
req.host = target.host;
|
|
140
|
+
req.headers.host = target.host;
|
|
141
|
+
}
|
|
142
|
+
proxy.web(req, res, {
|
|
143
|
+
target,
|
|
144
|
+
secure: !proxy.options || proxy.options.secure !== false,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
this._handleNotFound(req, res);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Listen to the `upgrade` event and proxy the WebSocket requests as well.
|
|
154
|
+
server.on('upgrade', websocketUpgrade);
|
|
155
|
+
|
|
156
|
+
server.on('error', (err) => logger.error('Http server error', { error: err }));
|
|
157
|
+
|
|
158
|
+
return server;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_shouldRedirectToHttps(certs, src, target) {
|
|
162
|
+
return certs && src in certs && target.sslRedirect;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
setupHttpsProxy(proxy, websocketUpgrade, sslOpts) {
|
|
166
|
+
let ssl = {
|
|
167
|
+
SNICallback: (hostname, cb) => {
|
|
168
|
+
if (cb) {
|
|
169
|
+
cb(null, this.certs[hostname]);
|
|
170
|
+
} else {
|
|
171
|
+
return this.certs[hostname];
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
// Default certs for clients that do not support SNI.
|
|
175
|
+
key: this._getCertData(sslOpts.key),
|
|
176
|
+
cert: this._getCertData(sslOpts.cert),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Allows the option to disable older SSL/TLS versions
|
|
180
|
+
if (sslOpts.secureOptions) {
|
|
181
|
+
ssl.secureOptions = sslOpts.secureOptions;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (sslOpts.opts) {
|
|
185
|
+
ssl = _.defaults(ssl, sslOpts.opts);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.httpsServer = https.createServer(ssl, (req, res) => {
|
|
189
|
+
const src = this._getSource(req);
|
|
190
|
+
if (this.opts.corsHandler(req, res) === false) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const httpProxyOpts = Object.assign({}, this.opts.httpProxy);
|
|
195
|
+
this._getTarget(src, req, res).then((target) => {
|
|
196
|
+
if (target) {
|
|
197
|
+
if (target.abort) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
httpProxyOpts.target = target;
|
|
202
|
+
proxy.web(req, res, httpProxyOpts);
|
|
203
|
+
} else {
|
|
204
|
+
this._handleNotFound(req, res);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
this.httpsServer.on('upgrade', websocketUpgrade);
|
|
210
|
+
this.httpsServer.on('error', (err) => logger.error('HTTPS server Error', { error: err }));
|
|
211
|
+
this.httpsServer.on('clientError', (err) => logger.error('HTTPS client Error', { error: err }));
|
|
212
|
+
|
|
213
|
+
logger.info('Listening to HTTPS requests on port %s', sslOpts.port);
|
|
214
|
+
this.httpsServer.listen(sslOpts.port, sslOpts.ip);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
addResolver(_resolver) {
|
|
218
|
+
const resolver = Array.isArray(_resolver) ? _resolver : [_resolver];
|
|
219
|
+
resolver.forEach((resolveObj) => {
|
|
220
|
+
if (!_.isFunction(resolveObj)) {
|
|
221
|
+
throw new Error('Resolver must be an invokable function.');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!resolveObj.priority) {
|
|
225
|
+
resolveObj.priority = 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.resolvers.push(resolveObj);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
this.resolvers = _.sortBy(_.uniq(this.resolvers), ['priority']).reverse();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
removeResolver(resolver) {
|
|
235
|
+
// since unique resolvers are not checked for performance, just remove every existence.
|
|
236
|
+
this.resolvers = this.resolvers.filter((x) => x !== resolver);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
static buildTarget(target, opts = {}) {
|
|
240
|
+
const newTarget = prepareUrl(target);
|
|
241
|
+
newTarget.sslRedirect = opts.ssl && opts.ssl.redirect !== false;
|
|
242
|
+
return newTarget;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Register a new route.
|
|
247
|
+
*
|
|
248
|
+
* @param {string | URL} src A string or a url parsed by node url module.
|
|
249
|
+
* Note that port is ignored, since the proxy just listens to one port.
|
|
250
|
+
* @param {string} target A string or a url parsed by node url module.
|
|
251
|
+
* @param {*} opts Route options.
|
|
252
|
+
*/
|
|
253
|
+
register(src, target, opts = {}) {
|
|
254
|
+
// allow registering with src or target as an object to pass in
|
|
255
|
+
// options specific to each one.
|
|
256
|
+
if (src && src.src) {
|
|
257
|
+
target = src.target;
|
|
258
|
+
opts = src;
|
|
259
|
+
src = src.src;
|
|
260
|
+
} else if (target && target.target) {
|
|
261
|
+
opts = target;
|
|
262
|
+
target = target.target;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!src || !target) {
|
|
266
|
+
throw Error('Cannot register a new route with unspecified src or target');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
src = prepareUrl(src);
|
|
270
|
+
|
|
271
|
+
const { ssl } = opts;
|
|
272
|
+
if (ssl) {
|
|
273
|
+
if (!this.httpsServer) {
|
|
274
|
+
throw Error('Cannot register https routes without defining a ssl port');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!this.certs[src.hostname]) {
|
|
278
|
+
if (ssl.key || ssl.cert) {
|
|
279
|
+
this.certs[src.hostname] = this._createCredentialContext(ssl.key, ssl.cert);
|
|
280
|
+
} else {
|
|
281
|
+
// Trigger the use of the default certificates.
|
|
282
|
+
this.certs[src.hostname] = undefined;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
target = ReverseProxy.buildTarget(target, opts);
|
|
288
|
+
|
|
289
|
+
if (!this.routing[src.hostname]) {
|
|
290
|
+
this.routing[src.hostname] = [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const host = this.routing[src.hostname];
|
|
294
|
+
const pathname = src.pathname || '/';
|
|
295
|
+
let route = _.find(host, { path: pathname });
|
|
296
|
+
|
|
297
|
+
if (!route) {
|
|
298
|
+
route = { path: pathname, rr: 0, urls: [], opts: Object.assign({}, opts) };
|
|
299
|
+
host.push(route);
|
|
300
|
+
|
|
301
|
+
// Sort routes
|
|
302
|
+
this.routing[src.hostname] = _.sortBy(host, (x) => -x.path.length);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
route.urls.push(target);
|
|
306
|
+
|
|
307
|
+
logger.info('Registered a new route', { from: src, to: target });
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
unregister(src, target) {
|
|
312
|
+
if (!src) {
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
src = prepareUrl(src);
|
|
317
|
+
const routes = this.routing[src.hostname] || [];
|
|
318
|
+
const pathname = src.pathname || '/';
|
|
319
|
+
let i = 0;
|
|
320
|
+
|
|
321
|
+
for (i = 0; i < routes.length; i++) {
|
|
322
|
+
if (routes[i].path === pathname) {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (i < routes.length) {
|
|
328
|
+
const route = routes[i];
|
|
329
|
+
|
|
330
|
+
if (target) {
|
|
331
|
+
target = prepareUrl(target);
|
|
332
|
+
_.remove(route.urls, (x) => x.href === target.href);
|
|
333
|
+
} else {
|
|
334
|
+
route.urls = [];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (route.urls.length === 0) {
|
|
338
|
+
routes.splice(i, 1);
|
|
339
|
+
delete this.certs[src.hostname];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
logger.info('Unregistered a route', { from: src, to: target });
|
|
343
|
+
}
|
|
344
|
+
return this;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
_defaultResolver(host, url) {
|
|
348
|
+
// Given a src resolve it to a target route if any available.
|
|
349
|
+
if (!host) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
url = url || '/';
|
|
354
|
+
|
|
355
|
+
const routes = this.routing[host];
|
|
356
|
+
let i = 0;
|
|
357
|
+
|
|
358
|
+
if (routes) {
|
|
359
|
+
const len = routes.length;
|
|
360
|
+
|
|
361
|
+
// Find path that matches the start of req.url
|
|
362
|
+
for (i = 0; i < len; i++) {
|
|
363
|
+
const route = routes[i];
|
|
364
|
+
|
|
365
|
+
if (route.path === '/' || this._startsWith(url, route.path)) {
|
|
366
|
+
return route;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Resolves to route
|
|
374
|
+
* @param host
|
|
375
|
+
* @param url
|
|
376
|
+
* @returns {*}
|
|
377
|
+
*/
|
|
378
|
+
resolve(host, url, req) {
|
|
379
|
+
const promiseArray = [];
|
|
380
|
+
|
|
381
|
+
host = host && host.toLowerCase();
|
|
382
|
+
for (let i = 0; i < this.resolvers.length; i++) {
|
|
383
|
+
promiseArray.push(this.resolvers[i].call(this, host, url, req));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return Promise.all(promiseArray)
|
|
387
|
+
.then((resolverResults) => {
|
|
388
|
+
for (let i = 0; i < resolverResults.length; i++) {
|
|
389
|
+
let route = resolverResults[i];
|
|
390
|
+
if (route) {
|
|
391
|
+
route = ReverseProxy.buildRoute(route);
|
|
392
|
+
// ensure resolved route has path that prefixes URL
|
|
393
|
+
// no need to check for native routes.
|
|
394
|
+
if (!route.isResolved || route.path === '/' || this._startsWith(url, route.path)) {
|
|
395
|
+
return route;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
.catch((error) => {
|
|
401
|
+
console.error('Resolvers error:', error);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
static buildRoute(route) {
|
|
406
|
+
if (!_.isString(route) && !_.isObject(route)) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// default route type matched.
|
|
411
|
+
if (_.isObject(route) && route.urls && route.path) {
|
|
412
|
+
return route;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// to bust cache for route, you can attach an id to the route object in business layer
|
|
416
|
+
const cacheKey = _.isString(route) ? route : hash(route);
|
|
417
|
+
const entry = routeCache.get(cacheKey);
|
|
418
|
+
if (entry) {
|
|
419
|
+
return entry;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const routeObject = { rr: 0, isResolved: true };
|
|
423
|
+
if (_.isString(route)) {
|
|
424
|
+
routeObject.urls = [ReverseProxy.buildTarget(route)];
|
|
425
|
+
routeObject.path = '/';
|
|
426
|
+
} else {
|
|
427
|
+
if (!route.url) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
routeObject.urls = (_.isArray(route.url) ? route.url : [route.url]).map((url) =>
|
|
432
|
+
ReverseProxy.buildTarget(url, route.opts || {})
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
routeObject.path = route.path || '/';
|
|
436
|
+
routeObject.opts = route.opts || {};
|
|
437
|
+
}
|
|
438
|
+
routeCache.set(cacheKey, routeObject);
|
|
439
|
+
return routeObject;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
_getTarget(src, req, res) {
|
|
443
|
+
const { url } = req;
|
|
444
|
+
|
|
445
|
+
return this.resolve(src, url, req).then((route) => {
|
|
446
|
+
if (!route) {
|
|
447
|
+
logger.warn('no valid route found for given source', { src, url });
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
req._url = url; // save original url
|
|
452
|
+
req.url = url.substr(route.path.length) || '';
|
|
453
|
+
|
|
454
|
+
// Perform Round-Robin on the available targets
|
|
455
|
+
// TODO: if target errors with EHOSTUNREACH we should skip this
|
|
456
|
+
// target and try with another.
|
|
457
|
+
const { urls } = route;
|
|
458
|
+
const j = route.rr;
|
|
459
|
+
route.rr = (j + 1) % urls.length; // get and update Round-robin index.
|
|
460
|
+
const target = route.urls[j];
|
|
461
|
+
|
|
462
|
+
// Fix request url if target name specified.
|
|
463
|
+
if (target.pathname) {
|
|
464
|
+
if (req.url) {
|
|
465
|
+
req.url = path.posix.join(target.pathname, req.url);
|
|
466
|
+
} else {
|
|
467
|
+
req.url = target.pathname;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (route.opts && route.opts.onRequest) {
|
|
472
|
+
const resultFromRequestHandler = route.opts.onRequest(req, res, target);
|
|
473
|
+
if (resultFromRequestHandler !== undefined) {
|
|
474
|
+
logger.info('proxy %s received result from onRequest handler, returning.', src + url);
|
|
475
|
+
return resultFromRequestHandler;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
logger.info('proxy: %s => %s', src + url, path.posix.join(target.host, req.url));
|
|
480
|
+
|
|
481
|
+
return target;
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
_getSource(req) {
|
|
486
|
+
if (this.opts.preferForwardedHost === true && req.headers['x-forwarded-host']) {
|
|
487
|
+
return req.headers['x-forwarded-host'].split(':')[0];
|
|
488
|
+
}
|
|
489
|
+
if (req.headers.host) {
|
|
490
|
+
return req.headers.host.split(':')[0];
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
close() {
|
|
495
|
+
try {
|
|
496
|
+
return Promise.all(
|
|
497
|
+
[this.server, this.httpsServer]
|
|
498
|
+
.filter((s) => s)
|
|
499
|
+
// eslint-disable-next-line no-promise-executor-return
|
|
500
|
+
.map((server) => new Promise((resolve) => server.close(resolve)))
|
|
501
|
+
);
|
|
502
|
+
} catch (err) {
|
|
503
|
+
// Ignore for now...
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
_handleNotFound = (req, res) => {
|
|
508
|
+
const err = new Error(`Not Found: ${this._getSource(req)}${req.url}`);
|
|
509
|
+
err.code = 'NOTFOUND';
|
|
510
|
+
this.opts.onError(err, req, res);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// Redirect to the HTTPS proxy
|
|
514
|
+
_redirectToHttps(req, res, target, ssl) {
|
|
515
|
+
req.url = req._url || req.url; // Get the original url since we are going to redirect.
|
|
516
|
+
|
|
517
|
+
const targetPort = Number(ssl.redirectPort || ssl.port);
|
|
518
|
+
const hostname = req.headers.host.split(':')[0] + (targetPort && targetPort !== 443 ? `:${targetPort}` : '');
|
|
519
|
+
const url = `https://${path.posix.join(hostname, req.url)}`;
|
|
520
|
+
logger.info('redirect: %s => %s', path.posix.join(req.headers.host, req.url), url);
|
|
521
|
+
// We can use 301 for permanent redirect, but its bad for debugging, we may have it as
|
|
522
|
+
// a configurable option.
|
|
523
|
+
res.writeHead(302, { Location: url });
|
|
524
|
+
res.end();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
_startsWith(input, str) {
|
|
528
|
+
return input.slice(0, str.length) === str && (input.length === str.length || input[str.length] === '/');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
_getCertData(source, unbundle) {
|
|
532
|
+
let data;
|
|
533
|
+
if (source) {
|
|
534
|
+
if (_.isArray(source)) {
|
|
535
|
+
const sources = source;
|
|
536
|
+
return _.flatten(_.map(sources, (_source) => this._getCertData(_source, unbundle)));
|
|
537
|
+
}
|
|
538
|
+
if (Buffer.isBuffer(source)) {
|
|
539
|
+
data = source.toString('utf8');
|
|
540
|
+
} else if (fs.existsSync(source)) {
|
|
541
|
+
data = fs.readFileSync(source, 'utf8');
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return data;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
_createCredentialContext(key, cert) {
|
|
548
|
+
const opts = {};
|
|
549
|
+
|
|
550
|
+
opts.key = this._getCertData(key);
|
|
551
|
+
opts.cert = this._getCertData(cert);
|
|
552
|
+
|
|
553
|
+
const credentials = tls.createSecureContext(opts);
|
|
554
|
+
return credentials.context;
|
|
555
|
+
}
|
|
556
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -7,16 +7,22 @@ const logger = require('@abtnode/logger')(`${require('../package.json').name}:pr
|
|
|
7
7
|
const debug = require('debug')(`${require('../package.json').name}:provider:index`);
|
|
8
8
|
|
|
9
9
|
const Nginx = require('./nginx');
|
|
10
|
-
const
|
|
10
|
+
const Default = require('./default');
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const providerMap = new Map([['default', Default]]);
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
]);
|
|
14
|
+
if (process.platform !== 'win32') {
|
|
15
|
+
providerMap.set('nginx', Nginx);
|
|
16
|
+
}
|
|
18
17
|
|
|
19
|
-
const getProviderNames = () =>
|
|
18
|
+
const getProviderNames = () =>
|
|
19
|
+
[...providerMap.keys()].sort((x) => {
|
|
20
|
+
if (x === 'nginx') {
|
|
21
|
+
return -1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return 1;
|
|
25
|
+
});
|
|
20
26
|
|
|
21
27
|
/**
|
|
22
28
|
* Get provider by name
|
|
@@ -37,22 +43,21 @@ const getProvider = (name) => {
|
|
|
37
43
|
const listProviders = async (configDir) =>
|
|
38
44
|
Promise.all(getProviderNames().map((x) => providerMap.get(x).describe({ configDir })));
|
|
39
45
|
|
|
40
|
-
const findExistsProvider =
|
|
41
|
-
for (const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
} catch (error) {
|
|
49
|
-
debug(error);
|
|
50
|
-
console.error(`provider ${key} exists check failed:`, error.message);
|
|
46
|
+
const findExistsProvider = () => {
|
|
47
|
+
for (const name of getProviderNames()) {
|
|
48
|
+
try {
|
|
49
|
+
const Provider = providerMap.get(name);
|
|
50
|
+
const exists = Provider.exists();
|
|
51
|
+
if (exists) {
|
|
52
|
+
return name;
|
|
51
53
|
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
debug(error);
|
|
56
|
+
console.error(`provider ${name} exists check failed:`, error.message);
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
return
|
|
60
|
+
return 'default';
|
|
56
61
|
};
|
|
57
62
|
|
|
58
63
|
const clearRouterByConfigKeyword = async (keyword) => {
|
|
@@ -64,10 +69,14 @@ const clearRouterByConfigKeyword = async (keyword) => {
|
|
|
64
69
|
debug('clear router by config directory prefix, config :', keyword);
|
|
65
70
|
for (const [key, Provider] of providerMap.entries()) {
|
|
66
71
|
const status = await Provider.getStatus(keyword);
|
|
67
|
-
if (status.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
if (status.managed) {
|
|
73
|
+
try {
|
|
74
|
+
await kill(status.pid);
|
|
75
|
+
debug('killed pid:', status.pid);
|
|
76
|
+
debug('killed router:', key);
|
|
77
|
+
} catch {
|
|
78
|
+
// do nothing
|
|
79
|
+
}
|
|
71
80
|
return key;
|
|
72
81
|
}
|
|
73
82
|
}
|
|
@@ -7,17 +7,8 @@ keepalive_timeout 30;
|
|
|
7
7
|
client_body_buffer_size 32k;
|
|
8
8
|
client_header_buffer_size 16k;
|
|
9
9
|
large_client_header_buffers 4 256k;
|
|
10
|
-
server_names_hash_bucket_size
|
|
10
|
+
server_names_hash_bucket_size 512;
|
|
11
11
|
|
|
12
12
|
## proxy
|
|
13
|
-
proxy_headers_hash_bucket_size 512;
|
|
14
|
-
proxy_buffer_size 10k;
|
|
15
|
-
proxy_next_upstream error timeout invalid_header;
|
|
16
13
|
real_ip_header X-Forwarded-For;
|
|
17
14
|
real_ip_recursive on;
|
|
18
|
-
proxy_redirect off;
|
|
19
|
-
proxy_http_version 1.1;
|
|
20
|
-
|
|
21
|
-
proxy_set_header Host $host;
|
|
22
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
23
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
package/lib/nginx/includes/proxy
CHANGED
|
@@ -15,3 +15,11 @@ proxy_set_header Upgrade $http_upgrade;
|
|
|
15
15
|
proxy_set_header Connection $connection_upgrade;
|
|
16
16
|
|
|
17
17
|
proxy_read_timeout 3600;
|
|
18
|
+
|
|
19
|
+
proxy_pass_header server;
|
|
20
|
+
|
|
21
|
+
proxy_headers_hash_bucket_size 512;
|
|
22
|
+
proxy_buffer_size 10k;
|
|
23
|
+
proxy_next_upstream error timeout invalid_header;
|
|
24
|
+
proxy_redirect off;
|
|
25
|
+
proxy_http_version 1.1;
|