@abtnode/router-provider 1.6.30 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,11 @@
1
+ /* eslint-disable no-continue */
1
2
  const url = require('url');
2
3
  const path = require('path');
3
4
  const fs = require('fs-extra');
4
5
  const get = require('lodash/get');
5
6
  const joinUrl = require('url-join');
6
7
  const checkDomainMatch = require('@abtnode/util/lib/check-domain-match');
7
- const {
8
- DEFAULT_IP_DNS_DOMAIN_SUFFIX,
9
- ROUTING_RULE_TYPES,
10
- WELLKNOWN_SERVICE_PATH_PREFIX,
11
- } = require('@abtnode/constant');
8
+ const { DEFAULT_IP_DOMAIN_SUFFIX, ROUTING_RULE_TYPES, WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
12
9
 
13
10
  const logger = require('@abtnode/logger')('router:default:daemon', { filename: 'engine' });
14
11
 
@@ -65,40 +62,39 @@ const createRequestHandler = (id) => (req, res, target) => {
65
62
  }
66
63
  }
67
64
 
68
- // FIXME logic related to server gateway should not in provider
69
- const rewritePathPrefix = rule.prefix.replace(WELLKNOWN_SERVICE_PATH_PREFIX, '') || '/';
70
-
71
65
  if (rule.type === ROUTING_RULE_TYPES.GENERAL_PROXY) {
72
- // we do not need rewrite for internal servers
66
+ // do not rewrite for internal servers
67
+ } else if (rule.prefix.includes(WELLKNOWN_SERVICE_PATH_PREFIX)) {
68
+ // do not rewrite for service requests
73
69
  } else {
74
- req.url = req.url.substr(rewritePathPrefix.length);
70
+ req.url = joinUrl(rule.target, req.url.substr(rule.prefix.length));
75
71
  }
76
-
77
72
  if (req.url.startsWith('/') === false) {
78
73
  req.url = `/${req.url}`;
79
74
  }
80
75
  logger.debug('transform url', { url: req.url, rule });
81
76
 
77
+ // NOTE: x-* header keys must be lower case to avoid collisions from chained proxy
82
78
  if (rule.did) {
83
- req.headers['X-Blocklet-Did'] = rule.did;
79
+ req.headers['x-blocklet-did'] = rule.did;
84
80
  }
85
81
  if (rule.realDid && rule.did !== rule.realDid) {
86
- req.headers['X-Blocklet-Real-Did'] = rule.realDid;
82
+ req.headers['x-blocklet-real-did'] = rule.realDid;
87
83
  }
88
84
 
89
- req.headers['X-Path-Prefix'] = rewritePathPrefix;
90
- req.headers['X-Group-Path-Prefix'] = rule.groupPrefix || '/';
85
+ req.headers['x-path-prefix'] = rule.prefix.replace(WELLKNOWN_SERVICE_PATH_PREFIX, '') || '/';
86
+ req.headers['x-group-path-prefix'] = rule.groupPrefix || '/';
91
87
 
92
88
  if (rule.type === ROUTING_RULE_TYPES.BLOCKLET) {
93
- req.headers['X-Blocklet-Url'] = `http://127.0.0.1:${rule.port}`;
89
+ req.headers['x-blocklet-url'] = `http://127.0.0.1:${rule.port}`;
94
90
  }
95
91
 
96
92
  if (rule.ruleId) {
97
- req.headers['X-Routing-Rule-Id'] = rule.ruleId;
93
+ req.headers['x-routing-rule-id'] = rule.ruleId;
98
94
  }
99
95
 
100
96
  if (rule.target && rule.target !== '/') {
101
- req.headers['X-Routing-Rule-Path-Prefix'] = rule.target;
97
+ req.headers['x-routing-rule-path-prefix'] = rule.target;
102
98
  }
103
99
  };
104
100
 
