@abtnode/router-provider 1.6.23 → 1.6.24
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 +316 -0
- package/lib/default/index.js +221 -0
- package/lib/default/proxy.js +567 -0
- package/lib/index.js +19 -19
- package/lib/nginx/index.js +115 -179
- package/lib/nginx/util.js +15 -56
- package/lib/util.js +165 -0
- package/package.json +17 -8
- package/lib/none/index.js +0 -50
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ const { getProvider } = require('router-provider');
|
|
|
9
9
|
const providerName = 'nginx';
|
|
10
10
|
const Provider = getProvider(providerName);
|
|
11
11
|
|
|
12
|
-
const provider = new Provider({
|
|
12
|
+
const provider = new Provider({ configDir });
|
|
13
13
|
provider.start();
|
|
14
14
|
provider.stop();
|
|
15
15
|
// ...
|
package/lib/base.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
|
|
1
3
|
class BaseProvider {
|
|
2
4
|
constructor(name) {
|
|
3
5
|
this.name = name;
|
|
@@ -34,6 +36,18 @@ class BaseProvider {
|
|
|
34
36
|
rotateLogs() {
|
|
35
37
|
throw new Error('rotateLogs method is not implemented');
|
|
36
38
|
}
|
|
39
|
+
|
|
40
|
+
getWorkerProcess(max = +process.env.ABT_NODE_MAX_CLUSTER_SIZE) {
|
|
41
|
+
if (this.isTest) {
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (process.env.NODE_ENV === 'production') {
|
|
46
|
+
return Math.min(os.cpus().length, max);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
module.exports = BaseProvider;
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
const url = require('url');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const get = require('lodash/get');
|
|
5
|
+
const joinUrl = require('url-join');
|
|
6
|
+
const checkDomainMatch = require('@abtnode/util/lib/check-domain-match');
|
|
7
|
+
const { DEFAULT_IP_DNS_DOMAIN_SUFFIX, ROUTING_RULE_TYPES } = require('@abtnode/constant');
|
|
8
|
+
|
|
9
|
+
const logger = require('@abtnode/logger')('router:default:daemon', { filename: 'engine' });
|
|
10
|
+
|
|
11
|
+
const { findCertificate, isSpecificDomain, toSlotDomain, matchRule } = require('../util');
|
|
12
|
+
const ProxyServer = require('./proxy');
|
|
13
|
+
|
|
14
|
+
const configPath = process.env.ABT_NODE_ROUTER_CONFIG;
|
|
15
|
+
const httpPort = +process.env.ABT_NODE_ROUTER_HTTP_PORT;
|
|
16
|
+
const httpsPort = +process.env.ABT_NODE_ROUTER_HTTPS_PORT;
|
|
17
|
+
const servicePort = +process.env.ABT_NODE_SERVICE_PORT;
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(configPath) === false) {
|
|
20
|
+
throw new Error('Router config file not found');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const wwwDir = path.join(path.dirname(configPath), 'www');
|
|
24
|
+
const fileIndex = fs.readFileSync(path.join(wwwDir, 'index.html')).toString();
|
|
25
|
+
const file404 = fs.readFileSync(path.join(wwwDir, '404.html')).toString();
|
|
26
|
+
const file5xx = fs.readFileSync(path.join(wwwDir, '5xx.html')).toString();
|
|
27
|
+
const file502 = fs.readFileSync(path.join(wwwDir, '502.html')).toString();
|
|
28
|
+
|
|
29
|
+
process.on('message', (msg) => {
|
|
30
|
+
if (msg.data === 'reload') {
|
|
31
|
+
logger.info('update config', msg);
|
|
32
|
+
updateConfig(true);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const config = { sites: [], rules: {}, certs: [], headers: [] };
|
|
37
|
+
|
|
38
|
+
// eslint-disable-next-line
|
|
39
|
+
const createRequestHandler = (id) => (req, res, target) => {
|
|
40
|
+
const rule = config.rules[id];
|
|
41
|
+
|
|
42
|
+
if (rule.type === ROUTING_RULE_TYPES.DIRECT_RESPONSE) {
|
|
43
|
+
if (rule.response.contentType) {
|
|
44
|
+
res.writeHead(rule.response.status, { 'Content-Type': rule.response.contentType });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
res.end(rule.response.body);
|
|
48
|
+
|
|
49
|
+
return { abort: true };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Note: redirection required for relative asset loading
|
|
53
|
+
if (req.method === 'GET' && rule.prefix !== '/') {
|
|
54
|
+
const parsed = url.parse(req.url);
|
|
55
|
+
if (parsed.pathname === rule.prefix) {
|
|
56
|
+
logger.info('redirect url', { url: req.url, rule });
|
|
57
|
+
parsed.pathname = joinUrl(rule.prefix, '/');
|
|
58
|
+
res.writeHead(307, { Location: url.format(parsed) });
|
|
59
|
+
res.end();
|
|
60
|
+
return { abort: true };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (rule.type === ROUTING_RULE_TYPES.GENERAL_PROXY) {
|
|
65
|
+
// we do not need rewrite for internal servers
|
|
66
|
+
} else {
|
|
67
|
+
req.url = joinUrl(rule.target, req.url.substr(rule.prefix.length));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (req.url.startsWith('/') === false) {
|
|
71
|
+
req.url = `/${req.url}`;
|
|
72
|
+
}
|
|
73
|
+
logger.debug('transform url', { url: req.url, rule });
|
|
74
|
+
|
|
75
|
+
if (rule.did) {
|
|
76
|
+
req.headers['X-Blocklet-Did'] = rule.did;
|
|
77
|
+
}
|
|
78
|
+
if (rule.realDid && rule.did !== rule.realDid) {
|
|
79
|
+
req.headers['X-Blocklet-Real-Did'] = rule.realDid;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
req.headers['X-Path-Prefix'] = rule.prefix;
|
|
83
|
+
req.headers['X-Group-Path-Prefix'] = rule.groupPrefix || '/';
|
|
84
|
+
|
|
85
|
+
if (rule.services.length) {
|
|
86
|
+
req.headers['X-Blocklet-Url'] = `http://127.0.0.1:${rule.port}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (rule.ruleId) {
|
|
90
|
+
req.headers['X-Routing-Rule-Id'] = rule.ruleId;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// eslint-disable-next-line
|
|
95
|
+
const sharedResolver = (host, url, req) => {
|
|
96
|
+
// match slot domain
|
|
97
|
+
const domain = toSlotDomain(host);
|
|
98
|
+
|
|
99
|
+
// match specific domain
|
|
100
|
+
let site = config.sites.find((x) => x.domain === domain);
|
|
101
|
+
|
|
102
|
+
// match wildcard domain
|
|
103
|
+
if (!site) {
|
|
104
|
+
site = config.sites.find((x) => checkDomainMatch(x.domain, domain));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// fallback to default domain
|
|
108
|
+
if (!site) {
|
|
109
|
+
site = config.sites.find((x) => x.domain === '_');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const rule = matchRule(site.rules, url);
|
|
113
|
+
if (!rule) {
|
|
114
|
+
logger.warn('rule not found for request', { host, url, domain });
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const match = findCertificate(config.certs, domain);
|
|
119
|
+
const upstream = `http://127.0.0.1:${rule.services.length ? servicePort : rule.port}`;
|
|
120
|
+
logger.debug('resolved request', { host, url, domain, upstream, rule });
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
id: rule.id,
|
|
124
|
+
url: upstream,
|
|
125
|
+
path: '/',
|
|
126
|
+
opts: {
|
|
127
|
+
ssl: match ? { key: match.privateKey, cert: match.certificate } : null,
|
|
128
|
+
onRequest: createRequestHandler(rule.id),
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
sharedResolver.priority = 200;
|
|
133
|
+
|
|
134
|
+
const onError = (err, req, res) => {
|
|
135
|
+
logger.error('proxy error', { error: err });
|
|
136
|
+
if (typeof res.writeHead !== 'function') {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (err.code === 'ECONNREFUSED') {
|
|
141
|
+
res.writeHead(502);
|
|
142
|
+
res.end(file502);
|
|
143
|
+
} else if (err.code === 'NOTFOUND') {
|
|
144
|
+
const parsed = url.parse(req.url);
|
|
145
|
+
if (config.info.enableWelcomePage && parsed.pathname === '/') {
|
|
146
|
+
res.writeHead(404);
|
|
147
|
+
res.end(fileIndex);
|
|
148
|
+
} else {
|
|
149
|
+
res.writeHead(404);
|
|
150
|
+
res.end(file404);
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
res.writeHead(500);
|
|
154
|
+
res.end(file5xx);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const corsHandler = (host, req, res) => {
|
|
159
|
+
if (req.method === 'OPTIONS') {
|
|
160
|
+
const domain = toSlotDomain(host);
|
|
161
|
+
const site = config.sites.find((x) => x.domain === domain);
|
|
162
|
+
if (!site) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const allowedOrigins = site.corsAllowedOrigins;
|
|
167
|
+
const currentOrigin = req.headers.origin;
|
|
168
|
+
if (allowedOrigins.includes('*')) {
|
|
169
|
+
res.writeHead(204, {
|
|
170
|
+
Vary: 'Origin',
|
|
171
|
+
'Access-Control-Allow-Origin': '*',
|
|
172
|
+
'Access-Control-Allow-Credentials': false,
|
|
173
|
+
'Access-Control-Allow-Methods': 'POST, GET, HEAD, PUT, DELETE, OPTIONS',
|
|
174
|
+
'Access-Control-Allow-Headers':
|
|
175
|
+
'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,$http_access_control_request_headers',
|
|
176
|
+
'Access-Control-Max-Age': 1800,
|
|
177
|
+
});
|
|
178
|
+
res.end();
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (allowedOrigins.some((x) => checkDomainMatch(x, currentOrigin))) {
|
|
183
|
+
res.writeHead(204, {
|
|
184
|
+
Vary: 'Origin',
|
|
185
|
+
'Access-Control-Allow-Origin': currentOrigin,
|
|
186
|
+
'Access-Control-Allow-Credentials': false,
|
|
187
|
+
'Access-Control-Allow-Methods': 'POST, GET, HEAD, PUT, DELETE, OPTIONS',
|
|
188
|
+
'Access-Control-Allow-Headers':
|
|
189
|
+
'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,$http_access_control_request_headers',
|
|
190
|
+
'Access-Control-Max-Age': 1800,
|
|
191
|
+
});
|
|
192
|
+
res.end();
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return true;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// internal servers
|
|
201
|
+
const internalServers = {};
|
|
202
|
+
const ensureInternalServer = (port) => {
|
|
203
|
+
if (internalServers[port]) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const server = new ProxyServer({ xfwd: false, internal: true, port, headers: config.headers, onError, corsHandler });
|
|
208
|
+
server.addResolver(sharedResolver);
|
|
209
|
+
internalServers[port] = server;
|
|
210
|
+
logger.info('internal server ready on port', { port });
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const updateConfig = (reload = false) => {
|
|
214
|
+
const { sites, certificates: rawCerts, info } = fs.readJsonSync(configPath);
|
|
215
|
+
config.info = info;
|
|
216
|
+
|
|
217
|
+
const obj = get(info, 'routing.headers', {});
|
|
218
|
+
config.headers = Object.keys(obj).map((key) => ({ key, value: JSON.parse(obj[key]) }));
|
|
219
|
+
|
|
220
|
+
// format certificates
|
|
221
|
+
config.certs = rawCerts.map((x) => {
|
|
222
|
+
x.privateKey = Buffer.from(x.privateKey);
|
|
223
|
+
x.certificate = Buffer.from(x.certificate);
|
|
224
|
+
return x;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// format sites
|
|
228
|
+
sites.forEach((site) => {
|
|
229
|
+
site.rules.forEach((rule) => {
|
|
230
|
+
config.rules[rule.id] = rule;
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// re-register sites in reload mode
|
|
235
|
+
if (reload) {
|
|
236
|
+
const oldDomains = config.sites.map((x) => x.domain);
|
|
237
|
+
const newDomains = sites.map((x) => x.domain);
|
|
238
|
+
|
|
239
|
+
const addedDomains = newDomains.filter((x) => !oldDomains.includes(x));
|
|
240
|
+
for (const domain of addedDomains) {
|
|
241
|
+
if (isSpecificDomain(domain)) {
|
|
242
|
+
logger.info('register domain on reload', { domain });
|
|
243
|
+
const site = sites.find((x) => x.domain === domain);
|
|
244
|
+
const [rule] = site.rules.filter((x) => ['daemon', 'blocklet'].includes(x.type));
|
|
245
|
+
const match = findCertificate(config.certs, site.domain);
|
|
246
|
+
main.register({
|
|
247
|
+
src: site.domain,
|
|
248
|
+
target: `http://127.0.0.1:${rule.port}`,
|
|
249
|
+
ssl: match ? { key: match.privateKey, cert: match.certificate } : null,
|
|
250
|
+
onRequest: createRequestHandler(rule.id),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const removedDomains = oldDomains.filter((x) => !newDomains.includes(x));
|
|
256
|
+
for (const domain of removedDomains) {
|
|
257
|
+
main.unregister(domain);
|
|
258
|
+
logger.info('unregister domain on reload', { domain });
|
|
259
|
+
delete main.routing[domain];
|
|
260
|
+
logger.info('remove domain cache on reload', { domain });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
config.sites.filter((x) => x.port).forEach((x) => ensureInternalServer(x.port));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
config.sites = sites;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// load initial config
|
|
270
|
+
updateConfig();
|
|
271
|
+
|
|
272
|
+
// create main server
|
|
273
|
+
const defaultCert = config.certs.find((x) => x.domain.endsWith(DEFAULT_IP_DNS_DOMAIN_SUFFIX));
|
|
274
|
+
const main = new ProxyServer({
|
|
275
|
+
xfwd: true,
|
|
276
|
+
onError,
|
|
277
|
+
corsHandler,
|
|
278
|
+
port: httpPort,
|
|
279
|
+
headers: config.headers,
|
|
280
|
+
ssl: {
|
|
281
|
+
port: httpsPort,
|
|
282
|
+
key: defaultCert ? defaultCert.privateKey : null,
|
|
283
|
+
cert: defaultCert ? defaultCert.certificate : null,
|
|
284
|
+
opts: {
|
|
285
|
+
honorCipherOrder: false,
|
|
286
|
+
maxVersion: 'TLSv1.3',
|
|
287
|
+
minVersion: 'TLSv1.2',
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
main.sites = config.sites;
|
|
292
|
+
main.addResolver(sharedResolver);
|
|
293
|
+
|
|
294
|
+
// register sites: to ensure https certificates are loaded
|
|
295
|
+
config.sites.forEach((site) => {
|
|
296
|
+
const { domain } = site;
|
|
297
|
+
if (isSpecificDomain(domain) === false) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const [rule] = site.rules.filter((x) => ['daemon', 'blocklet'].includes(x.type));
|
|
302
|
+
const match = findCertificate(config.certs, domain);
|
|
303
|
+
|
|
304
|
+
logger.info('register domain on start', { domain });
|
|
305
|
+
main.register({
|
|
306
|
+
src: domain,
|
|
307
|
+
target: `http://127.0.0.1:${rule.port}`,
|
|
308
|
+
ssl: match ? { key: match.privateKey, cert: match.certificate } : null,
|
|
309
|
+
onRequest: createRequestHandler(rule.id),
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// initialize internal servers
|
|
314
|
+
config.sites.filter((x) => x.port).forEach((x) => ensureInternalServer(x.port));
|
|
315
|
+
|
|
316
|
+
logger.info(`Default routing engine ready on ${httpPort} and ${httpsPort}`);
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const shelljs = require('shelljs');
|
|
4
|
+
const get = require('lodash/get');
|
|
5
|
+
const omit = require('lodash/omit');
|
|
6
|
+
const sortBy = require('lodash/sortBy');
|
|
7
|
+
const objectHash = require('object-hash');
|
|
8
|
+
const promiseRetry = require('promise-retry');
|
|
9
|
+
const pm2 = require('@abtnode/util/lib/async-pm2');
|
|
10
|
+
const { PROCESS_NAME_ROUTER, DAEMON_MAX_MEM_LIMIT_IN_MB } = require('@abtnode/constant');
|
|
11
|
+
|
|
12
|
+
const logger = require('@abtnode/logger')('router:default:controller', { filename: 'engine' });
|
|
13
|
+
|
|
14
|
+
const BaseProvider = require('../base');
|
|
15
|
+
const {
|
|
16
|
+
decideHttpPort,
|
|
17
|
+
decideHttpsPort,
|
|
18
|
+
get404Template,
|
|
19
|
+
get502Template,
|
|
20
|
+
get5xxTemplate,
|
|
21
|
+
getWelcomeTemplate,
|
|
22
|
+
formatRoutingTable,
|
|
23
|
+
} = require('../util');
|
|
24
|
+
|
|
25
|
+
class DefaultProvider extends BaseProvider {
|
|
26
|
+
constructor({ configDir, httpPort, httpsPort, isTest }) {
|
|
27
|
+
super('default');
|
|
28
|
+
|
|
29
|
+
this.isTest = !!isTest;
|
|
30
|
+
this.configDir = configDir;
|
|
31
|
+
|
|
32
|
+
this.logDir = path.join(this.configDir, 'log');
|
|
33
|
+
this.wwwDir = path.join(this.configDir, 'www');
|
|
34
|
+
|
|
35
|
+
this.engineLog = path.join(this.logDir, 'engine.log'); // should be symbol link created by winston
|
|
36
|
+
this.errorLog = path.join(this.logDir, 'engine-error.log'); // should be symbol link created by winston
|
|
37
|
+
this.ctrlLog = path.join(this.logDir, 'controller.log'); // managed by pm2
|
|
38
|
+
|
|
39
|
+
this.configPath = path.join(this.configDir, 'config.json');
|
|
40
|
+
|
|
41
|
+
this.httpPort = decideHttpPort(httpPort);
|
|
42
|
+
this.httpsPort = decideHttpsPort(httpsPort);
|
|
43
|
+
|
|
44
|
+
// ensure directories
|
|
45
|
+
[this.configDir, this.logDir, this.wwwDir].forEach((dir) => {
|
|
46
|
+
if (!fs.existsSync(dir)) {
|
|
47
|
+
fs.mkdirSync(dir);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.initialize();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Services are not supported by default provider
|
|
55
|
+
update({ routingTable = [], certificates = [], nodeInfo = {} } = {}) {
|
|
56
|
+
this.info = nodeInfo;
|
|
57
|
+
|
|
58
|
+
logger.info('routing table:', routingTable);
|
|
59
|
+
this._addWwwFiles(nodeInfo);
|
|
60
|
+
const { sites: rawSites } = formatRoutingTable(routingTable);
|
|
61
|
+
const sites = sortBy(rawSites, (x) => -x.domain.length);
|
|
62
|
+
sites.forEach((site) => {
|
|
63
|
+
site.rules.forEach((rule) => {
|
|
64
|
+
// NOTE: we generate uniq hash from rule, because there are duplicate ruleId across different rules
|
|
65
|
+
rule.id = objectHash(rule);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
fs.outputJSONSync(this.configPath, { sites, certificates, info: this.info }, { spaces: 2 });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async reload() {
|
|
73
|
+
await pm2.connectAsync();
|
|
74
|
+
|
|
75
|
+
// ensure daemon is live
|
|
76
|
+
let [info] = await pm2.describeAsync(PROCESS_NAME_ROUTER);
|
|
77
|
+
if (!info) {
|
|
78
|
+
await this.start();
|
|
79
|
+
[info] = await pm2.describeAsync(PROCESS_NAME_ROUTER);
|
|
80
|
+
logger.info('start daemon because not running', omit(info, ['pm2_env']));
|
|
81
|
+
} else {
|
|
82
|
+
logger.info('daemon already running', omit(info, ['pm2_env']));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// send signal to daemon
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
pm2.sendDataToProcessId(info.pm2_env.pm_id, { type: 'process:msg', data: 'reload', topic: 'reload' }, (err) => {
|
|
88
|
+
if (err) {
|
|
89
|
+
logger.error('failed to reload', { error: err });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pm2.disconnect(resolve);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async start() {
|
|
98
|
+
await pm2.connectAsync();
|
|
99
|
+
await pm2.startAsync({
|
|
100
|
+
namespace: 'daemon',
|
|
101
|
+
name: PROCESS_NAME_ROUTER,
|
|
102
|
+
script: path.join(__dirname, 'daemon.js'),
|
|
103
|
+
max_memory_restart: `${get(this.info, 'runtimeConfig.daemonMaxMemoryLimit', DAEMON_MAX_MEM_LIMIT_IN_MB)}M`,
|
|
104
|
+
output: this.ctrlLog,
|
|
105
|
+
error: this.ctrlLog,
|
|
106
|
+
cwd: __dirname,
|
|
107
|
+
max_restarts: 1,
|
|
108
|
+
time: true,
|
|
109
|
+
mergeLogs: true,
|
|
110
|
+
execMode: 'fork', // Note: there are some issues with reloading when run in cluster mode
|
|
111
|
+
env: {
|
|
112
|
+
ABT_NODE_LOG_DIR: this.logDir, // Note: this tells logger module to write log to router log
|
|
113
|
+
ABT_NODE_ROUTER_CONFIG: this.configPath,
|
|
114
|
+
ABT_NODE_ROUTER_HTTP_PORT: this.httpPort,
|
|
115
|
+
ABT_NODE_ROUTER_HTTPS_PORT: this.httpsPort,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async restart() {
|
|
121
|
+
await pm2.connectAsync();
|
|
122
|
+
await pm2.restartAsync(PROCESS_NAME_ROUTER, { updateEnv: true });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async stop() {
|
|
126
|
+
await pm2.connectAsync();
|
|
127
|
+
const [info] = await pm2.describeAsync(PROCESS_NAME_ROUTER);
|
|
128
|
+
if (!info) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const proc = await pm2.deleteAsync(PROCESS_NAME_ROUTER);
|
|
133
|
+
return proc;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
initialize() {}
|
|
137
|
+
|
|
138
|
+
validateConfig() {}
|
|
139
|
+
|
|
140
|
+
rotateLogs() {}
|
|
141
|
+
|
|
142
|
+
_addWwwFiles(nodeInfo) {
|
|
143
|
+
const welcomePage = nodeInfo.enableWelcomePage ? getWelcomeTemplate(nodeInfo) : get404Template(nodeInfo);
|
|
144
|
+
fs.writeFileSync(`${this.wwwDir}/index.html`, welcomePage); // disable index.html
|
|
145
|
+
fs.writeFileSync(`${this.wwwDir}/404.html`, get404Template(nodeInfo));
|
|
146
|
+
fs.writeFileSync(`${this.wwwDir}/502.html`, get502Template(nodeInfo));
|
|
147
|
+
fs.writeFileSync(`${this.wwwDir}/5xx.html`, get5xxTemplate(nodeInfo));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getLogFilesForToday() {
|
|
151
|
+
return {
|
|
152
|
+
access: fs.realpathSync(this.engineLog),
|
|
153
|
+
error: fs.realpathSync(this.errorLog),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
DefaultProvider.describe = async ({ configDir = '' } = {}) => {
|
|
159
|
+
const meta = {
|
|
160
|
+
name: 'default',
|
|
161
|
+
description: 'Use default to provide a lightweight routing layer for development purpose',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Use retry as a workaround for race-conditions between check and normal start
|
|
166
|
+
const result = await promiseRetry((retry) => DefaultProvider.check({ configDir }).catch(retry), { retries: 3 });
|
|
167
|
+
return { ...meta, ...result };
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return { ...meta, error: err.message, available: false, running: false };
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const getRouterStatus = async (configDir) => {
|
|
174
|
+
const result = {
|
|
175
|
+
managed: false,
|
|
176
|
+
pid: 0,
|
|
177
|
+
running: false,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
await pm2.connectAsync();
|
|
181
|
+
|
|
182
|
+
const [info] = await pm2.describeAsync(PROCESS_NAME_ROUTER);
|
|
183
|
+
if (!info) {
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
result.running = true;
|
|
188
|
+
if (info.pm2_env.ABT_NODE_ROUTER_CONFIG.indexOf(configDir) > -1) {
|
|
189
|
+
result.managed = true;
|
|
190
|
+
result.pid = info.pid;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return result;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
DefaultProvider.exists = () => true;
|
|
197
|
+
DefaultProvider.getStatus = getRouterStatus;
|
|
198
|
+
DefaultProvider.check = async ({ configDir = '' } = {}) => {
|
|
199
|
+
logger.info('check default provider', { configDir });
|
|
200
|
+
const binPath = shelljs.which('node').stdout;
|
|
201
|
+
const result = {
|
|
202
|
+
binPath,
|
|
203
|
+
available: true,
|
|
204
|
+
running: false,
|
|
205
|
+
managed: false,
|
|
206
|
+
error: '',
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const status = await getRouterStatus(configDir);
|
|
210
|
+
result.managed = status.managed;
|
|
211
|
+
result.running = status.running;
|
|
212
|
+
|
|
213
|
+
if (status.running && !status.managed) {
|
|
214
|
+
result.available = false;
|
|
215
|
+
result.error = 'Seems the default routing engine is running, please terminate the process before try again.';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return result;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
module.exports = DefaultProvider;
|