simplehttpserver
Version:
Simple HTTP Server for static files. Intended as testing and development tool
200 lines (174 loc) • 5.73 kB
JavaScript
var express = require('express'),
path = require('path'),
url = require('url'),
fs = require('fs'),
async = require('async'),
send = require('send'),
https = require('https'),
escape = require('querystring').escape,
morgan = require('morgan');
// bodyparser = require('body-parser');
var mainapp = express();
// Bodyparser parses HTTP POST parameters and JSON payload
//mainapp.use(bodyparser);
// Logger for requests
mainapp.use(morgan("combined"));
var argv = require('yargs')
.usage('usage: $0 <-b bindhost> <-p port> <webroot>')
.help('h')
.option('b', {
alias: 'bind',
default: '0.0.0.0',
describe: 'Interface to bind',
type: 'string'
})
.option('p', {
alias: 'port',
default: 8000,
describe: 'Port to listen',
type: 'number'
})
.option('m', {
alias: 'mime',
describe: 'Define a custom mime type',
type: 'string'
})
.argv;
var bindhost = argv.b || null;
var bindport = argv.p || 8000;
// Serve either current directory or directory given as argument
var webroot = argv._[0] || process.cwd();
webroot = path.resolve( webroot );
// define additional mime types
express.static.mime.define({"application/wasm": ["wasm"]});
express.static.mime.define({"text/cache-manifest": ["appcache"]});
// Define custom mime types from cli args
for(var k in argv.mime) {
console.log("Custom mime types");
var def = {};
var suffix = argv.mime[k];
if(suffix != "") {
def[k] = suffix.split(',');
console.log("*." + def[k],"mime type",k);
express.static.mime.define(def)
}
}
mainapp.use(express.static( webroot ));
// Add any dynamic handlers here
//mainapp.get('/ajax', function(req, res) {
// res.send('Query: ' + util.inspect(req.query));
//});
//mainapp.post('/test', function(req, res) {
// res.send('Parameters: ' + util.inspect(req.body));
//});
// Catch all function when static server did not find any file to serve. In case requested
// file matched directory, this tries to find first index.html and if that fails it builds
// the directory listing.
mainapp.get('*', function(req, res) {
var pathname = url.parse(req.url).pathname;
// check that pathname does not contain relative elements
// e.g.
// ../foo/bar
// /../foo/bar
// /foo/../bar
// /foo/..
if(pathname.search(/(\/|^)\.\.(\/|$)/) != -1) {
return res.sendStatus(404);
}
pathname = path.join(webroot, pathname);
// check that the requested path resides inside the webroot
var relative = path.relative(webroot, pathname);
// following check allows filenames like '...'
if(relative.startsWith(".." + path.sep) || relative == "..") {
// requested path is outside webroot
return res.sendStatus(404);
}
fs.stat(pathname, function(err, stat) {
// Check if path is directory
if ( !stat || !stat.isDirectory() ) return res.sendStatus(404);
// check for index.html
var indexpath = path.join(pathname, 'index.html');
fs.stat(indexpath, function(err, stat) {
if ( stat && stat.isFile() ) {
// index.html was found, serve that
send(res, indexpath)
.pipe(res);
return;
} else {
// No index.html found, build directory listing
fs.readdir(pathname, function(err, list) {
if ( err ) return res.send(404);
return directoryHTML( res, req.url, pathname, list );
});
}
});
});
});
function htmlsafe( str ) {
var tbl = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
var safestr = '';
for(var i=0; i < str.length; i++) {
safestr += tbl[str[i]] || str[i];
}
return safestr;
}
// Reads directory content and builds HTML response
function directoryHTML( res, urldir, pathname, list ) {
var ulist = [];
function sendHTML( list ) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.send('<!DOCTYPE html>' +
'<html>\n' +
'<title>Directory listing for '+htmlsafe(urldir)+'</title>\n' +
'<body>\n' +
'<h2>Directory listing for '+htmlsafe(urldir)+'</h2>\n' +
'<hr><ul>\n' +
list.join('\n') +
'</ul><hr>\n' +
'</body>\n' +
'</html>');
}
if ( !list.length ) {
// Nothing to resolve
return sendHTML( ulist );
}
// Check for each file if it's a directory or a file
var q = async.queue(function(item, cb) {
fs.stat(path.join(pathname, item), function(err, stat) {
if ( !stat ) cb();
var link = escape(item);
item = htmlsafe(item);
if ( stat.isDirectory() ) {
ulist.push('<li><a href="'+link+'/">'+item+'/</a></li>')
} else {
ulist.push('<li><a href="'+link+'">'+item+'</a></li>')
}
cb();
});
}, 4);
list.forEach(function(item) {
q.push(item);
});
q.drain = function() {
// Finished checking files, send the response
sendHTML(ulist);
};
}
// Fire up server
mainapp.listen(bindport, bindhost);
console.log('Listening ' + bindhost + ':' + bindport +' web root dir ' + webroot );
/*
var options = {
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt'),
};
var server = https.createServer(options, mainapp).listen(8090, function(err) {
console.log('Listening SSL port 8090 status:', err);
});
*/