UNPKG

3.35 kBJavaScriptView Raw
1const debug = require('debug')
2const cluster = require('cluster')
3const physicalCpuCount = require('physical-cpu-count')
4const eventToPromise = require('event-to-promise')
5const {redirect} = require('./redirect')
6const {lock, unlock} = require('./helpers/pid')
7const {normaliseManifest} = require('./helpers/normaliseManifest')
8// const {watch} = require('chokidar')
9// const debounce = require('debounce-collect')
10const recursiveReaddir = require('recursive-readdir')
11const {Configuration} = require('./configuration/Configuration')
12const {Parser} = require('expr-eval')
13
14function evaluateToNumber (expression) {
15 if (typeof expression === 'number') return expression
16 return Parser.evaluate(
17 expression,
18 {max_physical_cpu_cores: physicalCpuCount}
19 )
20}
21
22module.exports.Master = class Master {
23 constructor (options, argv) {
24 this.workers = []
25 this.options = options
26 this.argv = argv
27 this.log = debug(process.title)
28 }
29
30 stop () {
31 console.log('')
32 process.exit()
33 }
34
35 async start () {
36 const options = this.options
37
38 lock()
39 process.on('exit', unlock)
40 process.on('SIGINT', this.stop.bind(this))
41 process.on('SIGHUP', this.reload.bind(this))
42
43 await this._load()
44
45 if (options.http.redirect === true) {
46 this.redirector = await redirect({
47 http: options.http,
48 acme: options.acme
49 })
50 }
51
52 this.log('Server started')
53
54 // const broadcastExpireToWorkers = debounce(function (paths) {
55 // for (const path of new Set(paths)) {
56 // for (const worker of workers) {
57 // worker.send({expire: path})
58 // }
59 // }
60 // }, 50)
61
62 // watch(options.root, {})
63 // .on('change', broadcastExpireToWorkers)
64 // .on('unlink', broadcastExpireToWorkers)
65 }
66
67 async _load () {
68 const files = {}
69 for (const host of this.options.hosts) {
70 const {root, domain, manifest} = host
71
72 // Scan files so workers can build a cached lookup index.
73 // See also: hostOptions middleware
74 files[domain] = {root, index: await recursiveReaddir(root)}
75
76 host.manifest = normaliseManifest(manifest)
77 }
78
79 const workers = []
80 const maxWorkerCount = evaluateToNumber(this.options.workers.count)
81 if (maxWorkerCount < 1) throw new Error('Worker count must be at least 1')
82 cluster.setupMaster({exec: require.resolve('./worker.js')})
83 while (workers.length < maxWorkerCount) workers.push(cluster.fork())
84 await Promise.all(workers.map((worker) => eventToPromise(worker, 'online')))
85 workers.forEach((worker) => worker.send({options: this.options, files}))
86 await Promise.all(workers.map((worker) => eventToPromise(worker, 'listening')))
87 this.workers = workers
88
89 for (const {root, domain} of this.options.hosts) {
90 let authority = domain
91 if (this.options.https.port !== 443) {
92 authority += ':' + this.options.https.port
93 }
94 this.log(`Serving ${root} on port https://${authority}`)
95 }
96 }
97
98 async reload () {
99 this.log('Reloading configuration')
100 try {
101 const configuration = new Configuration(this.argv)
102 this.options = await configuration.load()
103 } catch (error) {
104 this.log(error)
105 return
106 }
107
108 const retirees = this.workers
109 await this._load()
110 for (const retiree of retirees) {
111 retiree.kill()
112 }
113 }
114}