cncjs

Форк
0
/
index.js 
273 строки · 8.7 Кб
1
import dns from 'dns';
2
import fs from 'fs';
3
import os from 'os';
4
import path from 'path';
5
import url from 'url';
6
import bcrypt from 'bcrypt-nodejs';
7
import chalk from 'chalk';
8
import ensureArray from 'ensure-array';
9
import expandTilde from 'expand-tilde';
10
import express from 'express';
11
import httpProxy from 'http-proxy';
12
import escapeRegExp from 'lodash/escapeRegExp';
13
import isEqual from 'lodash/isEqual';
14
import set from 'lodash/set';
15
import size from 'lodash/size';
16
import trimEnd from 'lodash/trimEnd';
17
import uniqWith from 'lodash/uniqWith';
18
import webappengine from 'webappengine';
19
import settings from './config/settings';
20
import app from './app';
21
import cncengine from './services/cncengine';
22
import monitor from './services/monitor';
23
import config from './services/configstore';
24
import { ensureString } from './lib/ensure-type';
25
import logger, { setLevel } from './lib/logger';
26
import urljoin from './lib/urljoin';
27

28
const log = logger('init');
29

30
const createServer = (options, callback) => {
31
  options = { ...options };
32

33
  { // verbosity
34
    const verbosity = options.verbosity;
35

36
    // https://github.com/winstonjs/winston#logging-levels
37
    if (verbosity === 1) {
38
      set(settings, 'verbosity', verbosity);
39
      setLevel('verbose');
40
    }
41
    if (verbosity === 2) {
42
      set(settings, 'verbosity', verbosity);
43
      setLevel('debug');
44
    }
45
    if (verbosity === 3) {
46
      set(settings, 'verbosity', verbosity);
47
      setLevel('silly');
48
    }
49
  }
50

51
  const rcfile = path.resolve(options.configFile || settings.rcfile);
52

53
  // configstore service
54
  log.info(`Loading configuration from ${chalk.yellow(JSON.stringify(rcfile))}`);
55
  config.load(rcfile);
56

57
  // rcfile
58
  settings.rcfile = rcfile;
59

60
  { // secret
61
    if (!config.get('secret')) {
62
      // generate a secret key
63
      const secret = bcrypt.genSaltSync(); // TODO: use a strong secret
64
      config.set('secret', secret);
65
    }
66

67
    settings.secret = config.get('secret', settings.secret);
68
  }
69

70
  { // watchDirectory
71
    const watchDirectory = options.watchDirectory || config.get('watchDirectory');
72

73
    if (watchDirectory) {
74
      if (fs.existsSync(watchDirectory)) {
75
        log.info(`Watching ${chalk.yellow(JSON.stringify(watchDirectory))} for file changes`);
76

77
        // monitor service
78
        monitor.start({ watchDirectory: watchDirectory });
79
      } else {
80
        log.error(`The directory ${chalk.yellow(JSON.stringify(watchDirectory))} does not exist.`);
81
      }
82
    }
83
  }
84

85
  { // accessTokenLifetime
86
    const accessTokenLifetime = options.accessTokenLifetime || config.get('accessTokenLifetime');
87

88
    if (accessTokenLifetime) {
89
      set(settings, 'accessTokenLifetime', accessTokenLifetime);
90
    }
91
  }
92

93
  { // allowRemoteAccess
94
    const allowRemoteAccess = options.allowRemoteAccess || config.get('allowRemoteAccess', false);
95

96
    if (allowRemoteAccess) {
97
      if (size(config.get('users')) === 0) {
98
        log.warn('You\'ve enabled remote access to the server. It\'s recommended to create an user account to protect against malicious attacks.');
99
      }
100

101
      set(settings, 'allowRemoteAccess', allowRemoteAccess);
102
    }
103
  }
104

105
  const { port = 0, host, backlog } = options;
106
  const mountPoints = uniqWith([
107
    ...ensureArray(options.mountPoints),
108
    ...ensureArray(config.get('mountPoints'))
109
  ], isEqual).filter(mount => {
110
    if (!mount || !mount.route || mount.route === '/') {
111
      log.error(`Must specify a valid route path ${JSON.stringify(mount.route)}.`);
112
      return false;
113
    }
114

115
    return true;
116
  });
117
  const routes = [];
118

119
  mountPoints.forEach(mount => {
120
    if (ensureString(mount.target).match(/^(http|https):\/\//i)) {
121
      log.info(`Starting a proxy server to proxy all requests starting with ${chalk.yellow(mount.route)} to ${chalk.yellow(mount.target)}`);
122

123
      routes.push({
124
        type: 'server',
125
        route: mount.route,
126
        server: (options) => {
127
          // route
128
          // > '/custom-widget/'
129
          // routeWithoutTrailingSlash
130
          // > '/custom-widget'
131
          // target
132
          // > 'https://cncjs.github.io/cncjs-widget-boilerplate/'
133
          // targetPathname
134
          // > '/cncjs-widget-boilerplate/'
135
          // proxyPathPattern
136
          // > RegExp('^/cncjs-widget-boilerplate/custom-widget')
137
          const { route = '/' } = { ...options };
138
          const routeWithoutTrailingSlash = trimEnd(route, '/');
139
          const target = mount.target;
140
          const targetPathname = url.parse(target).pathname;
141
          const proxyPathPattern = new RegExp('^' + escapeRegExp(urljoin(targetPathname, routeWithoutTrailingSlash)), 'i');
142

143
          log.debug(`> route=${chalk.yellow(route)}`);
144
          log.debug(`> routeWithoutTrailingSlash=${chalk.yellow(routeWithoutTrailingSlash)}`);
145
          log.debug(`> target=${chalk.yellow(target)}`);
146
          log.debug(`> targetPathname=${chalk.yellow(targetPathname)}`);
147
          log.debug(`> proxyPathPattern=RegExp(${chalk.yellow(proxyPathPattern)})`);
148

149
          const proxy = httpProxy.createProxyServer({
150
            // Change the origin of the host header to the target URL
151
            changeOrigin: true,
152

153
            // Do not verify the SSL certificate for self-signed certs
154
            //secure: false,
155

156
            target: target
157
          });
158

159
          proxy.on('proxyReq', (proxyReq, req, res, options) => {
160
            const originalPath = proxyReq.path || '';
161
            proxyReq.path = originalPath
162
              .replace(proxyPathPattern, targetPathname)
163
              .replace('//', '/');
164

165
            log.debug(`proxy.on('proxyReq'): modifiedPath=${chalk.yellow(proxyReq.path)}, originalPath=${chalk.yellow(originalPath)}`);
166
          });
167

168
          proxy.on('proxyRes', (proxyRes, req, res) => {
169
            log.debug(`proxy.on('proxyRes'): headers=${JSON.stringify(proxyRes.headers, true, 2)}`);
170
          });
171

172
          const app = express();
173

174
          // Matched routes:
175
          //   /widget/
176
          //   /widget/v1/
177
          app.all(urljoin(routeWithoutTrailingSlash, '*'), (req, res) => {
178
            const url = req.url;
179
            log.debug(`proxy.web(): url=${chalk.yellow(url)}`);
180
            proxy.web(req, res);
181
          });
182

183
          // Matched routes:
184
          //   /widget
185
          app.all(routeWithoutTrailingSlash, (req, res, next) => {
186
            const url = req.url;
187
            // Redirect URL with a trailing slash
188
            if (url.indexOf(routeWithoutTrailingSlash) === 0 &&
189
                            url.indexOf(routeWithoutTrailingSlash + '/') < 0) {
190
              const redirectUrl = routeWithoutTrailingSlash + '/' + url.slice(routeWithoutTrailingSlash.length);
191
              log.debug(`redirect: url=${chalk.yellow(url)}, redirectUrl=${chalk.yellow(redirectUrl)}`);
192
              res.redirect(301, redirectUrl);
193
              return;
194
            }
195

196
            next();
197
          });
198

199
          return app;
200
        }
201
      });
202
    } else {
203
      // expandTilde('~') => '/Users/<userhome>'
204
      const directory = expandTilde(ensureString(mount.target)).trim();
205

206
      log.info(`Mounting a directory ${chalk.yellow(JSON.stringify(directory))} to serve requests starting with ${chalk.yellow(mount.route)}`);
207

208
      if (!directory) {
209
        log.error(`The directory path ${chalk.yellow(JSON.stringify(directory))} must not be empty.`);
210
        return;
211
      }
212
      if (!path.isAbsolute(directory)) {
213
        log.error(`The directory path ${chalk.yellow(JSON.stringify(directory))} must be absolute.`);
214
        return;
215
      }
216
      if (!fs.existsSync(directory)) {
217
        log.error(`The directory path ${chalk.yellow(JSON.stringify(directory))} does not exist.`);
218
        return;
219
      }
220

221
      routes.push({
222
        type: 'static',
223
        route: mount.route,
224
        directory: directory
225
      });
226
    }
227
  });
228

229
  routes.push({
230
    type: 'server',
231
    route: '/',
232
    server: () => app()
233
  });
234

235
  webappengine({ port, host, backlog, routes })
236
    .on('ready', (server) => {
237
      // cncengine service
238
      cncengine.start(server, options.controller || config.get('controller', ''));
239

240
      const address = server.address().address;
241
      const port = server.address().port;
242

243
      callback && callback(null, {
244
        address,
245
        port,
246
        mountPoints,
247
      });
248

249
      if (address !== '0.0.0.0') {
250
        log.info('Starting the server at ' + chalk.yellow(`http://${address}:${port}`));
251
        return;
252
      }
253

254
      dns.lookup(os.hostname(), { family: 4, all: true }, (err, addresses) => {
255
        if (err) {
256
          log.error('Can\'t resolve host name:', err);
257
          return;
258
        }
259

260
        addresses.forEach(({ address, family }) => {
261
          log.info('Starting the server at ' + chalk.yellow(`http://${address}:${port}`));
262
        });
263
      });
264
    })
265
    .on('error', (err) => {
266
      callback && callback(err);
267
      log.error(err);
268
    });
269
};
270

271
export {
272
  createServer
273
};
274

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.