UNPKG

bogart-server

Version:
219 lines (178 loc) 4.92 kB
var q = require('q') , parseUrl = require('url').parse , util = require('util'); function createServer(app) { return new Server(app); } module.exports = createServer; // Conveniance shims createServer.when = q.when; createServer.defer = q.defer; function Server(app) { this.app = app; } Server.prototype.listen = function(port, host, opts) { opts = opts || {}; var listener = nodeListener(this.app); if (typeof port !== 'number') { opts = host; host = port; port = 8080; } if (typeof host !== 'string') { host = '127.0.0.1'; } if (opts.ssl) { require('https').createServer(opts.ssl, listener).listen(port); } else { require('http').createServer(listener).listen(port); } }; createServer.Request = Request; function nodeListener(app) { return function listener(request, response) { var req = new Request(request); process.nextTick(function() { var appRes = app(req); respond(response, appRes); }); } } /** * JSGI Request * * @param {Object} request Node.JS request * @constructor */ function Request(request) { var self = this; this.request = request; this.headers = request.headers; this.method = request.method; this.remoteAddr = request.connection.remoteAddress; if (this.method !== 'GET') { this.body = new InputStream(request); } } Request.prototype = { version: '0.3b', get requestUrl() { if (!this._parsedUrl) { this._parsedUrl = parseUrl(this.request.url); } return this._parsedUrl; }, get serverSoftware() { return 'Bogart Server v0.1.4'; } }; ['hostname','port','pathname','search','protocol','auth'].forEach(function(x) { Object.defineProperty(Request.prototype, x, { get: function() { return this.requestUrl[x]; }, set: function(val) { this.requestUrl[x] = val; } }); }); /** * Create an Input Stream for a request. * * @param {Object} request Node request. * @constructor */ function InputStream(request) { var inputBuffer = [] , deferred = createServer.defer(); var onData = function(data) { inputBuffer.push(data); } var onEnd = function() { request.removeAllListeners('data'); request.removeListener('end', onEnd); deferred.resolve(); }; request.connection.on('close', function() { request.removeAllListeners('data'); request.removeAllListeners('end'); }); request.on('data', onData).on('end', onEnd); this.forEach = function(callback) { if (this.encoding) { request.setBodyEncoding(this.encoding); } inputBuffer.forEach(callback); request.removeListener('data', onData); request.on('data', function(data) { callback(data); }); return deferred.promise; }; } /** * Like Array.prototype.join but the default seperator is empty string. * * @param {String} seperator An optional seperator, defaults to empty string. * @returns {Promise} A promise for the joined stream. */ InputStream.prototype.join = function(seperator) { var chunks = [] , deferred = createServer.defer(); deferred.resolve(createServer.when(this.forEach(function(chunk) { chunks.push(chunk); }), function() { return chunks.join(seperator || ''); })); return deferred.promise; }; /** * * * @param {Object} response Node response. * @param {Object} jsgiRes JSGI Response. * @returns {Function} A Function that processes a JSGI Response. */ function respond(response, jsgiRes) { var forEachResult; function writeError(err) { response.writeHead(500, { 'content-type': 'text/html' }); response.write('<html><head><title>Error</title></head><body><div>'); response.write(err.message); response.write('<br /><br />JSGI Response:<br />'); response.write(util.inspect(jsgiRes)); response.write('</div></body>'); response.end(); } if (jsgiRes.then && typeof jsgiRes.then === 'function') { return jsgiRes.then(function(jsgiRes) { return respond(response, jsgiRes); }); } if (!jsgiRes.status) { return writeError(new Error('JSGI Response must have `status` property.')); } if (!jsgiRes.headers) { return writeError(new Error('JSGI Response must have `headers` property.')); } if (!jsgiRes.body) { return writeError(new Error('JSGI Response must have `body` property.')); } response.writeHead(jsgiRes.status, jsgiRes.headers); if (typeof jsgiRes.body.forEach !== 'function') { throw new Error('The body does not have a forEach function'); } forEachResult = jsgiRes.body.forEach(function(chunk) { response.write(chunk, jsgiRes.body.encoding || 'utf8'); }); if (forEachResult && typeof forEachResult.then === 'function') { forEachResult.then(function() { response.end(); }, function(err) { console.log('error writing', err); response.end(); }); } else { response.end(); } }