1 let BuildCache = require('BuildCache')
2 let JSONCache = require('JSONCache')
3 let Site = require('./Site')
4 let assert = require('assert')
6 let js_template = require('js_template')
7 let url = require('url')
8 let util = require('util')
10 let fs_readFile = util.promisify(fs.readFile)
12 let Server = function(socket_io, caching) {
13 if (!this instanceof Server)
14 throw Error('Server is a constructor')
16 this.socket_io = socket_io
17 this.caching = caching || false
20 this.build_cache_email = new BuildCache()
21 this.build_cache_json = new BuildCache()
22 this.build_cache_less = new BuildCache()
23 this.build_cache_text = new BuildCache()
24 this.build_cache_zet = new BuildCache()
25 this.build_cache_zip = new BuildCache()
26 this.json_cache = new JSONCache(true)
28 this.sites = undefined
29 this.mime_types = undefined
30 this.mime_type_html = undefined
31 this.mime_type_default = 'application/octet-stream'
34 Server.prototype.attach = function(server, protocol) {
37 (request, response) =>
38 this.respond(request, response, protocol) // ignore returned promise
40 if (this.socket_io !== undefined)
41 this.socket_io.attach(server)
44 Server.prototype.refresh_config = async function() {
45 this.sites = await this.build_cache_json.get(
48 result.value = JSON.parse(await fs_readFile('config/sites.json'))
51 this.mime_types = await this.build_cache_json.get(
52 'config/mime_types.json',
54 result.value = JSON.parse(await fs_readFile('config/mime_types.json'))
58 Object.prototype.hasOwnProperty.call(this.mime_types, '.html') ?
59 this.mime_types['.html'] :
60 this.mime_type_default
63 Server.prototype.serve = function(response, status, mime_type, data) {
64 response.statusCode = status
65 // html files will be direct recipient of links/bookmarks so can't have
66 // a long lifetime, other files like css or images are often large files
67 // and won't change frequently (but we'll need cache busting eventually)
68 if (this.caching && mime_type !== this.mime_types_html)
69 response.setHeader('Cache-Control', 'max-age=3600')
70 response.setHeader('Content-Type', mime_type)
71 response.setHeader('Content-Length', data.length)
75 Server.prototype.die = function(response) {
76 let body = '<html><body>Page not found</body></html>'
77 this.serve(response, 404, this.mime_type_html, Buffer.from(body))
80 Server.prototype.redirect = function(response, location) {
81 response.statusCode = 301
82 response.setHeader('Location', location)
83 response.end('Redirecting to ' + location)
86 let site_factory_default = async root => new Site(root)
88 Server.prototype.respond = async function(request, response, protocol) {
90 await this.refresh_config()
91 let parsed_url = url.parse(
92 protocol + '//' + (request.headers.host || 'localhost') + request.url,
95 //console.log('parsed_url', parsed_url)
97 if (!Object.prototype.hasOwnProperty.call(this.sites, parsed_url.hostname)) {
98 console.log('nonexistent site', parsed_url.hostname)
102 let temp = this.sites[parsed_url.hostname]
105 let hostname = temp.domain
106 if (parsed_url.port !== undefined)
107 hostname += ':' + parsed_url.port
108 console.log('redirecting', parsed_url.host, 'to', hostname)
109 this.redirect(response, parsed_url.protocol + '//' + hostname + request.url)
114 site_factory = await js_template(
121 if (err.code !== 'ENOENT') // note: err.code might be undefined
123 site_factory = site_factory_default
127 !Object.prototype.hasOwnProperty.call(this.site_cache, temp.root) ||
128 (site = this.site_cache[temp.root]).factory !== site_factory
130 if (site !== undefined)
131 for (let i of site.object.socket_io_connect_listeners) {
132 assert(this.socket_io !== undefined)
133 this.socket_io.removeListener('connect', i)
136 factory: site_factory,
137 object: await site_factory(this, temp.root)
139 for (let i of site.object.socket_io_connect_listeners) {
140 assert(this.socket_io !== undefined)
141 this.socket_io.on('connect', i)
143 this.site_cache[temp.root] = site
145 await site.object.respond(
147 mime_type: this.mime_type_default,
148 parsed_url: parsed_url,
149 pathname: parsed_url.pathname,
163 let message = (err.stack || err.message).toString()
164 console.error(message)
165 let body = '<html><body><pre>' + message + '</pre></body></html>'
166 this.serve(response, 500, this.mime_type_html, Buffer.from(body, 'utf8'))
170 module.exports = Server