let fs_readFile = util.promisify(fs.readFile)
let yauzl_open = util.promisify(yauzl.open)
-let Server = function(socket_io, caching) {
+let Server = function(sites, enable_socket_io, enable_caching) {
if (!this instanceof Server)
throw Error('Server is a constructor')
- this.socket_io = socket_io
- this.caching = caching || false
- this.site_cache = {}
+ 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.build_cache_email = new BuildCache()
this.build_cache_json = new BuildCache()
this.build_cache_zip = new BuildCache()
this.json_cache = new JSONCache(true)
- this.sites = undefined
this.mime_types = undefined
this.mime_type_html = undefined
this.mime_type_default = 'application/octet-stream'
}
-Server.prototype.attach = function(server, protocol) {
+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) =>
}
Server.prototype.refresh_config = async function() {
- this.sites = await this.get_json('_config/sites.json')
this.mime_types = await this.get_json('_config/mime_types.json')
this.mime_type_html =
Object.prototype.hasOwnProperty.call(this.mime_types, '.html') ?
)
//console.log('parsed_url', parsed_url)
- if (!Object.prototype.hasOwnProperty.call(this.sites, parsed_url.hostname)) {
+ if (
+ !Object.prototype.hasOwnProperty.call(this.sites, parsed_url.hostname)
+ ) {
this.die(
response,
parsed_url.pathname,
)
return
}
- let temp = this.sites[parsed_url.hostname]
- switch (temp.type) {
- case 'redirect':
- let hostname = temp.domain
- if (parsed_url.port !== undefined)
- hostname += ':' + parsed_url.port
- this.redirect(
- response,
- `${parsed_url.protocol}//${hostname}${request.url}`,
- `redirecting ${parsed_url.host} to ${hostname}`
- )
- break
- case 'site':
- let site_factory
- try {
- site_factory = await this.get_jst(temp.root, 'site_factory.jst')
- }
- catch (err) {
- if (err.code !== 'ENOENT') // note: err.code might be undefined
- throw err
- site_factory = site_factory_default
- }
- let site = undefined
- if (
- !Object.prototype.hasOwnProperty.call(this.site_cache, temp.root) ||
- (site = this.site_cache[temp.root]).factory !== site_factory
- ) {
- if (site !== undefined)
- for (let i of site.object.socket_io_connect_listeners) {
- assert(this.socket_io !== undefined)
- this.socket_io.removeListener('connect', i)
- }
- site = {
- factory: site_factory,
- object: await site_factory(this, temp.root)
- }
- for (let i of site.object.socket_io_connect_listeners) {
- assert(this.socket_io !== undefined)
- this.socket_io.on('connect', i)
- }
- this.site_cache[temp.root] = site
+
+ let site = this.sites[parsed_url.hostname]
+ site.respond(
+ {
+ mime_type: this.mime_type_default,
+ parsed_url: parsed_url,
+ pathname: parsed_url.pathname,
+ pathname_pos: 0,
+ response: response,
+ request: request,
+ status: 200,
+ site: site
}
- await site.object.respond(
- {
- mime_type: this.mime_type_default,
- parsed_url: parsed_url,
- pathname: parsed_url.pathname,
- pathname_pos: 0,
- response: response,
- request: request,
- status: 200,
- site: site.object
- }
- )
- break
- default:
- assert(false)
- }
+ )
}
catch (err) {
let message = (err.stack || err.message).toString()
-let assert = require('assert')
-let fs = require('fs')
-let js_template = require('js_template')
-let util = require('util')
-
-let fs_readFile = util.promisify(fs.readFile)
-let fs_stat = util.promisify(fs.stat)
-
-let Site = function(server, root) {
+let Site = function(server) {
if (!this instanceof Site)
throw Error('Site is a constructor')
this.server = server
- this.root = root
- this.socket_io_connect_listeners = []
+ this.socket_io_connect_listeners = [] // later will use this for destruction
}
Site.prototype.serve = function(env, data, message_from) {
)
}
-Site.prototype.get_email = function(pathname) {
- return /*await*/ this.server.get_email(this.root + pathname)
-}
-Site.prototype.get_json = function(pathname) {
- return /*await*/ this.server.get_json(this.root + pathname)
-}
-Site.prototype.get_jst = function(pathname) {
- return /*await*/ this.server.get_jst(this.root, this.root + pathname)
-}
-Site.prototype.get_less = function(pathname) {
- return /*await*/ this.server.get_less(this.root, this.root + pathname)
-}
-Site.prototype.get_text = function(pathname) {
- return /*await*/ this.server.get_text(this.root + pathname)
-}
-Site.prototype.get_zet = function(pathname) {
- return /*await*/ this.server.get_zet(this.root + pathname)
-}
-Site.prototype.get_zip = function(pathname) {
- return /*await*/ this.server.get_zip(this.root + pathname)
-}
-
-Site.prototype.ensure_dir = async function(pathname) {
- return /*await*/ this.server.ensure_dir(this.root + pathname)
-}
-
-// this is for read/write JSON files
-// they will not be reloaded from disk if modified
-Site.prototype.read_json = async function(pathname, default_value) {
- return /*await*/ this.server.read_json(
- this.root + pathname,
- default_value
- )
-}
-Site.prototype.write_json = async function(pathname, value, timeout) {
- return /*await*/ this.server.write_json(
- this.root + pathname,
- value,
- timeout
- )
-}
-Site.prototype.modify_json =
- async function(pathname, default_value, modify_func, timeout) {
- return /*await*/ this.server.modify_json(
- this.root + pathname,
- default_value,
- modify_func,
- timeout
- )
- }
-
-Site.prototype.serve_jst = async function(env, pathname) {
- let jst
- try {
- jst = await this.get_jst(pathname)
- }
- catch (err) {
- if (err.code !== 'ENOENT')
- throw err
- return false
- }
- await jst(env)
- return true
-}
-
-Site.prototype.serve_less = async function(env, pathname) {
- if (env.pathname.slice(env.pathname_pos) !== '.css')
- return false
-
- let data
- try {
- data = await this.get_less(pathname)
- }
- catch (err) {
- if (err.code !== 'ENOENT')
- throw err
- return false
- }
- this.serve(env, data, 'less')
- return true
-}
-
-Site.prototype.serve_fs = async function(env, pathname) {
- let data
- try {
- data = await fs_readFile(this.root + pathname)
- }
- catch (err) {
- if (err.code !== 'ENOENT')
- throw err
- return false
- }
- this.serve(env, data, 'fs')
- return true
-}
-
-
-Site.prototype.serve_zip = async function(env, pathname) {
- let zip
- try {
- zip = await this.get_zip(pathname)
- }
- catch (err) {
- if (err.code !== 'ENOENT')
- throw err
- return false
- }
- if (!Object.prototype.hasOwnProperty.call(zip, env.pathname))
- return false
- this.serve(env, zip[env.pathname], 'zip')
- return true
-}
-
Site.prototype.respond = async function(env) {
- while (true) {
- if (env.pathname_pos >= env.pathname.length) {
- // directory without trailing slash
- this.redirect(env, env.pathname + '/index.html')
- return
- }
-
- assert(env.pathname.charAt(env.pathname_pos) === '/')
- let i = env.pathname_pos + 1
- let j = env.pathname.indexOf('/', i)
- if (j === -1)
- j = env.pathname.length
- let filename = env.pathname.slice(i, j)
-
- if (filename.length === 0) {
- if (j >= env.pathname.length)
- // directory with trailing slash
- this.redirect(env, env.pathname + 'index.html')
- else
- this.die(env, `empty directory name in ${env.pathname}`)
- return
- }
-
- if (
- filename.charAt(0) === '.' ||
- filename.charAt(0) === '_'
- ) {
- this.die(env, `bad component "${filename}" in ${env.pathname}`)
- return
- }
-
- let k = filename.lastIndexOf('.')
- if (k === -1)
- k = filename.length
- let filetype = filename.slice(k)
-
- if (
- filetype.length !== 0 &&
- Object.prototype.hasOwnProperty.call(this.server.mime_types, filetype)
- ) {
- if (j < env.pathname.length) {
- this.die(env, `non-directory filetype "${filetype}" in ${env.pathname}`)
- return
- }
- env.mime_type = this.server.mime_types[filetype]
- env.pathname_pos = i + k // advance to "." at start of filetype
- break
- }
-
- env.pathname_pos = j
- let pathname = env.pathname.slice(0, env.pathname_pos)
- if (await this.serve_jst(env, pathname + '.dir.jst'))
- return
-
- let stats
- try {
- stats = await fs_stat(this.root + pathname)
- }
- catch (err) {
- if (err.code !== 'ENOENT')
- throw err
- this.die(env, `directory not found ${pathname}`)
- return
- }
- if (!stats.isDirectory()) {
- this.die(
- env,
- j < env.pathname.length ?
- `not directory ${pathname}` :
- `unknown filetype "${filetype}" in ${pathname}`
- )
- return
- }
- }
-
- if (
- !await this.serve_jst(env, env.pathname + '.jst') &&
- !await this.serve_less(env, env.pathname + '.less') &&
- !await this.serve_fs(env, env.pathname) &&
- !await this.serve_zip(env, '/_favicon/favicons.zip')
- ) {
- this.die(env, `file not found ${env.pathname}`)
- }
+ throw new Error('not implemented')
}
module.exports = Site
--- /dev/null
+let AbstractSite = require('AbstractSite')
+
+let Site = function(server, root, config_file) {
+ AbstractSite.call(this, server)
+ this.root =
+ this.new_host = new_host
+}
+
+Site.prototype = Object.create(AbstractSite.prototype)
+
+SiteRedirect.respond = async function(env) {
+ let new_host = this.new_host
+ if (parsed_url.port !== undefined)
+ new_host += ':' + parsed_url.port
+ this.redirect(
+ response,
+ `${parsed_url.protocol}//${new_host}${request.url}`,
+ `redirecting ${parsed_url.host} to ${new_host}`
+ )
+}
--- /dev/null
+let Site = require('./Site')
+
+let SiteRedirect = function(server, redirect) {
+ if (!this instanceof SiteRedirect)
+ throw Error('SiteRedirect is a constructor')
+ Site.call(this, server)
+ this.redirect = redirect
+}
+
+SiteRedirect.prototype = Object.create(Site.prototype)
+
+SiteRedirect.respond = async function(env) {
+ let new_host = this.redirect
+ if (parsed_url.port !== undefined)
+ new_host += ':' + parsed_url.port
+ this.redirect(
+ response,
+ `${parsed_url.protocol}//${redirect}${request.url}`,
+ `redirecting ${parsed_url.host} to ${new_host}`
+ )
+}
+
+module.exports = SiteRedirect
--- /dev/null
+let Site = require('./Site')
+let assert = require('assert')
+let fs = require('fs')
+let js_template = require('js_template')
+let util = require('util')
+
+let fs_readFile = util.promisify(fs.readFile)
+let fs_stat = util.promisify(fs.stat)
+
+let SiteRoot = function(server, root) {
+ if (!this instanceof SiteRoot)
+ throw Error('SiteRoot is a constructor')
+ Site.call(this, server)
+ this.root = root
+}
+
+SiteRoot.prototype = Object.create(Site.prototype)
+
+SiteRoot.prototype.get_email = function(pathname) {
+ return /*await*/ this.server.get_email(this.root + pathname)
+}
+SiteRoot.prototype.get_json = function(pathname) {
+ return /*await*/ this.server.get_json(this.root + pathname)
+}
+SiteRoot.prototype.get_jst = function(pathname) {
+ return /*await*/ this.server.get_jst(this.root, /*this.root +*/ pathname)
+}
+SiteRoot.prototype.get_less = function(pathname) {
+ return /*await*/ this.server.get_less(this.root, this.root + pathname)
+}
+SiteRoot.prototype.get_text = function(pathname) {
+ return /*await*/ this.server.get_text(this.root + pathname)
+}
+SiteRoot.prototype.get_zet = function(pathname) {
+ return /*await*/ this.server.get_zet(this.root + pathname)
+}
+SiteRoot.prototype.get_zip = function(pathname) {
+ return /*await*/ this.server.get_zip(this.root + pathname)
+}
+
+SiteRoot.prototype.ensure_dir = async function(pathname) {
+ return /*await*/ this.server.ensure_dir(this.root + pathname)
+}
+
+// this is for read/write JSON files
+// they will not be reloaded from disk if modified
+SiteRoot.prototype.read_json = async function(pathname, default_value) {
+ return /*await*/ this.server.read_json(
+ this.root + pathname,
+ default_value
+ )
+}
+SiteRoot.prototype.write_json = async function(pathname, value, timeout) {
+ return /*await*/ this.server.write_json(
+ this.root + pathname,
+ value,
+ timeout
+ )
+}
+SiteRoot.prototype.modify_json =
+ async function(pathname, default_value, modify_func, timeout) {
+ return /*await*/ this.server.modify_json(
+ this.root + pathname,
+ default_value,
+ modify_func,
+ timeout
+ )
+ }
+
+SiteRoot.prototype.serve_jst = async function(env, pathname) {
+ let jst
+ try {
+ jst = await this.get_jst(pathname)
+ }
+ catch (err) {
+ if (err.code !== 'ENOENT')
+ throw err
+ return false
+ }
+ await jst(env)
+ return true
+}
+
+SiteRoot.prototype.serve_less = async function(env, pathname) {
+ if (env.pathname.slice(env.pathname_pos) !== '.css')
+ return false
+
+ let data
+ try {
+ data = await this.get_less(pathname)
+ }
+ catch (err) {
+ if (err.code !== 'ENOENT')
+ throw err
+ return false
+ }
+ this.serve(env, data, 'less')
+ return true
+}
+
+SiteRoot.prototype.serve_fs = async function(env, pathname) {
+ let data
+ try {
+ data = await fs_readFile(this.root + pathname)
+ }
+ catch (err) {
+ if (err.code !== 'ENOENT')
+ throw err
+ return false
+ }
+ this.serve(env, data, 'fs')
+ return true
+}
+
+
+SiteRoot.prototype.serve_zip = async function(env, pathname) {
+ let zip
+ try {
+ zip = await this.get_zip(pathname)
+ }
+ catch (err) {
+ if (err.code !== 'ENOENT')
+ throw err
+ return false
+ }
+ if (!Object.prototype.hasOwnProperty.call(zip, env.pathname))
+ return false
+ this.serve(env, zip[env.pathname], 'zip')
+ return true
+}
+
+SiteRoot.prototype.respond = async function(env) {
+ while (true) {
+ if (env.pathname_pos >= env.pathname.length) {
+ // directory without trailing slash
+ this.redirect(env, env.pathname + '/index.html')
+ return
+ }
+
+ assert(env.pathname.charAt(env.pathname_pos) === '/')
+ let i = env.pathname_pos + 1
+ let j = env.pathname.indexOf('/', i)
+ if (j === -1)
+ j = env.pathname.length
+ let filename = env.pathname.slice(i, j)
+
+ if (filename.length === 0) {
+ if (j >= env.pathname.length)
+ // directory with trailing slash
+ this.redirect(env, env.pathname + 'index.html')
+ else
+ this.die(env, `empty directory name in ${env.pathname}`)
+ return
+ }
+
+ if (
+ filename.charAt(0) === '.' ||
+ filename.charAt(0) === '_'
+ ) {
+ this.die(env, `bad component "${filename}" in ${env.pathname}`)
+ return
+ }
+
+ let k = filename.lastIndexOf('.')
+ if (k === -1)
+ k = filename.length
+ let filetype = filename.slice(k)
+
+ if (
+ filetype.length !== 0 &&
+ Object.prototype.hasOwnProperty.call(this.server.mime_types, filetype)
+ ) {
+ if (j < env.pathname.length) {
+ this.die(env, `non-directory filetype "${filetype}" in ${env.pathname}`)
+ return
+ }
+ env.mime_type = this.server.mime_types[filetype]
+ env.pathname_pos = i + k // advance to "." at start of filetype
+ break
+ }
+
+ env.pathname_pos = j
+ let pathname = env.pathname.slice(0, env.pathname_pos)
+ if (await this.serve_jst(env, pathname + '.dir.jst'))
+ return
+
+ let stats
+ try {
+ stats = await fs_stat(this.root + pathname)
+ }
+ catch (err) {
+ if (err.code !== 'ENOENT')
+ throw err
+ this.die(env, `directory not found ${pathname}`)
+ return
+ }
+ if (!stats.isDirectory()) {
+ this.die(
+ env,
+ j < env.pathname.length ?
+ `not directory ${pathname}` :
+ `unknown filetype "${filetype}" in ${pathname}`
+ )
+ return
+ }
+ }
+
+ if (
+ !await this.serve_jst(env, env.pathname + '.jst') &&
+ !await this.serve_less(env, env.pathname + '.less') &&
+ !await this.serve_fs(env, env.pathname) &&
+ !await this.serve_zip(env, '/_favicon/favicons.zip')
+ ) {
+ this.die(env, `file not found ${env.pathname}`)
+ }
+}
+
+module.exports = SiteRoot
--- /dev/null
+let SiteRoot = require('./SiteRoot')
+
+let SiteRootConfig = function(server, root, config) {
+ if (!this instanceof SiteRootConfig)
+ throw Error('SiteRootConfig is a constructor')
+ SiteRoot.call(this, server, root)
+ this.config = config
+}
+
+SiteRootConfig.prototype = Object.create(SiteRoot.prototype)
+
+SiteRootConfig.prototype.respond = async function(env) {
+ let site
+ try {
+ site = await this.get_jst(this.config)
+ }
+ catch (err) {
+ if (err.code !== 'ENOENT') // error type??
+ throw err
+ // no custom site object, handle the request ourselves
+ return SiteRoot.prototype.respond.call(this, env)
+ }
+ return site.respond(env)
+}
+
+module.exports = SiteRootConfig
--- /dev/null
+let Server = require('../Server')
+let SiteRedirect = require('../SiteRedirect')
+let SiteRootConfig = require('../SiteRootConfig')
+
+let server = new Server(
+ true, // enable_socket_io
+ false // enable_caching
+)
+server.sites = {
+ 'localhost': new SiteRootConfig(server, 'site', '/_config/site.jst'),
+ 'localhost.localdomain': new SiteRedirect(server, 'localhost')
+}
+await server.listen(8080, 'http:')
+await server.listen(8443, 'https:')
+return server // later will use to destroy when _config/server.jst changes
+++ /dev/null
-{
- "localhost": {"type": "site", "root": "site"},
- "localhost.localdomain": {"type": "redirect", "domain": "localhost"}
-}
exports.Server = require('./Server')
exports.Site = require('./Site')
+exports.SiteRedirect = require('./SiteRedirect')
+exports.SiteRoot = require('./SiteRoot')
+exports.SiteRootConfig = require('./SiteRootConfig')
#!/usr/bin/env node
-let Server = require('./Server')
-let SocketIO = require('socket.io')
-let commander = require('commander')
-let fs = require('fs')
-let http = require('http')
-let https = require('https')
+let js_template = require('js_template')
-commander.version('1.0.0').option(
- '-c, --enable-caching',
- 'Enable caching'
-).option(
- '-j, --ssl-cert [path]',
- 'Set SSL certificate [_ssl/localhost_cert_bundle.pem]',
- '_ssl/localhost_cert_bundle.pem'
-).option(
- '-k, --ssl-key [path]',
- 'Set SSL private key [_ssl/localhost_key.pem]',
- '_ssl/localhost_key.pem'
-).option(
- '-p, --http-port [port]',
- 'Set HTTP listen port, -1 disable [8080]',
- 8080
-).option(
- '-q, --https-port [port]',
- 'Set HTTPS listen port, -1 disable [8443]',
- 8443
-).parse(process.argv)
-
-let server = new Server(new SocketIO(), commander.enableCaching)
-if (commander.httpPort !== -1) {
- let http_server = http.createServer()
- server.attach(http_server, 'http:')
- http_server.listen(commander.httpPort)
- console.log('HTTP server listening on port', commander.httpPort)
-}
-if (commander.httpsPort !== -1) {
- let https_server = https.createServer(
- {
- 'cert': fs.readFileSync(commander.sslCert),
- 'key': fs.readFileSync(commander.sslKey)
+;(
+ async () => {
+ try {
+ await js_template('.', '.', '/_config/server.jst')
+ }
+ catch (err) {
+ console.error(err.stack || err.message)
}
- )
- server.attach(https_server, 'https:')
- https_server.listen(commander.httpsPort)
- console.log('HTTPS server listening on port', commander.httpsPort)
-}
+ }
+)() // ignore returned promise