diff --git a/.gitignore b/.gitignore index eb03e3e..740a0a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules *.log +package-lock.json diff --git a/bin/serv.js b/bin/serv.js index baded62..33c37c1 100644 --- a/bin/serv.js +++ b/bin/serv.js @@ -19,7 +19,7 @@ const options = yargs 's': 'Use https - self signed keys', 'h2': 'Enable http2 protocol', 'l': 'Enable directory listing', - 'f': 'Enable fast mode(no compression, ETags, logging)', + 'f': 'Enable fast mode(no compression/ETags/logging)', }) .alias({ 'p': 'port', diff --git a/lib/helpers/https-redirect.js b/lib/helpers/https-redirect.js index 8616077..b9b3d1b 100644 --- a/lib/helpers/https-redirect.js +++ b/lib/helpers/https-redirect.js @@ -1,6 +1,5 @@ 'use strict'; - const HTTPSRedirect = (req, res, next) => { if (req.hostname === 'localhost') { return next(); diff --git a/lib/helpers/logger.js b/lib/helpers/logger.js index a92c29d..74d61fd 100644 --- a/lib/helpers/logger.js +++ b/lib/helpers/logger.js @@ -47,7 +47,7 @@ const logger = (req, res, next) => { }; // for outgoing response - const logHandler = (error, ee, event, args) => { + const logHandler = (error, ee, event) => { cleanup(); if (event === 'end' || event === 'finish') { diff --git a/lib/index.js b/lib/index.js index dbd7082..13eeb88 100644 --- a/lib/index.js +++ b/lib/index.js @@ -32,11 +32,20 @@ const certificateOptions = { */ class Serv { constructor(options) { - if (!options) { - options = Serv.DEFAULT_OPTIONS; + this.opts = Object.assign({}, Serv.DEFAULTS, options || {}); + + if (typeof this.opts.dir !== 'string') { + throw new Error('option.dir is not a string'); + } + + if (typeof this.opts.port !== 'number') { + throw new Error('option.port is not a number'); } - this.opts = Object.assign({}, options); + this.opts.compress = Boolean(this.opts.compress); + this.opts.secure = Boolean(this.opts.secure); + this.opts.http2 = Boolean(this.opts.http2); + this.opts.listing = Boolean(this.opts.listening); try { this.opts.port = Number.parseInt(this.opts.port, 10); @@ -52,7 +61,10 @@ class Serv { * @memberof Serv */ async start() { + // get an available port const PORT = await getPort(this.opts.port); + + // listen on 0.0.0.0, allows external requests const HOST = '::'; this.opts.port = PORT; @@ -95,7 +107,6 @@ class Serv { // A few settings for polka // disable etags, compression and logging in fast mode - if (!options.fast) { // Enable SHA256 etags polkaApp.use(addETag); @@ -120,12 +131,16 @@ class Serv { polkaApp.use(options.logger); } - const staticHandler = staticServe(staticAssetPath, { - dotfiles: 'allow', + // can override dotfiles, cannot override etags + const handlerOpts = Object.assign({ + dotfile: 'allow', + }, this.opts, { etag: false, listing: options.listing, }); + const staticHandler = staticServe(staticAssetPath, handlerOpts); + polkaApp.use(staticHandler); return polkaApp; @@ -182,6 +197,9 @@ class Serv { return polkaApp; } + /** + * Stop this serv instance + */ async stop() { if (this.server && this.server.listening) { this.server.close(); @@ -194,7 +212,7 @@ class Serv { * @readonly * @memberof Serv */ - get DEFAULT_OPTIONS() { + get DEFAULTS() { return { dir: '.', port: 8080, @@ -212,7 +230,8 @@ class Serv { * @memberof Serv */ get options() { - return this.opts; + // return a copy only + return Object.assign({}, this.opts); } } diff --git a/lib/static-serve.js b/lib/static-serve.js index 08189cd..4197987 100644 --- a/lib/static-serve.js +++ b/lib/static-serve.js @@ -12,6 +12,9 @@ const parseUrl = require('parseurl'); /** * send a reponse with additional headers + * + * @param {Object} res http.ServerResponse + * @param {string} body data to send */ const sendResponse = (res, body) => { res.setHeader('X-Content-Type-Options', 'nosniff'); @@ -41,6 +44,9 @@ const send505 = (res) => { /** * returns HTML containing listing of a directory * + * @param {string} root path to the root of directory + * @param {string} pathname relative path to root + * * @returns {string} */ const getListing = async (root, pathname) => { @@ -119,6 +125,12 @@ const getListing = async (root, pathname) => { return body; }; +/** + * return a function to execute when send hits a directory + * + * @param {Object} opts serv options + * @param {Object} req http.ServerRequest + */ const onSendDirectory = (opts, req) => { return async function onDirectory(res) { if (opts.listing) { @@ -129,8 +141,14 @@ const onSendDirectory = (opts, req) => { }; }; +/** + * return a function to execute when send fails + * + * @param {Object} res http.ServerResponse + */ const onSendError = (res) => { return async function onError(e) { + // send 404 for known errors if (e.code === 'ENOENT' || e.code === 'ENAMETOOLONG' || e.code === 'ENOTDIR') { return send404(res); } @@ -139,18 +157,24 @@ const onSendError = (res) => { }; }; -const serveStatic = (root, options) => { - let opts = Object.assign({}, options || {}); +/** + * return a http handler + * + * @param {string} root path to serve + * @param {Object} options serv options + */ +const serveStatic = (root, options = {}) => { + let opts = Object.assign({}, options); // setup options for send opts.maxage = opts.maxage || opts.maxAge || 0; opts.root = path.resolve(root); opts.index = ['index.html', 'index.htm']; - opts.dotfiles = 'allow'; return async function(req, res) { res.req = req; + // no no for other type of requests if (req.method !== 'GET' && req.method !== 'HEAD') { res.statusCode = 405; res.setHeader('Allow', 'GET, HEAD'); diff --git a/package.json b/package.json index fde8eb4..f9e5a18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ramlmn/serv", - "version": "0.1.2", + "version": "0.2.0", "description": "Simple development server for static files", "license": "MIT", "repository": "ramlmn/serv", @@ -23,7 +23,7 @@ "polka": "^0.3.3", "send": "^0.16.2", "uuid": "^3.2.1", - "yargs": "^11.0.0" + "yargs": "^11.1.0" }, "yargs": { "short-option-groups": false