@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
package/lib/nginx/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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')(
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
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 = (
|
|
64
|
+
const parseServerName = (domain) => {
|
|
56
65
|
// ipDnsDomain template
|
|
57
|
-
if (
|
|
58
|
-
const name =
|
|
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 (
|
|
64
|
-
const name =
|
|
72
|
+
if (domain.startsWith('*.')) {
|
|
73
|
+
const name = domain.replace('*', '').replace(/\./g, '\\.');
|
|
65
74
|
return `~.+${name}$`;
|
|
66
75
|
}
|
|
67
76
|
|
|
68
|
-
return
|
|
77
|
+
return domain;
|
|
69
78
|
};
|
|
70
79
|
|
|
71
80
|
class NginxProvider extends BaseProvider {
|
|
72
81
|
/**
|
|
73
|
-
* @param {
|
|
74
|
-
* @param {
|
|
75
|
-
* @param {number} httpPort
|
|
76
|
-
* @param {number}
|
|
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({
|
|
79
|
-
super('nginx
|
|
80
|
-
if (!
|
|
81
|
-
throw new Error('invalid
|
|
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.
|
|
92
|
-
this.
|
|
93
|
-
this.
|
|
94
|
-
this.
|
|
95
|
-
this.
|
|
96
|
-
this.
|
|
97
|
-
this.
|
|
98
|
-
this.
|
|
99
|
-
|
|
100
|
-
|
|
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.
|
|
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
|
-
|
|
121
|
+
configDir,
|
|
108
122
|
httpPort: this.httpPort,
|
|
109
123
|
httpsPort: this.httpsPort,
|
|
110
|
-
|
|
124
|
+
cacheEnabled: this.cacheEnabled,
|
|
111
125
|
});
|
|
112
126
|
|
|
113
127
|
// ensure directories
|
|
114
|
-
[this.
|
|
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
|
-
|
|
126
|
-
|
|
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 =
|
|
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
|
|
151
|
-
|
|
152
|
-
conf.live(this.configFilePath);
|
|
187
|
+
this.conf = conf;
|
|
153
188
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
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
|
|
224
|
-
const
|
|
225
|
-
const
|
|
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(
|
|
228
|
-
|
|
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.
|
|
234
|
-
const keyPath = `${path.join(this.
|
|
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:
|
|
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:
|
|
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 (
|
|
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.
|
|
271
|
-
if (nginxStatus.
|
|
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.
|
|
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.
|
|
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.
|
|
323
|
-
if (!nginxStatus.
|
|
324
|
-
logger.warn('nginx
|
|
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.
|
|
319
|
+
const files = [this.accessLog, this.errorLog];
|
|
330
320
|
const rotateTasks = files.map(
|
|
331
|
-
(file) => rotateNginxLogFile({ file, nginxPid: nginxStatus.pid, cwd: this.
|
|
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}
|
|
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(
|
|
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
|
-
|
|
451
|
+
componentId,
|
|
432
452
|
corsAllowedOrigins,
|
|
433
453
|
target,
|
|
454
|
+
targetPrefix, // used to strip prefix from target
|
|
434
455
|
ruleId,
|
|
435
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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 (
|
|
465
|
-
location._add('proxy_set_header', `X-Blocklet-
|
|
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.
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
494
|
-
location._add('
|
|
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
|
|
502
|
-
|
|
503
|
-
|
|
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
|
|
530
|
+
location._add('proxy_pass', `http://${getUpstreamName(port)}`);
|
|
509
531
|
}
|
|
510
532
|
|
|
511
|
-
_addRedirectTypeLocation({ server, url, redirectCode, prefix, suffix }) {
|
|
512
|
-
const
|
|
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} ${
|
|
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} ${
|
|
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} ${
|
|
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('
|
|
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.
|
|
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
|
-
|
|
620
|
-
fs.writeFileSync(`${this.
|
|
621
|
-
fs.writeFileSync(`${this.
|
|
622
|
-
fs.writeFileSync(`${this.
|
|
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.
|
|
670
|
+
fs.copySync(path.join(__dirname, 'includes'), this.includesDir, {
|
|
627
671
|
overwrite: true,
|
|
628
672
|
});
|
|
629
|
-
fs.copySync(path.join(__dirname, '..', 'www'), this.
|
|
673
|
+
fs.copySync(path.join(__dirname, '..', 'www'), this.wwwDir, { overwrite: true });
|
|
630
674
|
}
|
|
631
675
|
|
|
632
676
|
_ensureDhparam() {
|
|
633
|
-
const targetFile = path.join(this.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
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({
|
|
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 = `${
|
|
750
|
-
const keyPath = `${
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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.
|
|
944
|
+
if (nginxStatus.running && !nginxStatus.managed) {
|
|
855
945
|
result.available = false;
|
|
856
946
|
result.error =
|
|
857
|
-
'
|
|
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.
|
|
863
|
-
const pidFile = path.join(configDir, 'nginx
|
|
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
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
-
|
|
886
|
-
|
|
973
|
+
const provider = new NginxProvider({ configDir: tempConfDir, isTest: true });
|
|
974
|
+
provider.initialize();
|
|
887
975
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
-
|
|
983
|
+
const missingModules = getMissingModules(provider.readNginxConfigParams());
|
|
896
984
|
|
|
897
|
-
|
|
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 =
|
|
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
|
-
|
|
1018
|
+
const provider = new NginxProvider({ configDir, isTest: true });
|
|
1019
|
+
provider.initialize();
|
|
906
1020
|
|
|
907
|
-
|
|
1021
|
+
await addTestServer({ configPath: provider.configPath, port });
|
|
1022
|
+
await provider.start();
|
|
1023
|
+
await provider.stop();
|
|
908
1024
|
|
|
909
|
-
|
|
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
|
-
|
|
918
|
-
|
|
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;
|