let fs_readFile = util.promisify(fs.readFile)
let yauzl_open = util.promisify(yauzl.open)
-let Server = function(sites, enable_socket_io, enable_caching) {
+let Server = function() {
if (!this instanceof Server)
throw Error('Server is a constructor')
- this.sites = sites
- this.socket_io = enable_socket_io ? new (require('socket.io'))() : undefined
- this.enable_caching = enable_caching || false
-
- this.http_servers = []
- this.https_servers = []
+ this.mime_types = {}
+ this.listen = []
+ this.enable_caching = false
this.build_cache_email = new BuildCache()
this.build_cache_json = new BuildCache()
this.mime_type_default = 'application/octet-stream'
}
-Server.prototype.listen = async function(port, protocol, ssl_cert, ssl_key) {
- console.log(`listen on port ${port} protocol ${protocol}`)
- let server
- switch (protocol) {
- case 'http:':
- server = require('http').createServer()
- this.http_servers.push(server) // in future use this for shutdown/reload
- break;
- case 'https:':
- server = require('https').createServer(
- {
- 'cert': await fs_readFile(
- ssl_cert || '_ssl/localhost_cert_bundle.pem'
- ),
- 'key': await fs_readFile(
- ssl_key || '_ssl/localhost_key.pem'
- )
- }
- )
- this.https_servers.push(server) // in future use this for shutdown/reload
- break;
- default:
- assert(false);
- }
- server.listen(port)
- server.on(
- 'request',
- (request, response) =>
- this.respond(request, response, protocol) // ignore returned promise
- )
- if (this.socket_io !== undefined)
- this.socket_io.attach(server)
-}
-
Server.prototype.get_email = function(pathname) {
return this.build_cache_email.get(
pathname,
(resolve, reject) => {
write_stream.
on('finish', () => {resolve(write_stream.getContents())}).
- on('error', () => {reject()})
+ on('error', err => {reject(err)})
}
)
read_stream.pipe(write_stream)
)
}
+let compare_listen = (a, b) => {
+ if (a.port < b.port)
+ return -1
+ if (a.port > b.port)
+ return 1
+ if (a.host < b.host)
+ return -1
+ if (a.host > b.host)
+ return 1
+ if (a.protocol < b.protocol)
+ return -1
+ if (a.protocol > b.protocol)
+ return 1
+ if (a.ssl_cert < b.ssl_cert)
+ return -1
+ if (a.ssl_cert > b.ssl_cert)
+ return 1
+ if (a.ssl_key < b.ssl_key)
+ return -1
+ if (a.ssl_key > b.ssl_key)
+ return 1
+ return 0
+}
+
+Server.prototype.attach = function(server, protocol) {
+ server.on(
+ 'request',
+ (request, response) =>
+ this.respond(request, response, protocol)
+ // ignore returned promise
+ )
+}
+
Server.prototype.refresh_config = async function() {
- this.mime_types = await this.get_json('_config/mime_types.json')
+ let config = await this.get_json('_config/server.json')
+
+ this.enable_caching = config.enable_caching
+
+ this.mime_types = config.mime_types
this.mime_type_html =
Object.prototype.hasOwnProperty.call(this.mime_types, '.html') ?
this.mime_types['.html'] :
this.mime_type_default
+
+ let listen = []
+ for (let i = 0; i < config.listen.length; ++i)
+ listen.push(
+ Object.assign(
+ {
+ port: 0,
+ host: '0.0.0.0',
+ protocol: 'http:',
+ ssl_cert: '_ssl/localhost_cert_bundle.pem',
+ ssl_key: '_ssl/localhost_key.pem',
+ },
+ config.listen[i]
+ )
+ )
+ listen.sort(compare_listen)
+ //console.log('listen', listen)
+
+ // stop all servers no longer in configuration file
+ let i = 0
+ let j = 0
+ while (i < this.listen.length)
+ switch (
+ j < this.listen.length ?
+ compare_listen(this.listen[i], listen[j]) :
+ -1
+ ) {
+ case -1:
+ if (this.listen[i].server !== undefined) {
+ console.log(
+ `stop listening on ${this.listen[i].protocol}//${this.listen[i].host}:${this.listen[i].port}`
+ )
+ await new Promise(
+ (resolve, reject) => {
+ this.listen[i].server.close(
+ err => {
+ if (err)
+ reject(err)
+ resolve()
+ }
+ )
+ }
+ )
+ }
+ ++i
+ break
+ case 0:
+ listen[j++].server = this.listen[i++].server
+ break
+ case 1:
+ listen[j++].server = undefined // just to be on the safe side
+ break
+ }
+
+ // then start all newly mentioned servers (or those which need retrying)
+ for (i = 0; i < listen.length; ++i)
+ if (listen[i].server === undefined) {
+ console.log(
+ `start listening on ${listen[i].protocol}//${listen[i].host}:${listen[i].port}`
+ )
+ let server
+ switch (listen[i].protocol) {
+ case 'http:':
+ server = require('http').createServer()
+ break
+ case 'https:':
+ server = require('https').createServer(
+ {
+ 'cert': await fs_readFile(listen[i].ssl_cert),
+ 'key': await fs_readFile(listen[i].ssl_key)
+ }
+ )
+ break
+ default:
+ assert(false)
+ }
+ try {
+ await new Promise(
+ (resolve, reject) => {
+ server.on('listening', () => {resolve()})
+ server.on('error', err => {reject(err)})
+ server.listen(listen[i].port)
+ // should remove the listeners afterwards
+ }
+ )
+ }
+ catch (err) {
+ if (err.code !== 'EADDRINUSE') // err type ??
+ throw err
+ console.log(
+ `address ${listen[i].protocol}//${listen[i].host}:${listen[i].port} in use`
+ )
+ continue // leaves listen[i].server undefined, will retry
+ }
+ this.attach(server, listen[i].protocol)
+ if (this.socket_io !== undefined)
+ this.socket_io.attach(server)
+ listen[i].server = server
+ }
+
+ this.listen = listen
}
Server.prototype.serve = function(response, status, mime_type, data, message) {
let site_factory_default = async (server, root) => new Site(server, root)
Server.prototype.respond = async function(request, response, protocol) {
+ let sites = await this.get_jst('.', '/_config/sites.jst')
try {
- await this.refresh_config()
let parsed_url = url.parse(
protocol + '//' + (request.headers.host || 'localhost') + request.url,
true
//console.log('parsed_url', parsed_url)
if (
- !Object.prototype.hasOwnProperty.call(this.sites, parsed_url.hostname)
+ !Object.prototype.hasOwnProperty.call(sites, parsed_url.hostname)
) {
this.die(
response,
return
}
- let site = this.sites[parsed_url.hostname]
+ let site = sites[parsed_url.hostname]
site.respond(
{
mime_type: this.mime_type_default,