@abtnode/router-provider 1.15.17 → 1.16.0-beta-b16cb035

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.
@@ -10,75 +10,86 @@ const getPort = require('get-port');
10
10
  const uniqBy = require('lodash/uniqBy');
11
11
  const isEmpty = require('lodash/isEmpty');
12
12
  const cloneDeep = require('lodash/cloneDeep');
13
- const normalizePathPrefix = require('@abtnode/util/lib/normalize-path-prefix');
13
+ const formatBackSlash = require('@abtnode/util/lib/format-back-slash');
14
14
  const {
15
15
  DOMAIN_FOR_DEFAULT_SITE,
16
16
  ROUTING_RULE_TYPES,
17
17
  CONFIG_FOLDER_NAME,
18
18
  DEFAULT_ADMIN_PATH,
19
19
  SLOT_FOR_IP_DNS_SITE,
20
- WELLKNOWN_AUTH_PATH_PREFIX,
20
+ WELLKNOWN_SERVICE_PATH_PREFIX,
21
+ USER_AVATAR_PATH_PREFIX,
22
+ LOG_RETAIN_IN_DAYS,
23
+ ROUTER_CACHE_GROUPS,
21
24
  } = require('@abtnode/constant');
22
25
  const md5 = require('@abtnode/util/lib/md5');
23
26
 
24
27
  const promiseRetry = require('promise-retry');
25
28
 
26
- const logger = require('@abtnode/logger')(`${require('../../package.json').name}:provider:nginx`);
29
+ const logger = require('@abtnode/logger')('router:nginx:controller');
27
30
 
28
31
  const BaseProvider = require('../base');
29
- const util = require('./util');
30
32
  const {
31
33
  addTestServer,
32
- decideHttpPort,
33
- decideHttpsPort,
34
34
  formatError,
35
35
  getNginxLoadModuleDirectives,
36
+ parseNginxConfigArgs,
37
+ getNginxStatus,
38
+ rotateNginxLogFile,
39
+ getMissingModules,
40
+ getMainTemplate,
41
+ getUpstreamName,
42
+ joinNginxPath,
43
+ } = require('./util');
44
+ const {
45
+ decideHttpPort,
46
+ decideHttpsPort,
47
+ getUsablePorts,
36
48
  get404Template,
37
49
  get502Template,
38
50
  get5xxTemplate,
39
51
  getWelcomeTemplate,
40
- parseNginxConfigArgs,
41
52
  trimEndSlash,
42
53
  concatPath,
43
54
  findCertificate,
44
- getNginxStatus,
45
- rotateNginxLogFile,
46
- getMissingModules,
47
- } = require('./util');
55
+ formatRoutingTable,
56
+ } = require('../util');
48
57
 
49
58
  const REQUIRED_MODULES = [{ configName: 'with-stream', moduleBinaryName: 'ngx_stream_module.so' }];
50
59
 
51
60
  // TODO: move the did-auth-path-prefix to a constant
52
- const DID_AUTH_PATH_PREFIX = `${DEFAULT_ADMIN_PATH}/api/did`;
61
+ const ADMIN_DID_AUTH_PATH_PREFIX = `${DEFAULT_ADMIN_PATH}/api/did`;
53
62
 
54
63
  // convert wildcard domain and ipDnsDomain template to regex
