let fs_readFile = util.promisify(fs.readFile)
let fs_stat = util.promisify(fs.stat)
-let Site = function(server, root) {
+let Site = function(resources, root, options) {
if (!this instanceof Site)
throw new Error('Site is a constructor')
- this.server = server
- this.socket_io_connect_listeners = [] // later will use this for destruction
+ this.resources = resources
this.root = root
+ this.options = {
+ caching: false,
+ mime_types: {
+ '.css': 'text/css; charset=utf-8',
+ '.html': 'text/html; charset=utf-8',
+ '.ico': 'image/x-icon',
+ '.jpg': 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.js': 'application/javascript; charset=utf-8',
+ '.json': 'application/json; charset=utf-8',
+ '.png': 'image/png',
+ '.svg': 'image/svg+xml',
+ '.xml': 'text/xml; charset=utf-8'
+ }
+ }
+ if (options !== undefined)
+ Object.assign(this.options, options)
- this.json_cache = server.resources.ref(
+ this.socket_io_connect_listeners = [] // later will use this for destruction
+ this.json_cache = resources.ref(
'json_cache',
() => new JSONCache(true)
)
- this.json_cache_rw = server.resources.ref(
+ this.json_cache_rw = resources.ref(
'json_cache_rw',
() => new JSONCacheRW(true)
)
- this.jst_cache = server.resources.ref(
+ this.jst_cache = resources.ref(
`jst_cache:${root}`,
() => new JSTCache(root, {_jst_server: jst_server}, true)
)
- this.less_css_cache = server.resources.ref(
+ this.less_css_cache = resources.ref(
`less_css_cache:${root}`,
() => new LessCSSCache(root, true)
)
- this.min_css_cache = server.resources.ref(
+ this.min_css_cache = resources.ref(
'min_css_cache',
() => new MinCSSCache(true)
)
- this.min_js_cache = server.resources.ref(
+ this.min_js_cache = resources.ref(
'min_js_cache',
() => new MinJSCache(true)
)
- this.min_html_cache = server.resources.ref(
+ this.min_html_cache = resources.ref(
'min_html_cache',
() => new MinHTMLCache(true)
)
- this.min_svg_cache = server.resources.ref(
+ this.min_svg_cache = resources.ref(
'min_svg_cache',
() => new MinSVGCache(true)
)
- this.text_cache = server.resources.ref(
+ this.text_cache = resources.ref(
'text_cache',
() => new TextCache(true)
)
- this.zip_cache = server.resources.ref(
+ this.zip_cache = resources.ref(
'zip_cache',
() => new ZipCache(true)
)
Site.prototype = Object.create(Site.prototype)
-Site.prototype.serve = function(env, status, data, message_from) {
- this.server.serve(
- env.response,
- status,
- env.mime_type,
- data,
- `${env.parsed_url.host} serving ${env.pathname} size ${data.length} from ${message_from}`
+Site.prototype.serve_internal = function(response, status, mime_type, data) {
+ response.statusCode = status
+ // html files will be direct recipient of links/bookmarks so can't have
+ // a long lifetime, other files like css or images are often large files
+ // and won't change frequently (but we'll need cache busting eventually)
+ if (this.options.caching && mime_type !== this.options.mime_type['.html'])
+ response.setHeader('Cache-Control', 'max-age=3600')
+ response.setHeader('Content-Type', mime_type)
+ response.setHeader('Content-Length', data.length)
+ response.end(data)
+}
+
+Site.prototype.serve = function(env, status, data, from) {
+ console.log(
+ `${env.parsed_url.host} serving ${env.parsed_url.pathname} size ${data.length} from ${from}`
)
+ this.serve_internal(env.response, status, env.mime_type, data)
}
Site.prototype.die = function(env, message) {
- this.server.die(
+ console.log(`${env.parsed_url.host} ${message}`)
+ this.serve_internal(
env.response,
- env.pathname,
- `${env.parsed_url.host} ${message}`
+ 404,
+ this.options.mime_types['.html'],
+ Buffer.from(
+ `<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <title>404 Not Found</title>
+ </head>
+ <body style="font-family: sans-serif, serif, monospace; font-size: 16px;">
+ <h2>404 Not Found</h2>
+ <p>The document ${env.parsed_url.pathname} was not found.</p>
+ </body>
+</html>
+`
+ )
)
}
-Site.prototype.redirect = function(env, pathname) {
- this.server.redirect(
+Site.prototype.redirect = function(env, pathname, message) {
+ console.log(
+ `${env.parsed_url.host} redirecting ${env.parsed_url.pathname} to ${pathname}`
+ )
+ let location = pathname + (env.parsed_url.search || '')
+ response.statusCode = 301
+ response.setHeader('Location', location)
+ this.serve_internal(
env.response,
- pathname + (env.parsed_url.search || ''),
- `${env.parsed_url.host} redirecting ${env.pathname} to ${pathname}`
+ 301,
+ this.options.mime_types['.html'],
+ Buffer.from(
+ `<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html;charset=utf-8">
+ <title>301 Moved Permanently</title>
+ </head>
+ <body style="font-family: sans-serif, serif, monospace; font-size: 16px;">
+ <h2>301 Moved Permanently</h2>
+ <p>The document has moved <a href="${location}">here</a>.</p>
+ </body>
+</html>
+`
+ )
)
}
return
if (await this.serve_fs(env, pathname))
return
- this.die(env, `file not found ${env.pathname}`)
+ this.die(env, `file not found ${env.parsed_url.pathname}`)
}
Site.prototype.serve_dir = async function(env, pathname, components) {
env,
components.length > 1 ?
`not directory ${pathname}` :
- `unknown extension "${extension}" in ${env.pathname}`
+ `unknown extension "${extension}" in ${env.parsed_url.pathname}`
)
return
}
//console.log(`serve_path ${pathname} ${components}`)
if (components.length === 0) {
// directory without trailing slash
- this.redirect(env, env.pathname + '/index.html')
+ this.redirect(env, env.parsed_url.pathname + '/index.html')
return
}
if (components[0].length === 0) {
if (components.length > 1)
- this.die(env, `empty directory name in ${env.pathname}`)
+ this.die(env, `empty directory name in ${env.parsed_url.pathname}`)
else
// directory with trailing slash
- this.redirect(env, env.pathname + 'index.html')
+ this.redirect(env, env.parsed_url.pathname + 'index.html')
return
}
components[0].charAt(0) === '.' ||
components[0].charAt(0) === '_'
) {
- this.die(env, `bad component "${components[0]}" in ${env.pathname}`)
+ this.die(env, `bad component "${components[0]}" in ${env.parsed_url.pathname}`)
return
}
pathname = `${pathname}/${components[0]}`
if (
extension.length !== 0 &&
- Object.prototype.hasOwnProperty.call(this.server.mime_types, extension)
+ Object.prototype.hasOwnProperty.call(this.options.mime_types, extension)
) {
if (components.length > 1) {
- this.die(env, `non-directory extension "${extension}" in ${env.pathname}`)
+ this.die(env, `non-directory extension "${extension}" in ${env.parsed_url.pathname}`)
return
}
return /*await*/ this.serve_file(env, pathname)
}
Site.prototype.respond = async function(env) {
+ env.mime_type = 'application/octet-stream'
+ let i = env.parsed_url.pathname.lastIndexOf('.')
+ if (i !== -1) {
+ let extension = env.parsed_url.pathname.slice(i)
+ if (
+ Object.prototype.hasOwnProperty.call(this.options.mime_types, extension)
+ )
+ env.mime_type = this.options.mime_types[extension]
+ }
if (
await this.serve_zip(
env,
this.root + '/_favicon/favicons.zip',
- env.pathname
+ env.parsed_url.pathname
)
)
return
- let components = env.pathname.split('/')
+ let components = env.parsed_url.pathname.split('/')
if (components.length) {
assert(components[0].length == 0)
components = components.slice(1)