@@ -249,18 +245,23 @@ const updateConfig = (reload = false) => {
249
245
 
250
246
  const addedDomains = newDomains.filter((x) => !oldDomains.includes(x));
251
247
  for (const domain of addedDomains) {
252
- if (isSpecificDomain(domain)) {
253
- logger.info('register domain on reload', { domain });
254
- const site = sites.find((x) => x.domain === domain);
255
- const [rule] = site.rules.filter((x) => ['daemon', 'blocklet'].includes(x.type));
256
- const match = findCertificate(config.certs, site.domain);
257
- main.register({
258
- src: site.domain,
259
- target: `http://127.0.0.1:${rule.port}`,
260
- ssl: match ? { key: match.privateKey, cert: match.certificate } : null,
261
- onRequest: createRequestHandler(rule.id),
262
- });
248
+ if (isSpecificDomain(domain) === false) {
249
+ continue;
250
+ }
251
+ const site = sites.find((x) => x.domain === domain);
252
+ const [rule] = site.rules.filter((x) => ['daemon', 'blocklet'].includes(x.type));
253
+ if (!rule) {
254
+ continue;
263
255
  }
256
+
257
+ logger.info('register domain on reload', { domain });
258
+ const match = findCertificate(config.certs, site.domain);
259
+ main.register({
260
+ src: site.domain,
261
+ target: `http://127.0.0.1:${rule.port}`,
262
+ ssl: match ? { key: match.privateKey, cert: match.certificate } : null,
263
+ onRequest: createRequestHandler(rule.id),
264
+ });
264
265
  }
265
266
 
266
267
  const removedDomains = oldDomains.filter((x) => !newDomains.includes(x));
@@ -281,7 +282,7 @@ const updateConfig = (reload = false) => {
281
282
  updateConfig();
282
283
 
283
284
  // create main server
284
- const defaultCert = config.certs.find((x) => x.domain.endsWith(DEFAULT_IP_DNS_DOMAIN_SUFFIX));
285
+ const defaultCert = config.certs.find((x) => x.domain.endsWith(DEFAULT_IP_DOMAIN_SUFFIX));
285
286
  const main = new ProxyServer({
286
287
  xfwd: true,
287
288
  onError,
@@ -310,8 +311,11 @@ config.sites.forEach((site) => {
310
311
  }
311
312
 
312
313
  const [rule] = site.rules.filter((x) => ['daemon', 'blocklet'].includes(x.type));
313
- const match = findCertificate(config.certs, domain);
314
+ if (!rule) {
315
+ return;
316
+ }
314
317
 
318
+ const match = findCertificate(config.certs, domain);
315
319
  logger.info('register domain on start', { domain });
316
320
  main.register({
317
321
  src: domain,
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
+ const http = require('http');
3
4
  const shelljs = require('shelljs');
4
5
  const get = require('lodash/get');
5
6
  const omit = require('lodash/omit');
@@ -15,6 +16,7 @@ const BaseProvider = require('../base');
15
16
  const {
16
17
  decideHttpPort,
17
18
  decideHttpsPort,
19
+ getUsablePorts,
18
20
  get404Template,
19
21
  get502Template,
20
22
  get5xxTemplate,
@@ -70,6 +72,10 @@ class DefaultProvider extends BaseProvider {
70
72
  }
71
73
 
72
74
  async reload() {
75
+ if (process.env.NODE_ENV === 'development') {
76
+ return this.start();
77
+ }
78
+
73
79
  await pm2.connectAsync();
74
80
 
75
81
  // ensure daemon is live
@@ -218,4 +224,27 @@ DefaultProvider.check = async ({ configDir = '' } = {}) => {
218
224
  return result;
219
225
  };
220
226
 
227
+ DefaultProvider.getUsablePorts = async () =>
228
+ getUsablePorts(
229
+ 'default',
230
+ (port) =>
231
+ new Promise((resolve) => {
232
+ try {
233
+ const server = http.createServer();
234
+
235
+ server.once('error', () => {
236
+ server.close();
237
+ resolve(false);
238
+ });
239
+
240
+ server.listen(port, (err) => {
241
+ server.close();
242
+ resolve(!err);
243
+ });
244
+ } catch (err) {
245
+ resolve(false);
246
+ }
247
+ })
248
+ );
249
+
221
250
  module.exports = DefaultProvider;
@@ -93,6 +93,7 @@ module.exports = class ReverseProxy {
93
93
 
94
94
  // @link: https://github.com/http-party/node-http-proxy/issues/1401
95
95
  this.proxy.on('proxyRes', (proxyRes) => {
96
+ delete proxyRes.headers['x-powered-by'];
96
97
  this.opts.headers.forEach((x) => {
97
98
  proxyRes.headers[x.key] = x.value;
98
99
  });
@@ -38,6 +38,7 @@ const {
38
38
  const {
39
39
  decideHttpPort,
40
40
  decideHttpsPort,
41
+ getUsablePorts,
41
42
  get404Template,
42
43
  get502Template,
43
44
  get5xxTemplate,
@@ -781,7 +782,6 @@ NginxProvider.describe = async ({ configDir = '' } = {}) => {
781
782
  * @param {string} param.configDir nginx config directory
782
783
  */
783
784
  NginxProvider.check = async ({ configDir = '' } = {}) => {
784
- logger.info('check nginx provider');
785
785
  logger.info('check formal config directory', { configDir });
786
786
  const binPath = shelljs.which('nginx');
787
787
  const result = {
@@ -823,24 +823,24 @@ NginxProvider.check = async ({ configDir = '' } = {}) => {
823
823
  }
824
824
  }
825
825
 
826
- const tempConfigDirectory = path.join(
826
+ const tmpDir = path.join(
827
827
  os.tmpdir(),
828
- `test_abtnode_nginx_provider-${Date.now()}-${Math.ceil(Math.random() * 100)}`,
828
+ `test_nginx_provider-${Date.now()}-${Math.ceil(Math.random() * 10000)}`,
829
829
  CONFIG_FOLDER_NAME
830
830
  );
831
- fs.mkdirSync(tempConfigDirectory, { recursive: true });
831
+ fs.mkdirSync(tmpDir, { recursive: true });
832
832
 
833
- const nginxProvider = new NginxProvider({ configDir: tempConfigDirectory, isTest: true });
834
- nginxProvider.initialize();
833
+ const provider = new NginxProvider({ configDir: tmpDir, isTest: true });
834
+ provider.initialize();
835
835
 
836
- logger.info('check:addTestServer', { configPath: nginxProvider.configPath });
836
+ logger.info('check:addTestServer', { configPath: provider.configPath });
837
837
  await addTestServer({
838
- configPath: nginxProvider.configPath,
838
+ configPath: provider.configPath,
839
839
  port: await getPort(),
840
840
  upstreamPort: await getPort(),
841
841
  });
842
842
 
843
- const missingModules = getMissingModules(nginxProvider.readNginxConfigParams());
843
+ const missingModules = getMissingModules(provider.readNginxConfigParams());
844
844
 
845
845
  if (missingModules.length > 0) {
846
846
  result.available = false;
@@ -850,9 +850,10 @@ NginxProvider.check = async ({ configDir = '' } = {}) => {
850
850
  return result;
851
851
  }
852
852
 
853
- await nginxProvider.start();
853
+ await provider.start();
854
+ await provider.stop();
854
855
 
855
- await nginxProvider.stop();
856
+ fs.rmdirSync(tmpDir, { recursive: true });
856
857
 
857
858
  return result;
858
859
  };
@@ -860,4 +861,29 @@ NginxProvider.check = async ({ configDir = '' } = {}) => {
860
861
  NginxProvider.getStatus = getNginxStatus;
861
862
  NginxProvider.exists = () => !!shelljs.which('nginx');
862
863
 
864
+ NginxProvider.getUsablePorts = async () =>
865
+ getUsablePorts('nginx', async (port) => {
866
+ try {
867
+ const configDir = path.join(
868
+ os.tmpdir(),
869
+ `test_nginx_provider-${Date.now()}-${Math.ceil(Math.random() * 10000)}`,
870
+ CONFIG_FOLDER_NAME
871
+ );
872
+ fs.mkdirSync(configDir, { recursive: true });
873
+
874
+ const provider = new NginxProvider({ configDir, isTest: true });
875
+ provider.initialize();
876
+
877
+ await addTestServer({ configPath: provider.configPath, port });
878
+ await provider.start();
879
+ await provider.stop();
880
+
881
+ fs.rmdirSync(configDir, { recursive: true });
882
+
883
+ return true;
884
+ } catch (err) {
885
+ return false;
886
+ }
887
+ });
888
+
863
889
  module.exports = NginxProvider;
package/lib/nginx/util.js CHANGED
@@ -57,20 +57,22 @@ const addTestServer = ({ configPath, port, upstreamPort }) =>
57
57
 
58
58
  location / {
59
59
  if ($uri = /admin/did-connect) {include includes/cors-strict; include includes/security;}
60
- return 200 'Hello AbtNode!';
60
+ return 200 'Hello Blocklet Server!';
61
61
  }
62
62
  `
63
63
  );
64
64
 
65
- conf.nginx._addVerbatimBlock(
66
- 'stream',
67
- `
65
+ if (upstreamPort) {
66
+ conf.nginx._addVerbatimBlock(
67
+ 'stream',
68
+ `
68
69
  server {
69
70
  listen ${upstreamPort} udp;
70
71
  proxy_pass 127.0.0.1:${port};
71
72
  }
72
73
  `
73
- );
74
+ );
75
+ }
74
76
 
75
77
  conf.on('flushed', () => resolve());
76
78
  conf.live(configPath);
package/lib/util.js CHANGED
@@ -1,3 +1,8 @@
1
+ /* eslint-disable no-await-in-loop */
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const getPort = require('get-port');
5
+ const portUsed = require('port-used');
1
6
  const sortBy = require('lodash/sortBy');
2
7
  const isValidDomain = require('is-valid-domain');
3
8
  const checkDomainMatch = require('@abtnode/util/lib/check-domain-match');
@@ -8,7 +13,7 @@ const {
8
13
  IP,
9
14
  DEFAULT_HTTP_PORT,
10
15
  DEFAULT_HTTPS_PORT,
11
- DEFAULT_IP_DNS_DOMAIN_SUFFIX,
16
+ DEFAULT_IP_DOMAIN_SUFFIX,
12
17
  DOMAIN_FOR_DEFAULT_SITE,
13
18
  ROUTING_RULE_TYPES,
14
19
  SLOT_FOR_IP_DNS_SITE,
@@ -110,7 +115,7 @@ const isSpecificDomain = (domain) => {
110
115
  return false;
111
116
  }
112
117
 
113
- if (domain.endsWith(DEFAULT_IP_DNS_DOMAIN_SUFFIX)) {
118
+ if (domain.endsWith(DEFAULT_IP_DOMAIN_SUFFIX)) {
114
119
  return false;
115
120
  }
116
121
 
@@ -118,7 +123,7 @@ const isSpecificDomain = (domain) => {
118
123
  };
119
124
 
120
125
  const toSlotDomain = (domain) => {
121
- if (domain.endsWith(DEFAULT_IP_DNS_DOMAIN_SUFFIX)) {
126
+ if (domain.endsWith(DEFAULT_IP_DOMAIN_SUFFIX)) {
122
127
  const subDomain = domain.split('.').shift();
123
128
  const matches = subDomain.match(IP);
124
129
  if (matches) {
@@ -148,9 +153,45 @@ const matchRule = (rules, url) => {
148
153
  return findRule(rulesWithSuffix, url) || findRule(rulesWithoutSuffix, url);
149
154
  };
150
155
 
156
+ const isPortOccupied = (port) => portUsed.check(port);
157
+ const getUsablePort = async (preferredPorts, hasPortPermission) => {
158
+ for (const port of preferredPorts) {
159
+ const occupied = await isPortOccupied(port);
160
+ if (!occupied) {
161
+ const listenable = await hasPortPermission(port);
162
+ if (listenable) {
163
+ return port;
164
+ }
165
+ }
166
+ }
167
+
168
+ return getPort();
169
+ };
170
+ const getUsablePorts = async (provider, hasPortPermission) => {
171
+ const file = path.join(process.env.PM2_HOME, `${provider}-preferred-ports.json`);
172
+ if (fs.existsSync(file)) {
173
+ const config = fs.readJsonSync(file);
174
+ if (config.httpPort && config.httpsPort) {
175
+ return config;
176
+ }
177
+ }
178
+
179
+ const [httpPort, httpsPort] = await Promise.all([
180
+ getUsablePort([80, 8080], hasPortPermission),
181
+ getUsablePort([443, 8443], hasPortPermission),
182
+ ]);
183
+
184
+ const config = { httpPort, httpsPort };
185
+ fs.writeJsonSync(file, config);
186
+ return config;
187
+ };
188
+
151
189
  module.exports = {
152
190
  decideHttpPort,
153
191
  decideHttpsPort,
192
+ getUsablePorts,
193
+ getUsablePort,
194
+ isPortOccupied,
154
195
  findCertificate,
155
196
  concatPath,
156
197
  trimEndSlash,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abtnode/router-provider",
3
- "version": "1.6.30",
3
+ "version": "1.7.1",
4
4
  "description": "Routing engine implementations for abt node",
5
5
  "author": "polunzh <polunzh@gmail.com>",
6
6
  "homepage": "https://github.com/ArcBlock/blocklet-server#readme",
@@ -32,10 +32,10 @@
32
32
  "url": "https://github.com/ArcBlock/blocklet-server/issues"
33
33
  },
34
34
  "dependencies": {
35
- "@abtnode/constant": "1.6.30",
36
- "@abtnode/logger": "1.6.30",
37
- "@abtnode/router-templates": "1.6.30",
38
- "@abtnode/util": "1.6.30",
35
+ "@abtnode/constant": "1.7.1",
36
+ "@abtnode/logger": "1.7.1",
37
+ "@abtnode/router-templates": "1.7.1",
38
+ "@abtnode/util": "1.7.1",
39
39
  "axios": "^0.25.0",
40
40
  "debug": "^4.3.3",
41
41
  "find-process": "^1.4.3",
@@ -49,6 +49,7 @@
49
49
  "moment": "^2.29.1",
50
50
  "nginx-conf": "^1.5.0",
51
51
  "object-hash": "^3.0.0",
52
+ "port-used": "^2.0.8",
52
53
  "promise-retry": "^2.0.1",
53
54
  "shelljs": "^0.8.4",
54
55
  "tar": "^6.1.0",
@@ -61,5 +62,5 @@
61
62
  "fs-extra": "^10.0.0",
62
63
  "needle": "^3.0.0"
63
64
  },
64
- "gitHead": "d8ea64b807f7852186cd4bf544193496fbb31e7c"
65
+ "gitHead": "5d7efb21dd09f90206251fb74601ca37b0ef84bf"
65
66
  }