xserver
Version:
A simple http server.
198 lines (178 loc) • 5.26 kB
JavaScript
var fs = require("fs");
var http = require("http");
var https = require("https");
var path = require("path");
var Directive = require("./directive");
function XServer ( config ) {
this.directories = config.directories || [];
this.port = config.port || 80;
this.certs = config.certificates;
this.routes = {};
this.maxRequestSize = config.maxRequestSize || 2000000;
this.temporaryDirectory = fs.realpathSync(config.temporaryDirectory || ".");
}
XServer.prototype.start = function () {
this.stop();
var listener = this.onTransaction.bind(this);
this.server = this.certs ? https.createServer(this.certs, listener) : this.server = http.createServer(listener);
this.server.listen(this.port);
};
XServer.prototype.stop = function () {
if ( this.server ) {
this.server.close();
delete this.server;
}
};
// method, regex, [size], [download], calls...
XServer.prototype.route = function () {
var args = Array.prototype.slice.call(arguments);
var method = String(args[0]).toUpperCase();
var index = 2;
var opts = {
regex: args[1],
size: this.maxRequestSize,
download: false
};
if ( args.length > 3 && typeof args[2] == "number" ) {
index++;
opts.size = args[2];
}
if ( args.length > 4 && typeof args[3] == "boolean" ) {
index++;
opts.download = args[3];
}
if ( !this.routes[method] ) {
this.routes[method] = {};
}
opts.methods = args.slice(index, args.length);
this.routes[method][opts.regex] = opts;
};
XServer.prototype.getRoute = function ( directive ) {
var routes = this.routes[directive.request.method]
for ( key in routes ) {
var rx = routes[key].regex;
if ( typeof rx === "string" ) {
rx = new RegExp(rx);
}
uri = directive.url.value.substr(1, directive.url.value.length);
if ( rx.test && rx.test(uri) ) {
var params = rx.exec(uri);
params.splice(0, 1);
return {
chain: routes[key].methods,
params: params,
download: routes[key].download,
size: routes[key].size
};
}
}
};
XServer.prototype.runUntilSent = function ( directive, arr, execute ) {
if ( directive.sent ) return;
if ( !arr.length && !directive.sent ) return directive.send();
execute(arr.shift(), (function () {
this.runUntilSent(directive, arr, execute);
}).bind(this));
};
XServer.prototype.finishRouteDirective = function ( route, directive ) {
this.runUntilSent(directive, route.chain.slice(), function ( process, next ) {
directive.next = next;
process.apply(process, [directive].concat(route.params));
});
};
XServer.prototype.limitRequestSize = function ( directive, limit, abort ) {
var size = 0;
var call = function ( chunk ) {
size += chunk.length;
if ( size > limit ) {
directive.request.removeListener("data", call);
abort();
}
};
directive.request.on("data", call);
};
XServer.prototype.respondToUpload = function ( route, directive ) {
var name = String((new Date()).getTime())
var uri = path.join(this.temporaryDirectory, name);
var stream = fs.createWriteStream(uri);
var onEnd = (function () {
this.finishRouteDirective(route, directive)
}).bind(this);
directive.fileUri = uri;
directive.request.pipe(stream);
directive.request.once("end", onEnd);
this.limitRequestSize(directive, route.size, function () {
directive.request.removeListener("end", onEnd);
directive.sendStatus(413);
});
};
XServer.prototype.respondToRoute = function ( route, directive ) {
var clean = function () {
directive.request.removeListener("end", onEnd);
directive.request.removeListener("data", onData);
}
var onData = function ( chunk ) {
if ( directive.body ) {
directive.body += chunk;
}
else {
directive.body = chunk;
}
};
var onEnd = (function () {
clean();
this.finishRouteDirective(route, directive);
}).bind(this);
directive.request.on("data", onData);
directive.request.on("end", onEnd);
this.limitRequestSize(directive, route.size, function () {
clean();
directive.sendStatus(413);
});
};
XServer.prototype.respondWithFile = function ( directive ) {
directive.setStatus(404);
this.runUntilSent(directive, this.directories.slice(), function ( directory, next ) {
var path = directory + directive.url.value;
directive.prepareFile(path, function ( err ) {
if ( err ) return next();
directive.send();
});
});
};
XServer.prototype.onTransaction = function( request, response ) {
var directive = new Directive(request, response);
var route;
if ( route = this.getRoute(directive) ) {
if ( route.download ) {
this.respondToUpload(route, directive);
}
else {
this.respondToRoute(route, directive);
}
}
else {
this.respondWithFile(directive);
}
};
var methods = [
"connect",
"delete",
"get",
"head",
"options",
"patch",
"post",
"put",
"trace"
];
for ( var i = 0; i < methods.length; i++ ) {
var method = methods[i];
XServer.prototype[method] = (function ( method ) {
return function () {
var args = Array.prototype.slice.call(arguments);
this.route.apply(this, [method].concat(args));
}
})(method);
}
module.exports = XServer;