55
- const parseServerName = (serverName) => {
64
+ const parseServerName = (domain) => {
56
65
  // ipDnsDomain template
57
- if (serverName.includes(SLOT_FOR_IP_DNS_SITE)) {
58
- const name = serverName.replace(/\./g, '\\.').replace(SLOT_FOR_IP_DNS_SITE, '\\d+-\\d+-\\d+-\\d+');
66
+ if (domain.includes(SLOT_FOR_IP_DNS_SITE)) {
67
+ const name = domain.replace(/\./g, '\\.').replace(SLOT_FOR_IP_DNS_SITE, '\\d+-\\d+-\\d+-\\d+');
59
68
  return `~^${name}$`;
60
69
  }
61
70
 
62
71
  // wildcard domain
63
- if (serverName.startsWith('*.')) {
64
- const name = serverName.replace('*', '').replace(/\./g, '\\.');
72
+ if (domain.startsWith('*.')) {
73
+ const name = domain.replace('*', '').replace(/\./g, '\\.');
65
74
  return `~.+${name}$`;
66
75
  }
67
76
 
68
- return serverName;
77
+ return domain;
69
78
  };
70
79
 
71
80
  class NginxProvider extends BaseProvider {
72
81
  /**
73
- * @param {string} configDirectory
74
- * @param {number} httpPort
75
- * @param {number} httpPort
76
- * @param {number} cacheDisabled
82
+ * @param {object} options
83
+ * @param {string} options.configDir
84
+ * @param {number} options.httpPort
85
+ * @param {number} options.httpsPort
86
+ * @param {boolean} [options.cacheEnabled]
87
+ * @param {boolean} [options.isTest]
77
88
  */
78
- constructor({ configDirectory, httpPort, httpsPort, cacheDisabled, isTest }) {
79
- super('nginx-provider');
80
- if (!configDirectory) {
81
- throw new Error('invalid configDirectory');
89
+ constructor({ configDir, httpPort, httpsPort, cacheEnabled = true, isTest = false }) {
90
+ super('nginx');
91
+ if (!configDir) {
92
+ throw new Error('invalid configDir');
82
93
  }
83
94
 
84
95
  if (shelljs.which('nginx')) {
@@ -88,30 +99,33 @@ class NginxProvider extends BaseProvider {
88
99
  }
89
100
 
90
101
  this.isTest = !!isTest;
91
- this.configDirectory = configDirectory;
92
- this.logDirectory = path.join(this.configDirectory, 'log');
93
- this.accessLogFile = path.join(this.logDirectory, 'access.log');
94
- this.errorLogFile = path.join(this.logDirectory, 'error.log');
95
- this.tmpDirectory = path.join(this.configDirectory, 'tmp');
96
- this.certificateDirectory = path.join(this.configDirectory, 'certs');
97
- this.includesDirectory = path.join(this.configDirectory, 'includes');
98
- this.wwwDirectory = path.join(this.configDirectory, 'www');
99
-
100
- this.configFilePath = path.join(this.configDirectory, 'nginx.conf');
102
+ this.configDir = configDir;
103
+ this.logDir = path.join(this.configDir, 'log');
104
+ this.accessLog = path.join(this.logDir, 'access.log');
105
+ this.errorLog = path.join(this.logDir, 'error.log');
106
+ this.tmpDir = path.join(this.configDir, 'tmp');
107
+ this.certDir = path.join(this.configDir, 'certs');
108
+ this.cacheDir = path.join(this.configDir, 'cache');
109
+ this.includesDir = path.join(this.configDir, 'includes');
110
+ this.wwwDir = path.join(this.configDir, 'www');
111
+
112
+ this.configPath = path.join(this.configDir, 'nginx.conf');
101
113
 
102
114
  this.httpPort = decideHttpPort(httpPort);
103
115
  this.httpsPort = decideHttpsPort(httpsPort);
104
- this.cacheDisabled = !!cacheDisabled;
116
+ this.cacheEnabled = !!cacheEnabled;
117
+ this.isHttp2Supported = this._isHttp2Supported();
118
+ this.conf = null; // nginx `conf` object
105
119
 
106
120
  logger.info('nginx provider config', {
107
- configDirectory,
121
+ configDir,
108
122
  httpPort: this.httpPort,
109
123
  httpsPort: this.httpsPort,
110
- cacheDisabled: this.cacheDisabled,
124
+ cacheEnabled: this.cacheEnabled,
111
125
  });
112
126
 
113
127
  // ensure directories
114
- [this.configDirectory, this.logDirectory, this.tmpDirectory, this.certificateDirectory].forEach((dir) => {
128
+ [this.configDir, this.logDir, this.cacheDir, this.tmpDir, this.certDir].forEach((dir) => {
115
129
  if (!fs.existsSync(dir)) {
116
130
  fs.mkdirSync(dir);
117
131
  }
@@ -122,23 +136,46 @@ class NginxProvider extends BaseProvider {
122
136
  this.initialize();
123
137
  }
124
138
 
125
- async update({ routingTable = [], certificates = [], globalHeaders, services = [], nodeInfo = {} } = {}) {
126
- logger.info('routing table:', routingTable);
139
+ static isDidAuthPrefix(prefix) {
140
+ return prefix === ADMIN_DID_AUTH_PATH_PREFIX;
141
+ }
142
+
143
+ getRelativeConfigDir(dir) {
144
+ return path.relative(this.configDir, dir);
145
+ }
146
+
147
+ getConfTemplate() {
148
+ return getMainTemplate({
149
+ logDir: this.getRelativeConfigDir(formatBackSlash(this.logDir)),
150
+ tmpDir: this.getRelativeConfigDir(formatBackSlash(this.tmpDir)),
151
+ cacheDir: this.getRelativeConfigDir(formatBackSlash(this.cacheDir)),
152
+ workerProcess: this.getWorkerProcess(),
153
+ nginxLoadModules: getNginxLoadModuleDirectives(REQUIRED_MODULES, this.readNginxConfigParams()).join(os.EOL),
154
+ });
155
+ }
127
156
 
157
+ async update({
158
+ routingTable = [],
159
+ certificates = [],
160
+ commonHeaders,
161
+ services = [],
162
+ nodeInfo = {},
163
+ requestLimit,
164
+ cacheEnabled,
165
+ } = {}) {
128
166
  if (!Array.isArray(routingTable)) {
129
167
  throw new Error('routingTable must be an array');
130
168
  }
131
169
 
170
+ if (typeof cacheEnabled !== 'undefined') {
171
+ this.cacheEnabled = !!cacheEnabled;
172
+ }
173
+
132
174
  this._addWwwFiles(nodeInfo);
133
175
 
134
176
  // eslint-disable-next-line consistent-return
135
177
  return new Promise((resolve, reject) => {
136
- const confTemplate = util.getMainTemplate({
137
- logDir: this.logDirectory.replace(this.configDirectory, '').replace(/^\//, ''),
138
- tmpDirectory: this.tmpDirectory.replace(this.configDirectory, '').replace(/^\//, ''),
139
- workerProcess: this.getWorkerProcess(),
140
- nginxLoadModules: getNginxLoadModuleDirectives(REQUIRED_MODULES, this.readNginxConfigParams()).join(os.EOL),
141
- });
178
+ const confTemplate = this.getConfTemplate();
142
179
 
143
180
  NginxConfFile.createFromSource(confTemplate, (err, conf) => {
144
181
  if (err) {
@@ -147,91 +184,47 @@ class NginxProvider extends BaseProvider {
147
184
  return;
148
185
  }
149
186
 
150
- conf.on('flushed', () => resolve());
151
-
152
- conf.live(this.configFilePath);
187
+ this.conf = conf;
153
188
 
154
- let addedDefaultServer = false;
155
- const rulesMap = new Map();
156
- const siteCorsConfigs = [];
157
-
158
- routingTable.forEach((site) => {
159
- let serverName = site.domain;
160
- if (site.domain === DOMAIN_FOR_DEFAULT_SITE) {
161
- serverName = '_';
162
- addedDefaultServer = true;
163
- }
164
-
165
- siteCorsConfigs.push({ serverName, corsAllowedOrigins: site.corsAllowedOrigins });
166
-
167
- if (!rulesMap.has(serverName)) {
168
- rulesMap.set(serverName, {
169
- rules: [],
170
- port: site.port,
171
- corsAllowedOrigins: site.corsAllowedOrigins,
172
- });
189
+ conf.on('flushed', () => resolve());
190
+ conf.live(this.configPath);
191
+
192
+ const { sites, configs: siteCorsConfigs } = formatRoutingTable(routingTable, (rules, rule) => {
193
+ // TODO: this is really hacky, and should be abstracted out
194
+ if (rule.type === ROUTING_RULE_TYPES.DAEMON && rule.prefix === DEFAULT_ADMIN_PATH) {
195
+ const clonedRule = cloneDeep(rule);
196
+ clonedRule.prefix = ADMIN_DID_AUTH_PATH_PREFIX;
197
+ rules.push(clonedRule);
173
198
  }
174
-
175
- (site.rules || []).forEach((x) => {
176
- const prefix = trimEndSlash(x.from.pathPrefix || '/');
177
- const groupPrefix = trimEndSlash(x.from.groupPathPrefix || '/');
178
- const suffix = trimEndSlash(x.from.pathSuffix || '');
179
-
180
- const rule = {
181
- ruleId: x.id,
182
- type: x.to.type,
183
- prefix,
184
- groupPrefix,
185
- suffix,
186
- };
187
- if (x.to.type === ROUTING_RULE_TYPES.REDIRECT) {
188
- rule.redirectCode = x.to.redirectCode || 302;
189
- rule.url = x.to.url;
190
- } else {
191
- rule.port = x.to.port;
192
- rule.did = x.to.did;
193
- rule.realDid = x.to.realDid;
194
- rule.target = trimEndSlash(normalizePathPrefix(x.to.target || '/'));
195
- rule.services = x.services || [];
196
- }
197
-
198
- const tmpPath = concatPath(prefix, suffix);
199
- const tmpRules = rulesMap.get(serverName).rules;
200
- if (!tmpRules.find(({ prefix: p, suffix: s }) => concatPath(p, s) === tmpPath)) {
201
- rulesMap.get(serverName).rules.push(rule);
202
-
203
- if (rule.type === ROUTING_RULE_TYPES.DAEMON && rule.prefix === DEFAULT_ADMIN_PATH) {
204
- const clonedRule = cloneDeep(rule);
205
- clonedRule.prefix = DID_AUTH_PATH_PREFIX;
206
- rulesMap.get(serverName).rules.push(clonedRule);
207
- }
208
- }
209
- });
210
199
  });
211
200
 
212
- logger.debug('rule map:', { rulesMap });
213
-
214
201
  this._addCorsMap(conf, siteCorsConfigs);
215
-
216
- // disable server version
217
202
  conf.nginx.http._add('server_tokens', 'off');
218
- this._addGlobalHeaders(conf, globalHeaders);
219
-
203
+ this._addCommonResHeaders(conf.nginx.http, commonHeaders);
220
204
  this._addExposeServices(conf, services);
205
+ if (requestLimit && requestLimit.enabled) {
206
+ this.addGlobalReqLimit(conf.nginx.http, requestLimit);
207
+ }
208
+
209
+ const allRules = sites.reduce((acc, site) => {
210
+ acc.push(...(site.rules || []));
211
+ return acc;
212
+ }, []);
213
+
214
+ this.ensureUpstreamServers(allRules);
221
215
 
222
216
  // eslint-disable-next-line no-restricted-syntax
223
- for (const [serverName, { rules: locations, corsAllowedOrigins, port }] of rulesMap) {
224
- const certificate = findCertificate(certificates, serverName);
225
- const isHttps = !!certificate;
217
+ for (const site of sites) {
218
+ const { domain, port, rules, corsAllowedOrigins, blockletDid } = site;
219
+ const certificate = findCertificate(certificates, domain);
226
220
 
227
- const parsedServerName = parseServerName(serverName);
228
- const sortedLocations = this._sortLocations(locations);
229
- if (isHttps) {
221
+ const parsedServerName = parseServerName(domain);
222
+ if (certificate) {
230
223
  // HTTPS configurations
231
224
  // update all certs to disk
232
225
  certificates.forEach((item) => {
233
- const crtPath = `${path.join(this.certificateDirectory, item.domain)}.crt`;
234
- const keyPath = `${path.join(this.certificateDirectory, item.domain)}.key`;
226
+ const crtPath = `${path.join(this.certDir, item.domain.replace('*', '-'))}.crt`;
227
+ const keyPath = `${path.join(this.certDir, item.domain.replace('*', '-'))}.key`;
235
228
  fs.writeFileSync(crtPath, item.certificate);
236
229
  fs.writeFileSync(keyPath, item.privateKey);
237
230
  });
@@ -239,25 +232,29 @@ class NginxProvider extends BaseProvider {
239
232
  // if match certificate, then add https server
240
233
  this._addHttpsServer({
241
234
  conf,
242
- locations: sortedLocations,
235
+ locations: rules,
243
236
  certificateFileName: certificate.domain,
244
237
  serverName: parsedServerName,
245
238
  corsAllowedOrigins,
246
239
  daemonPort: nodeInfo.port,
240
+ commonHeaders,
241
+ blockletDid,
247
242
  });
248
243
  } else {
249
244
  this._addHttpServer({
250
245
  conf,
251
- locations: sortedLocations,
246
+ locations: rules,
252
247
  serverName: parsedServerName,
253
248
  corsAllowedOrigins,
254
249
  port,
255
250
  daemonPort: nodeInfo.port,
251
+ commonHeaders,
252
+ blockletDid,
256
253
  });
257
254
  }
258
255
  }
259
256
 
260
- if (addedDefaultServer === false) {
257
+ if (!sites.find((x) => x.domain === '_')) {
261
258
  this._addDefaultServer(conf, nodeInfo.port);
262
259
  }
263
260
 
@@ -267,8 +264,8 @@ class NginxProvider extends BaseProvider {
267
264
  }
268
265
 
269
266
  async reload() {
270
- const nginxStatus = await getNginxStatus(this.configFilePath);
271
- if (nginxStatus.ownByABTNode) {
267
+ const nginxStatus = await getNginxStatus(this.configPath);
268
+ if (nginxStatus.managed) {
272
269
  const result = this._exec('reload');
273
270
  logger.info('reload:reload', { result: result.stdout });
274
271
  return result;
@@ -291,26 +288,19 @@ class NginxProvider extends BaseProvider {
291
288
 
292
289
  async stop() {
293
290
  logger.info('stop');
291
+
294
292
  return this._exec('stop');
295
293
  }
296
294
 
297
295
  // FIXME: 这个函数可以不暴露出去?
298
296
  initialize() {
299
- if (!fs.existsSync(this.configFilePath)) {
300
- fs.writeFileSync(
301
- this.configFilePath,
302
- util.getMainTemplate({
303
- logDir: this.logDirectory.replace(this.configDirectory, '').replace(/^\//, ''),
304
- tmpDirectory: this.tmpDirectory.replace(this.configDirectory, '').replace(/^\//, ''),
305
- nginxLoadModules: getNginxLoadModuleDirectives(REQUIRED_MODULES, this.readNginxConfigParams()).join(os.EOL),
306
- workerProcess: this.getWorkerProcess(),
307
- })
308
- );
297
+ if (!fs.existsSync(this.configPath)) {
298
+ fs.writeFileSync(this.configPath, this.getConfTemplate());
309
299
  }
310
300
  }
311
301
 
312
302
  async validateConfig() {
313
- const command = `${this.binPath} -t -c ${this.configFilePath} -p ${this.configDirectory}`; // eslint-disable-line no-param-reassign
303
+ const command = `${this.binPath} -t -c ${this.configPath} -p ${this.configDir}`; // eslint-disable-line no-param-reassign
314
304
  const result = shelljs.exec(command, { silent: true });
315
305
  if (result.code !== 0) {
316
306
  logger.info(`exec ${command} error`, { error: result.stderr });
@@ -318,47 +308,36 @@ class NginxProvider extends BaseProvider {
318
308
  }
319
309
  }
320
310
 
321
- async rotateLogs() {
322
- const nginxStatus = await getNginxStatus(this.configDirectory);
323
- if (!nginxStatus.ownByABTNode) {
324
- logger.warn('nginx does not running');
311
+ async rotateLogs({ retain = LOG_RETAIN_IN_DAYS } = {}) {
312
+ const nginxStatus = await getNginxStatus(this.configDir);
313
+ if (!nginxStatus.managed) {
314
+ logger.warn('nginx is not running');
325
315
  return;
326
316
  }
327
317
 
328
318
  logger.info('start rotate nginx log files');
329
- const files = [this.accessLogFile, this.errorLogFile];
319
+ const files = [this.accessLog, this.errorLog];
330
320
  const rotateTasks = files.map(
331
- (file) => rotateNginxLogFile({ file, nginxPid: nginxStatus.pid, cwd: this.logDirectory })
321
+ (file) => rotateNginxLogFile({ file, nginxPid: nginxStatus.pid, cwd: this.logDir, retain })
332
322
  // eslint-disable-next-line function-paren-newline
333
323
  );
334
324
  await Promise.all(rotateTasks);
335
325
  logger.info('rotate nginx log files finished');
336
326
  }
337
327
 
338
- getWorkerProcess() {
339
- if (this.isTest) {
340
- return 1;
341
- }
342
-
343
- if (process.env.NODE_ENV === 'production') {
344
- return os.cpus().length;
345
- }
346
-
347
- return 1;
348
- }
349
-
350
328
  /**
351
329
  * execute nginx command, default is to start nginx
352
330
  * @param {string} param param of nginx -s {param}
353
331
  */
354
332
  _exec(param = '') {
355
333
  logger.info('exec', { param });
356
- let command = `${this.binPath} -c ${this.configFilePath} -p ${this.configDirectory}`; // eslint-disable-line no-param-reassign
334
+ let command = `${this.binPath} -c ${this.configPath} -p ${formatBackSlash(this.configDir)}`; // eslint-disable-line no-param-reassign
357
335
  if (param) {
358
336
  command = `${command} -s ${param}`;
359
337
  }
360
338
 
361
339
  logger.info('exec command:', { command });
340
+
362
341
  const result = shelljs.exec(command, { silent: true });
363
342
  if (result.code !== 0) {
364
343
  logger.error(`exec ${command} error`, { error: result.stderr });
@@ -368,6 +347,11 @@ class NginxProvider extends BaseProvider {
368
347
  return result;
369
348
  }
370
349
 
350
+ _isHttp2Supported() {
351
+ const configArgs = this.readNginxConfigParams();
352
+ return typeof configArgs['with-http_v2_module'] !== 'undefined';
353
+ }
354
+
371
355
  readNginxConfigParams() {
372
356
  const result = shelljs.exec(`${this.binPath} -V`, { silent: true });
373
357
 
@@ -385,7 +369,9 @@ class NginxProvider extends BaseProvider {
385
369
  return parseNginxConfigArgs(result.stderr);
386
370
  }
387
371
 
388
- _addReverseProxy({ type, ...args }) {
372
+ _addReverseProxy(args) {
373
+ const { type } = args;
374
+
389
375
  if (type === ROUTING_RULE_TYPES.REDIRECT) {
390
376
  this._addRedirectTypeLocation(args);
391
377
  return;
@@ -396,6 +382,11 @@ class NginxProvider extends BaseProvider {
396
382
  return;
397
383
  }
398
384
 
385
+ if (type === ROUTING_RULE_TYPES.DIRECT_RESPONSE) {
386
+ this._addDirectResponseLocation(args);
387
+ return;
388
+ }
389
+
399
390
  if (type === ROUTING_RULE_TYPES.NONE) {
400
391
  this._addNotFoundLocation(args);
401
392
  return;
@@ -404,21 +395,50 @@ class NginxProvider extends BaseProvider {
404
395
  this._addBlockletTypeLocation(args);
405
396
  }
406
397
 
398
+ addUpstreamServer(port) {
399
+ this.conf.nginx.http._add('upstream', getUpstreamName(port));
400
+ const upstream = this.conf.nginx.http.upstream.length
401
+ ? this.conf.nginx.http.upstream[this.conf.nginx.http.upstream.length - 1]
402
+ : this.conf.nginx.http.upstream;
403
+
404
+ upstream._add('server', `127.0.0.1:${port} max_fails=1 fail_timeout=2s`);
405
+ upstream._add('keepalive', '2');
406
+ }
407
+
408
+ ensureUpstreamServers(rules) {
409
+ const upstreamMap = new Map();
410
+
411
+ const servicePort = process.env.ABT_NODE_SERVICE_PORT;
412
+
413
+ if (!upstreamMap.has(servicePort)) {
414
+ this.addUpstreamServer(servicePort);
415
+ upstreamMap.set(servicePort, 1);
416
+ }
417
+
418
+ for (let i = 0; i < rules.length; i++) {
419
+ const rule = rules[i];
420
+
421
+ if (
422
+ [
423
+ ROUTING_RULE_TYPES.DAEMON,
424
+ ROUTING_RULE_TYPES.SERVICE,
425
+ ROUTING_RULE_TYPES.BLOCKLET,
426
+ ROUTING_RULE_TYPES.GENERAL_PROXY,
427
+ ].includes(rule.type) &&
428
+ !upstreamMap.has(String(rule.port))
429
+ ) {
430
+ this.addUpstreamServer(rule.port);
431
+ upstreamMap.set(String(rule.port), 1);
432
+ }
433
+ }
434
+ }
435
+
407
436
  /**
408
437
  * Returns:
409
438
  * server /flash/ {
410
439
  * rewrite ^/flash/(.*) /$1 break;
411
440
  * proxy_pass http://127.0.0.1:8090;
412
- * proxy_set_header Host $host;
413
- * proxy_set_header X-Real-IP $remote_addr;
414
- * proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
415
441
  * }
416
- * @param {object} server
417
- * @param {number} port
418
- * @param {string} pathPrefix
419
- * @param {string} pathSuffix
420
- * @param {string} did root blocklet did
421
- * @param {string} realDid child blocklet did
422
442
  */
423
443
  _addBlockletTypeLocation({
424
444
  server,
@@ -428,58 +448,62 @@ class NginxProvider extends BaseProvider {
428
448
  groupPrefix,
429
449
  suffix,
430
450
  did,
431
- realDid,
451
+ componentId,
432
452
  corsAllowedOrigins,
433
453
  target,
454
+ targetPrefix, // used to strip prefix from target
434
455
  ruleId,
435
- services = [],
456
+ type,
457
+ commonHeaders,
458
+ cacheGroup,
436
459
  }) {
437
460
  server._add('location', concatPath(prefix, suffix));
438
461
 
439
462
  const location = this._getLastLocation(server);
440
- const isDidAuthPath = prefix === DID_AUTH_PATH_PREFIX;
441
- const rewritePathPrefix = isDidAuthPath ? DEFAULT_ADMIN_PATH : prefix;
463
+ const isDidAuthPath = NginxProvider.isDidAuthPrefix(prefix);
442
464
 
443
465
  // Note: 下面这段代码比较 tricky,不要在这段代码之前添加任何 add_header, proxy_set_header, proxy_hide_header 的语句,否则 nginx 配置可能无法按预期工作
444
- if (!isEmpty(corsAllowedOrigins)) {
445
- if (corsAllowedOrigins.includes(DOMAIN_FOR_DEFAULT_SITE) || isDidAuthPath) {
446
- location._add('include', 'includes/cors-loose');
447
- location._add('include', 'includes/security');
448
- } else {
449
- location._add('add_header', `Access-Control-Allow-Origin $allow_origin_${md5(serverName)} always`);
450
- location._add('include', 'includes/cors-strict');
451
- location._add('include', 'includes/security');
452
- }
466
+ this._addCors({ location, corsAllowedOrigins, serverName, isDidAuthPath });
453
467
 
454
- location._addVerbatimBlock('if ($request_method = "OPTIONS")', 'return 204;');
455
- } else {
456
- location._add('include', 'includes/security');
468
+ this._addCommonResHeaders(location, commonHeaders);
469
+ if (!cacheGroup && !suffix) {
470
+ this._addTailSlashRedirection(location, prefix); // Note: 末尾 "/" 的重定向要放在 CORS(OPTIONS) 响应之后, 这样不会影响 OPTIONS 的响应
457
471
  }
458
472
 
459
- this._addTailSlashRedirection(location, prefix); // Note: 末尾 "/" 的重定向要放在 CORS(OPTIONS) 响应之后, 这样不会影响 OPTIONS 的响应
460
-
461
473
  if (did) {
462
474
  location._add('set', `$did "${did}"`);
463
475
  location._add('proxy_set_header', `X-Blocklet-Did "${did}"`);
464
- if (realDid) {
465
- location._add('proxy_set_header', `X-Blocklet-Real-Did "${realDid}"`);
476
+ if (componentId) {
477
+ location._add('proxy_set_header', `X-Blocklet-Component-Id "${componentId}"`);
466
478
  }
467
479
  }
468
480
 
481
+ location._add('include', 'includes/proxy');
482
+
469
483
  // kill cache
470
- if (this.cacheDisabled) {
484
+ if (this.cacheEnabled === false) {
471
485
  location._add('expires', '-1');
486
+ } else if (ROUTER_CACHE_GROUPS[cacheGroup]) {
487
+ location._add('proxy_cache', cacheGroup);
488
+ location._add('proxy_cache_valid', `200 ${ROUTER_CACHE_GROUPS[cacheGroup].period}`);
489
+ location._add('include', 'includes/cache');
472
490
  }
473
491
 
474
- location._add('include', 'includes/proxy');
475
- location._add('proxy_set_header', `X-Path-Prefix "${rewritePathPrefix}"`);
476
- if (groupPrefix) {
477
- location._add('proxy_set_header', `X-Group-Path-Prefix "${groupPrefix}"`);
478
- }
492
+ // Redirect blocklet traffic
493
+ if (type === ROUTING_RULE_TYPES.BLOCKLET) {
494
+ // FIXME: logic related to server gateway should not in provider
495
+ let rewritePathPrefix = prefix.replace(WELLKNOWN_SERVICE_PATH_PREFIX, '').replace(USER_AVATAR_PATH_PREFIX, '');
479
496
 
480
- // Redirect traffic to service process
481
- if (services.length > 0) {
482
497
  // Add header
498
+ if (targetPrefix) {
499
+ rewritePathPrefix = rewritePathPrefix.replace(targetPrefix, '');
500
+ }
501
+
502
+ location._add('proxy_set_header', `X-Path-Prefix "${rewritePathPrefix}"`);
503
+ if (groupPrefix) {
504
+ location._add('proxy_set_header', `X-Group-Path-Prefix "${groupPrefix}"`);
505
+ }
506
+
483
507
  location._add('proxy_set_header', `X-Blocklet-Url "http://127.0.0.1:${port}"`);
484
508
  if (ruleId) {
485
509
  location._add('proxy_set_header', `X-Routing-Rule-Id "${ruleId}"`);
@@ -490,42 +514,44 @@ class NginxProvider extends BaseProvider {
490
514
  }
491
515
 
492
516
  // Rewrite path
493
- const rewrite = target === WELLKNOWN_AUTH_PATH_PREFIX ? `${target}/`.replace(/\/\//g, '/') : '/';
494
- location._add('rewrite', `^${rewritePathPrefix}/?(.*) ${rewrite}$1 break`);
495
-
496
- location._add('proxy_pass', `http://127.0.0.1:${process.env.ABT_NODE_SERVICE_PORT}`);
517
+ location._add('rewrite', `^${rewritePathPrefix}/?(.*) /$1 break`);
518
+ location._add('proxy_pass', `http://${getUpstreamName(process.env.ABT_NODE_SERVICE_PORT)}/`);
497
519
 
498
520
  return;
499
521
  }
500
522
 
501
- // Redirect traffic to blocklet process
502
-
503
- // Rewrite path
523
+ // Redirect daemon traffic
524
+ const rewritePathPrefix = isDidAuthPath ? DEFAULT_ADMIN_PATH : prefix;
525
+ location._add('proxy_set_header', `X-Path-Prefix "${rewritePathPrefix}"`);
504
526
  if (!suffix && prefix !== target) {
505
527
  location._add('rewrite', `^${rewritePathPrefix}/?(.*) ${`${target}/`.replace(/\/\//g, '/')}$1 break`);
506
528
  }
507
529
 
508
- location._add('proxy_pass', `http://127.0.0.1:${port}`);
530
+ location._add('proxy_pass', `http://${getUpstreamName(port)}`);
509
531
  }
510
532
 
511
- _addRedirectTypeLocation({ server, url, redirectCode, prefix, suffix }) {
512
- const formatedUrl = trimEndSlash(url);
513
-
533
+ _addRedirectTypeLocation({ server, url, redirectCode, prefix, suffix, corsAllowedOrigins, serverName }) {
534
+ const cleanUrl = trimEndSlash(url);
514
535
  server._add('location', `${concatPath(prefix, suffix)}`);
515
536
  const location = this._getLastLocation(server);
537
+
538
+ const isDidAuthPath = NginxProvider.isDidAuthPrefix(prefix);
539
+
540
+ this._addCors({ location, corsAllowedOrigins, serverName, isDidAuthPath });
541
+
516
542
  location._add('set $abt_query_string', '""');
517
543
  location._addVerbatimBlock('if ($query_string)', 'set $abt_query_string "?$query_string";');
518
544
 
519
545
  // 如果当前请求的 path 和 prefix 一样,则不需要重写,直接返回重定向地址就可以了
520
- location._addVerbatimBlock(`if ($uri = ${prefix})`, `return ${redirectCode} ${formatedUrl}$abt_query_string;`);
546
+ location._addVerbatimBlock(`if ($uri = ${prefix})`, `return ${redirectCode} ${cleanUrl}$abt_query_string;`);
521
547
 
522
548
  // 如果 prefix 是根路径,则不需要重写,直接将当前的请求附加到设置的重定向地址后面
523
549
  if (prefix === '/') {
524
- location._add('return', `${redirectCode} ${formatedUrl}$request_uri`);
550
+ location._add('return', `${redirectCode} ${cleanUrl}$request_uri`);
525
551
  } else {
526
552
  // 将当前请求中的 prefix 去掉,然后拼接对应的重定地址
527
553
  location._add('rewrite', `^${prefix}(.*) $1`);
528
- location._add('return', `${redirectCode} ${formatedUrl === '/' ? '' : formatedUrl}$1$abt_query_string`);
554
+ location._add('return', `${redirectCode} ${cleanUrl === '/' ? '' : cleanUrl}$1$abt_query_string`);
529
555
  }
530
556
  }
531
557
 
@@ -537,12 +563,27 @@ class NginxProvider extends BaseProvider {
537
563
  location._add('try_files', '$uri /404.html break');
538
564
  }
539
565
 
540
- _addGeneralProxyLocation({ server, port, prefix, suffix }) {
566
+ _addGeneralProxyLocation({ server, port, prefix, suffix, blockletDid }) {
567
+ server._add('location', concatPath(prefix, suffix));
568
+ const location = this._getLastLocation(server);
569
+ this._addCommonHeader(location);
570
+ location._add('include', 'includes/proxy');
571
+ if (blockletDid) {
572
+ location._add('proxy_set_header', `X-Blocklet-Did ${blockletDid}`);
573
+ }
574
+
575
+ location._add('proxy_pass', `http://${getUpstreamName(port)}`);
576
+ }
577
+
578
+ _addDirectResponseLocation({ server, response, prefix, suffix }) {
541
579
  server._add('location', concatPath(prefix, suffix));
542
580
  const location = this._getLastLocation(server);
543
581
  this._addCommonHeader(location);
582
+ if (response.contentType) {
583
+ location._add('default_type', response.contentType);
584
+ }
544
585
 
545
- location._add('proxy_pass', `http://127.0.0.1:${port}`);
586
+ location._add('return', `${response.status} "${response.body}"`);
546
587
  }
547
588
 
548
589
  /**
@@ -564,10 +605,7 @@ class NginxProvider extends BaseProvider {
564
605
  // eslint-disable-next-line no-template-curly-in-string
565
606
  'set $tail_slash_redirect_flag "${tail_slash_redirect_flag}2";'
566
607
  );
567
- location._addVerbatimBlock(
568
- 'if ($tail_slash_redirect_flag = 12)',
569
- `return 307 $abt_proto://$abt_host${prefix}/$abt_query_string;`
570
- );
608
+ location._addVerbatimBlock('if ($tail_slash_redirect_flag = 12)', `return 307 ${prefix}/$abt_query_string;`);
571
609
  }
572
610
  }
573
611
 
@@ -591,7 +629,7 @@ class NginxProvider extends BaseProvider {
591
629
  throw new Error('daemonPort is required');
592
630
  }
593
631
 
594
- server._add('root', this.wwwDirectory.replace(this.configDirectory, '').replace(/^\//, ''));
632
+ server._add('root', this.getRelativeConfigDir(this.wwwDir));
595
633
  server._add('error_page', '404 =404 /_abtnode_404');
596
634
  server._add('error_page', '502 =502 /_abtnode_502');
597
635
  server._add('error_page', '500 502 503 504 =500 /_abtnode_5xx');
@@ -616,28 +654,34 @@ class NginxProvider extends BaseProvider {
616
654
 
617
655
  _addWwwFiles(nodeInfo) {
618
656
  const welcomePage = nodeInfo.enableWelcomePage ? getWelcomeTemplate(nodeInfo) : get404Template(nodeInfo);
619
- fs.writeFileSync(`${this.wwwDirectory}/index.html`, welcomePage); // disable index.html
620
- fs.writeFileSync(`${this.wwwDirectory}/404.html`, get404Template(nodeInfo));
621
- fs.writeFileSync(`${this.wwwDirectory}/502.html`, get502Template(nodeInfo));
622
- fs.writeFileSync(`${this.wwwDirectory}/5xx.html`, get5xxTemplate(nodeInfo));
657
+
658
+ fs.writeFileSync(`${this.wwwDir}/index.html`, welcomePage); // disable index.html
659
+ fs.writeFileSync(`${this.wwwDir}/404.html`, get404Template(nodeInfo));
660
+ fs.writeFileSync(`${this.wwwDir}/502.html`, get502Template(nodeInfo));
661
+ fs.writeFileSync(`${this.wwwDir}/5xx.html`, get5xxTemplate(nodeInfo));
662
+ // 将 @abtnode/router-templates/lib/styles (font 相关样式) 复制到 www/router-template-styles 中
663
+ fs.copySync(
664
+ `${path.dirname(require.resolve('@abtnode/router-templates/package.json'))}/lib/styles`,
665
+ `${this.wwwDir}/router-template-styles`
666
+ );
623
667
  }
624
668
 
625
669
  _copyConfigFiles() {
626
- fs.copySync(path.join(__dirname, 'includes'), this.includesDirectory, {
670
+ fs.copySync(path.join(__dirname, 'includes'), this.includesDir, {
627
671
  overwrite: true,
628
672
  });
629
- fs.copySync(path.join(__dirname, '..', 'www'), this.wwwDirectory, { overwrite: true });
673
+ fs.copySync(path.join(__dirname, '..', 'www'), this.wwwDir, { overwrite: true });
630
674
  }
631
675
 
632
676
  _ensureDhparam() {
633
- const targetFile = path.join(this.includesDirectory, 'dhparam.pem');
677
+ const targetFile = path.join(this.includesDir, 'dhparam.pem');
634
678
  if (fs.existsSync(targetFile)) {
635
679
  this._isDhparamGenerated = true;
636
680
  return;
637
681
  }
638
682
 
639
683
  if (this.isTest || process.env.NODE_ENV === 'test') {
640
- fs.copySync(path.join(__dirname, 'includes/dhparam.pem'), targetFile, { overwrite: true });
684
+ fs.copySync(path.join(__dirname, 'includes', 'dhparam.pem'), targetFile, { overwrite: true });
641
685
  this._isDhparamGenerated = true;
642
686
  return;
643
687
  }
@@ -658,26 +702,13 @@ class NginxProvider extends BaseProvider {
658
702
  }
659
703
  }
660
704
 
661
- _sortLocations(rules = []) {
662
- const rulesWithoutSuffix = rules.filter((x) => !x.suffix);
663
- const rulesWithSuffix = rules
664
- .filter((x) => x.suffix)
665
- .sort((a, b) => {
666
- const lenA = (a.prefix || '').length + (a.prefix || '').length;
667
- const lenB = (b.prefix || '').length + (b.prefix || '').length;
668
- return lenB - lenA;
669
- });
670
-
671
- return rulesWithoutSuffix.concat(rulesWithSuffix);
672
- }
673
-
674
- _addGlobalHeaders(conf, headers) {
705
+ _addCommonResHeaders(block, headers) {
675
706
  if (!headers || Object.prototype.toString.call(headers) !== '[object Object]') {
676
707
  return;
677
708
  }
678
709
 
679
710
  Object.keys(headers).forEach((key) => {
680
- conf.nginx.http._add('add_header', `${key} ${headers[key]}`);
711
+ block._add('add_header', `${key} ${headers[key]}`);
681
712
  });
682
713
  }
683
714
 
@@ -709,14 +740,32 @@ class NginxProvider extends BaseProvider {
709
740
  : conf.nginx.stream.server;
710
741
  }
711
742
 
712
- _addHttpServer({ locations = [], serverName, conf, corsAllowedOrigins, port, daemonPort }) {
743
+ _addHttpServer({
744
+ locations = [],
745
+ serverName,
746
+ conf,
747
+ corsAllowedOrigins,
748
+ port,
749
+ daemonPort,
750
+ commonHeaders,
751
+ blockletDid,
752
+ }) {
713
753
  const httpServerUnit = this._addHttpServerUnit({ conf, serverName, port });
714
754
  this._addDefaultLocations(httpServerUnit, daemonPort);
715
755
  // eslint-disable-next-line max-len
716
- locations.forEach((x) => this._addReverseProxy({ server: httpServerUnit, ...x, serverName, corsAllowedOrigins })); // prettier-ignore
756
+ locations.forEach((x) => this._addReverseProxy({ server: httpServerUnit, ...x, serverName, corsAllowedOrigins, commonHeaders, blockletDid })); // prettier-ignore
717
757
  }
718
758
 
719
- _addHttpsServer({ conf, locations, certificateFileName, serverName, corsAllowedOrigins, daemonPort }) {
759
+ _addHttpsServer({
760
+ conf,
761
+ locations,
762
+ certificateFileName,
763
+ serverName,
764
+ corsAllowedOrigins,
765
+ daemonPort,
766
+ commonHeaders,
767
+ blockletDid,
768
+ }) {
720
769
  const httpsServerUnit = this._addHttpsServerUnit({ conf, serverName, certificateFileName });
721
770
 
722
771
  const httpServerUnit = this._addHttpServerUnit({ conf, serverName });
@@ -724,7 +773,7 @@ class NginxProvider extends BaseProvider {
724
773
 
725
774
  this._addDefaultLocations(httpsServerUnit, daemonPort);
726
775
  // eslint-disable-next-line max-len
727
- locations.forEach((x) => this._addReverseProxy({ server: httpsServerUnit, ...x, serverName, corsAllowedOrigins })); // prettier-ignore
776
+ locations.forEach((x) => this._addReverseProxy({ server: httpsServerUnit, ...x, serverName, corsAllowedOrigins, commonHeaders, blockletDid })); // prettier-ignore
728
777
  }
729
778
 
730
779
  _addHttpServerUnit({ conf, serverName, port }) {
@@ -746,10 +795,10 @@ class NginxProvider extends BaseProvider {
746
795
  conf.nginx.http._add('server');
747
796
  const httpsServerUnit = this._getLastServer(conf);
748
797
 
749
- const crtPath = `${path.join(this.certificateDirectory, certificateFileName)}.crt`;
750
- const keyPath = `${path.join(this.certificateDirectory, certificateFileName)}.key`;
798
+ const crtPath = `${joinNginxPath(this.certDir, certificateFileName.replace('*', '-'))}.crt`;
799
+ const keyPath = `${joinNginxPath(this.certDir, certificateFileName.replace('*', '-'))}.key`;
751
800
 
752
- let listen = `${this.httpsPort} ssl`;
801
+ let listen = `${this.httpsPort} ssl ${this.isHttp2Supported ? 'http2' : ''}`.trim();
753
802
  if (serverName === '_') {
754
803
  listen = `${listen} default_server`;
755
804
  }
@@ -783,7 +832,13 @@ class NginxProvider extends BaseProvider {
783
832
  validServices.forEach((service) => {
784
833
  conf.nginx.stream._add('server');
785
834
  const server = this._getLastStreamServer(conf);
786
- const protocol = service.protocol === 'tcp' ? '' : ` ${service.protocol}`;
835
+ let protocol = '';
836
+ if (service.protocol === 'udp') {
837
+ protocol = ` ${service.protocol}`;
838
+ server._add('proxy_responses', 1);
839
+ server._add('proxy_timeout', '1s');
840
+ }
841
+
787
842
  server._add('listen', `${service.port}${protocol}`);
788
843
  server._add('proxy_pass', `127.0.0.1:${service.upstreamPort}`);
789
844
  });
@@ -804,12 +859,47 @@ class NginxProvider extends BaseProvider {
804
859
 
805
860
  allowedOrigins.push('default "";');
806
861
  conf.nginx.http._addVerbatimBlock(
807
- `map $http_origin $allow_origin_${md5(parseServerName(corsConfig.serverName))}`,
862
+ `map $http_origin $allow_origin_${md5(parseServerName(corsConfig.domain))}`,
808
863
  allowedOrigins.join(' ')
809
864
  );
810
865
  }
811
866
  });
812
867
  }
868
+
869
+ _addCors({ location, corsAllowedOrigins, serverName, isDidAuthPath }) {
870
+ if (!isEmpty(corsAllowedOrigins)) {
871
+ if (corsAllowedOrigins.includes(DOMAIN_FOR_DEFAULT_SITE) || isDidAuthPath) {
872
+ location._add('include', 'includes/cors-loose');
873
+ location._add('include', 'includes/security');
874
+ } else {
875
+ location._add('add_header', `Access-Control-Allow-Origin $allow_origin_${md5(serverName)} always`);
876
+ location._add('include', 'includes/cors-strict');
877
+ location._add('include', 'includes/security');
878
+ }
879
+
880
+ location._addVerbatimBlock('if ($request_method = "OPTIONS")', 'return 204;');
881
+ } else {
882
+ location._add('include', 'includes/security');
883
+ }
884
+ }
885
+
886
+ addGlobalReqLimit(block, limit) {
887
+ const key = limit.ipHeader ? `$http_${limit.ipHeader}` : '$binary_remote_addr';
888
+ block._add('limit_req_zone', `${key} zone=ip_limit:20m rate=${limit.rate || 5}r/s`);
889
+ block._add('limit_req', `zone=ip_limit burst=${limit.maxInstantRate || 30} delay=10`);
890
+ block._add('limit_req_status', 429);
891
+ }
892
+
893
+ getLogFilesForToday() {
894
+ return {
895
+ access: this.accessLog,
896
+ error: this.errorLog,
897
+ };
898
+ }
899
+
900
+ getLogDir() {
901
+ return this.logDir;
902
+ }
813
903
  }
814
904
 
815
905
  NginxProvider.describe = async ({ configDir = '' } = {}) => {
@@ -834,7 +924,6 @@ NginxProvider.describe = async ({ configDir = '' } = {}) => {
834
924
  * @param {string} param.configDir nginx config directory
835
925
  */
836
926
  NginxProvider.check = async ({ configDir = '' } = {}) => {
837
- logger.info('check nginx provider');
838
927
  logger.info('check formal config directory', { configDir });
839
928
  const binPath = shelljs.which('nginx');
840
929
  const result = {
@@ -846,21 +935,22 @@ NginxProvider.check = async ({ configDir = '' } = {}) => {
846
935
 
847
936
  if (!binPath) {
848
937
  result.available = false;
849
- result.error = 'nginx is not detected, make sure you have nginx installed.';
938
+ result.error =
939
+ 'Nginx is not detected, to have nginx installed you can checkout: https://nginx.org/en/docs/install.html.';
850
940
  return result;
851
941
  }
852
942
 
853
943
  const nginxStatus = await getNginxStatus(configDir);
854
- if (nginxStatus.running && !nginxStatus.ownByABTNode) {
944
+ if (nginxStatus.running && !nginxStatus.managed) {
855
945
  result.available = false;
856
946
  result.error =
857
- 'seems a nginx daemon is already running on your machine, a controlled nginx is required by Blocklet Server to work properly, please terminate the running nginx daemon before try again.';
947
+ 'Seems a nginx daemon already running, a controlled nginx is required by Blocklet Server to work properly, please terminate the running nginx daemon before try again.';
858
948
 
859
949
  return result;
860
950
  }
861
951
 
862
- if (nginxStatus.ownByABTNode) {
863
- const pidFile = path.join(configDir, 'nginx/nginx.pid');
952
+ if (nginxStatus.managed) {
953
+ const pidFile = path.join(configDir, 'nginx', 'nginx.pid');
864
954
  if (fs.existsSync(pidFile)) {
865
955
  const diskPid = Number(fs.readFileSync(pidFile).toString().trim());
866
956
  // If we have the pid lock file, but not the same as running nginx pid, nginx should be killed
@@ -875,49 +965,75 @@ NginxProvider.check = async ({ configDir = '' } = {}) => {
875
965
  }
876
966
  }
877
967
 
878
- const tempConfigDirectory = path.join(
879
- os.tmpdir(),
880
- `test_abtnode_nginx_provider-${Date.now()}-${Math.ceil(Math.random() * 100)}`,
881
- CONFIG_FOLDER_NAME
882
- );
883
- fs.mkdirSync(tempConfigDirectory, { recursive: true });
968
+ const testDir = path.join(os.tmpdir(), `test_nginx_provider-${Date.now()}-${Math.ceil(Math.random() * 10000)}`);
969
+ try {
970
+ const tempConfDir = path.join(testDir, CONFIG_FOLDER_NAME);
971
+ fs.mkdirSync(tempConfDir, { recursive: true });
884
972
 
885
- const nginxProvider = new NginxProvider({ configDirectory: tempConfigDirectory, isTest: true });
886
- nginxProvider.initialize();
973
+ const provider = new NginxProvider({ configDir: tempConfDir, isTest: true });
974
+ provider.initialize();
887
975
 
888
- logger.info('check:addTestServer', { configFilePath: nginxProvider.configFilePath });
889
- await addTestServer({
890
- configFilePath: nginxProvider.configFilePath,
891
- port: await getPort(),
892
- upstreamPort: await getPort(),
893
- });
976
+ logger.info('check:addTestServer', { configPath: provider.configPath });
977
+ await addTestServer({
978
+ configPath: provider.configPath,
979
+ port: await getPort(),
980
+ upstreamPort: await getPort(),
981
+ });
894
982
 
895
- const missingModules = getMissingModules(nginxProvider.readNginxConfigParams());
983
+ const missingModules = getMissingModules(provider.readNginxConfigParams());
896
984
 
897
- if (missingModules.length > 0) {
985
+ if (missingModules.length > 0) {
986
+ result.available = false;
987
+ result.error = `Blocklet Server depends on some modules of Nginx that are not compiled: ${missingModules.join(
988
+ ', '
989
+ )}`;
990
+ return result;
991
+ }
992
+
993
+ await provider.start();
994
+ await provider.stop();
995
+
996
+ return result;
997
+ } catch (error) {
898
998
  result.available = false;
899
- result.error = `Blocklet Server depends on some modules of Nginx that are not compiled: ${missingModules.join(
900
- ', '
901
- )}`;
999
+ result.error = error.message;
1000
+ logger.error('check nginx failed', { error });
902
1001
  return result;
1002
+ } finally {
1003
+ if (fs.existsSync(testDir)) {
1004
+ fs.rmSync(testDir, { recursive: true, force: true });
1005
+ }
903
1006
  }
1007
+ };
1008
+
1009
+ const hasPortPermission = async (port) => {
1010
+ const configDir = path.join(
1011
+ os.tmpdir(),
1012
+ `test_nginx_provider-${Date.now()}-${Math.ceil(Math.random() * 10000)}`,
1013
+ CONFIG_FOLDER_NAME
1014
+ );
1015
+ try {
1016
+ fs.mkdirSync(configDir, { recursive: true });
904
1017
 
905
- await nginxProvider.start();
1018
+ const provider = new NginxProvider({ configDir, isTest: true });
1019
+ provider.initialize();
906
1020
 
907
- await nginxProvider.stop();
1021
+ await addTestServer({ configPath: provider.configPath, port });
1022
+ await provider.start();
1023
+ await provider.stop();
908
1024
 
909
- return result;
1025
+ return true;
1026
+ } catch (err) {
1027
+ return false;
1028
+ } finally {
1029
+ fs.rmSync(configDir, { recursive: true, force: true });
1030
+ }
910
1031
  };
911
1032
 
912
1033
  NginxProvider.getStatus = getNginxStatus;
913
1034
  NginxProvider.exists = () => !!shelljs.which('nginx');
914
- NginxProvider.getLogFilesOfCurrentDay = (routerDirectory) => {
915
- const logDirectory = path.join(routerDirectory, 'log');
916
1035
 
917
- return {
918
- access: path.join(logDirectory, 'access.log'),
919
- error: path.join(logDirectory, 'error.log'),
920
- };
921
- };
1036
+ NginxProvider.getUsablePorts = async () => getUsablePorts('nginx', hasPortPermission);
1037
+ NginxProvider.hasPortPermission = hasPortPermission;
922
1038
 
923
1039
  module.exports = NginxProvider;