var events = require('events'),1qs = require('querystring'),2util = require('util'),3director = require('../../director'),4responses = require('./responses');56//7// ### Expose all HTTP methods and responses8//9exports.methods = require('./methods');10Object.keys(responses).forEach(function (name) {11exports[name] = responses[name];12})1314//15// ### function Router (routes)16// #### @routes {Object} **Optional** Routing table for this instance.17// Constuctor function for the HTTP Router object responsible for building18// and dispatching from a given routing table.19//20var Router = exports.Router = function (routes) {21//22// ### Extend the `Router` prototype with all of the RFC methods.23//24this.params = {};25this.routes = {};26this.methods = ['on', 'after', 'before'];27this.scope = [];28this._methods = {};29this.recurse = 'forward';30this._attach = [];3132this.extend(exports.methods.concat(['before', 'after']));33this.configure();34this.mount(routes || {});35};3637//38// Inherit from `director.Router`.39//40util.inherits(Router, director.Router);4142//43// ### function on (method, path, route)44// #### @method {string} **Optional** Method to use45// #### @path {string} Path to set this route on.46// #### @route {Array|function} Handler for the specified method and path.47// Adds a new `route` to this instance for the specified `method`48// and `path`.49//50Router.prototype.on = function (method, path) {51var args = Array.prototype.slice.call(arguments, 2),52route = args.pop(),53options = args.pop();5455if (options && options.stream) {56route.stream = true;57}5859director.Router.prototype.on.call(this, method, path, route);60};6162//63// ### function attach (func)64// ### @func {function} Function to execute on `this` before applying to router function65// Ask the router to attach objects or manipulate `this` object on which the66// function passed to the http router will get applied67Router.prototype.attach = function (func) {68this._attach.push(func);69};7071//72// ### function dispatch (method, path)73// #### @req {http.ServerRequest} Incoming request to dispatch.74// #### @res {http.ServerResponse} Outgoing response to dispatch.75// #### @callback {function} **Optional** Continuation to respond to for async scenarios.76// Finds a set of functions on the traversal towards77// `method` and `path` in the core routing table then78// invokes them based on settings in this instance.79//80Router.prototype.dispatch = function (req, res, callback) {81//82// Dispatch `HEAD` requests to `GET`83//84var method = req.method === 'HEAD' ? 'get' : req.method.toLowerCase(),85url = decodeURI(req.url.split('?', 1)[0]),86fns = this.traverse(method, url, this.routes, ''),87thisArg = { req: req, res: res },88self = this,89runlist,90stream,91error;9293if (this._attach) {94for (var i in this._attach) {95this._attach[i].call(thisArg);96}97}9899if (!fns || fns.length === 0) {100error = new exports.NotFound('Could not find path: ' + req.url)101if (typeof this.notfound === 'function') {102this.notfound.call(thisArg, callback);103}104else if (callback) {105callback.call(thisArg, error, req, res);106}107return false;108}109110if (this.recurse === 'forward') {111fns = fns.reverse();112}113114runlist = this.runlist(fns);115stream = runlist.some(function (fn) { return fn.stream === true });116117function parseAndInvoke() {118error = self.parse(req);119if (error) {120if (callback) {121callback.call(thisArg, error, req, res);122}123return false;124}125126self.invoke(runlist, thisArg, callback);127}128129if (!stream) {130//131// If there is no streaming required on any of the functions on the132// way to `path`, then attempt to parse the fully buffered request stream133// once it has emitted the `end` event.134//135if (req.readable) {136//137// If the `http.ServerRequest` is still readable, then await138// the end event and then continue139//140req.once('end', parseAndInvoke)141}142else {143//144// Otherwise, just parse the body now.145//146parseAndInvoke();147}148}149else {150this.invoke(runlist, thisArg, callback);151}152153return true;154};155156//157// ### @parsers {Object}158// Lookup table of parsers to use when attempting to159// parse incoming responses.160//161Router.prototype.parsers = {162'application/x-www-form-urlencoded': qs.parse,163'application/json': JSON.parse164};165166//167// ### function parse (req)168// #### @req {http.ServerResponse|BufferedStream} Incoming HTTP request to parse169// Attempts to parse `req.body` using the value found at `req.headers['content-type']`.170//171Router.prototype.parse = function (req) {172function mime(req) {173var str = req.headers['content-type'] || '';174return str.split(';')[0];175}176177var parser = this.parsers[mime(req)],178body;179180if (parser) {181req.body = req.body || '';182183if (req.chunks) {184req.chunks.forEach(function (chunk) {185req.body += chunk;186});187}188189try {190req.body = req.body && req.body.length191? parser(req.body)192: {};193}194catch (err) {195return new exports.BadRequest('Malformed data');196}197}198};199200201202