Comparing version 1.0.9 to 1.0.10
@@ -7,2 +7,7 @@ var http = require('http'), | ||
var server = http.createServer(function (req, res) { | ||
req.chunks = []; | ||
req.on('data', function (chunk) { | ||
req.chunks.push(chunk.toString()); | ||
}); | ||
router.dispatch(req, res, function (err) { | ||
@@ -13,2 +18,4 @@ if (err) { | ||
} | ||
console.log('Served ' + req.url); | ||
}); | ||
@@ -22,3 +29,8 @@ }); | ||
router.post(/foo/, function () { | ||
this.res.writeHead(200, { 'Content-Type': 'application/json' }) | ||
this.res.end(JSON.stringify(this.req.body)); | ||
}); | ||
server.listen(8080); | ||
console.log('vanilla http server with director running on 8080'); |
@@ -23,3 +23,3 @@ | ||
director.Router.prototype.configure.call(this, options); | ||
// | ||
@@ -38,3 +38,3 @@ // Delimiter must always be `\s` in CLI routing. | ||
// Finds a set of functions on the traversal towards | ||
// `method` and `path` in the core routing table then | ||
// `method` and `path` in the core routing table then | ||
// invokes them based on settings in this instance. | ||
@@ -46,3 +46,3 @@ // | ||
// algorithm will recognize it. This is because we always assume | ||
// that the `path` begins with `this.delimiter`. | ||
// that the `path` begins with `this.delimiter`. | ||
// | ||
@@ -58,12 +58,12 @@ path = ' ' + path; | ||
} | ||
return false; | ||
} | ||
if (this.recurse === 'forward') { | ||
fns = fns.reverse(); | ||
} | ||
this.invoke(fns, { tty: tty, cmd: path }, callback); | ||
this.invoke(this.runlist(fns), { tty: tty, cmd: path }, callback); | ||
return true; | ||
}; |
@@ -18,3 +18,3 @@ var events = require('events'), | ||
// #### @routes {Object} **Optional** Routing table for this instance. | ||
// Constuctor function for the HTTP Router object responsible for building | ||
// Constuctor function for the HTTP Router object responsible for building | ||
// and dispatching from a given routing table. | ||
@@ -32,3 +32,3 @@ // | ||
this.recurse = 'forward'; | ||
this.extend(exports.methods.concat(['before', 'after'])); | ||
@@ -46,3 +46,3 @@ this.configure(); | ||
// ### function on (method, path, route) | ||
// #### @method {string} **Optional** Method to use | ||
// #### @method {string} **Optional** Method to use | ||
// #### @path {string} Path to set this route on. | ||
@@ -57,17 +57,26 @@ // #### @route {Array|function} Handler for the specified method and path. | ||
options = args.pop(); | ||
if (options && options.stream) { | ||
route.stream = true; | ||
} | ||
director.Router.prototype.on.call(this, method, path, route); | ||
director.Router.prototype.on.call(this, method, path, route); | ||
}; | ||
// | ||
// ### function attach (func) | ||
// ### @func {function} Function to execute on `this` before applying to router function | ||
// Ask the router to attach objects or manipulate `this` object on which the | ||
// function passed to the http router will get applied | ||
Router.prototype.attach = function (func) { | ||
this._attach = func; | ||
}; | ||
// | ||
// ### function dispatch (method, path) | ||
// #### @req {http.ServerRequest} Incoming request to dispatch. | ||
// #### @res {http.ServerResponse} Outgoing response to dispatch. | ||
// #### @callback {function} **Optional** Continuation to respond to for async scenarios. | ||
// #### @callback {function} **Optional** Continuation to respond to for async scenarios. | ||
// Finds a set of functions on the traversal towards | ||
// `method` and `path` in the core routing table then | ||
// `method` and `path` in the core routing table then | ||
// invokes them based on settings in this instance. | ||
@@ -78,5 +87,5 @@ // | ||
// Dispatch `HEAD` requests to `GET` | ||
// | ||
// | ||
var method = req.method === 'HEAD' ? 'get' : req.method.toLowerCase(), | ||
url = req.url.split("?", 1)[0], | ||
url = req.url.split('?', 1)[0], | ||
fns = this.traverse(method, url, this.routes, ''), | ||
@@ -88,11 +97,18 @@ thisArg = { req: req, res: res }, | ||
error; | ||
if (this._attach) { | ||
this._attach.call(thisArg); | ||
} | ||
if (!fns || fns.length === 0) { | ||
error = new exports.NotFound('Could not find path: ' + req.url) | ||
if (callback) { | ||
callback.call(thisArg, error); | ||
if (typeof this.notfound === 'function') { | ||
this.notfound.call(thisArg, callback); | ||
} | ||
else if (callback) { | ||
callback.call(thisArg, error, req, res); | ||
} | ||
return false; | ||
} | ||
if (this.recurse === 'forward') { | ||
@@ -102,4 +118,4 @@ fns = fns.reverse(); | ||
stream = fns.some(function (fn) { return fn.stream === true }); | ||
runlist = this.runlist(fns); | ||
stream = runlist.some(function (fn) { return fn.stream === true }); | ||
@@ -110,10 +126,10 @@ function parseAndInvoke() { | ||
if (callback) { | ||
callback.call(thisArg, error); | ||
callback.call(thisArg, error, req, res); | ||
} | ||
return false; | ||
} | ||
self.invoke(runlist, thisArg, callback); | ||
} | ||
if (!stream) { | ||
@@ -148,3 +164,3 @@ // | ||
// ### @parsers {Object} | ||
// Lookup table of parsers to use when attempting to | ||
// Lookup table of parsers to use when attempting to | ||
// parse incoming responses. | ||
@@ -167,9 +183,9 @@ // | ||
} | ||
var parser = this.parsers[mime(req)], | ||
body; | ||
if (parser) { | ||
req.body = req.body || ''; | ||
if (req.chunks) { | ||
@@ -180,3 +196,3 @@ req.chunks.forEach(function (chunk) { | ||
} | ||
try { | ||
@@ -186,3 +202,3 @@ req.body = req.body && req.body.length | ||
: {}; | ||
} | ||
} | ||
catch (err) { | ||
@@ -189,0 +205,0 @@ return new exports.BadRequest('Malformed data'); |
@@ -12,3 +12,3 @@ /*! | ||
/** | ||
* Hypertext Transfer Protocol -- HTTP/1.1 | ||
* Hypertext Transfer Protocol -- HTTP/1.1 | ||
* http://www.ietf.org/rfc/rfc2616.txt | ||
@@ -25,3 +25,3 @@ */ | ||
/** | ||
* Versioning Extensions to WebDAV | ||
* Versioning Extensions to WebDAV | ||
* http://www.ietf.org/rfc/rfc3253.txt | ||
@@ -32,3 +32,3 @@ */ | ||
/** | ||
* Ordered Collections Protocol (WebDAV) | ||
* Ordered Collections Protocol (WebDAV) | ||
* http://www.ietf.org/rfc/rfc3648.txt | ||
@@ -39,3 +39,3 @@ */ | ||
/** | ||
* Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol | ||
* Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol | ||
* http://www.ietf.org/rfc/rfc3744.txt | ||
@@ -52,3 +52,3 @@ */ | ||
/** | ||
* PATCH Method for HTTP | ||
* PATCH Method for HTTP | ||
* http://www.ietf.org/rfc/rfc5789.txt | ||
@@ -55,0 +55,0 @@ */ |
@@ -8,5 +8,5 @@ // | ||
this.status = 304; | ||
this.options = { | ||
this.options = { | ||
removeContentHeaders: true | ||
}; | ||
}; | ||
}; | ||
@@ -51,3 +51,3 @@ | ||
this.headers = { allow: allowed }; | ||
this.body = { error: "method not allowed." }; | ||
this.body = { error: 'method not allowed.' }; | ||
}; | ||
@@ -61,4 +61,4 @@ | ||
this.body = { | ||
error: "cannot generate '" + accept + "' response", | ||
only: "application/json" | ||
error: 'cannot generate "' + accept + '" response', | ||
only: 'application/json' | ||
}; | ||
@@ -65,0 +65,0 @@ }; |
/* | ||
* router.js: Base functionality for the router. | ||
* router.js: Base functionality for the router. | ||
* | ||
@@ -35,3 +35,3 @@ * (C) 2011, Nodejitsu Inc. | ||
// | ||
// Helper function for performing an asynchronous every | ||
// Helper function for performing an asynchronous every | ||
// in series in the browser and the server. | ||
@@ -43,3 +43,3 @@ // | ||
} | ||
var completed = 0; | ||
@@ -66,8 +66,8 @@ (function iterate() { | ||
// | ||
// Helper function for expanding "named" matches | ||
// (e.g. `:dog`, etc.) against the given set | ||
// Helper function for expanding "named" matches | ||
// (e.g. `:dog`, etc.) against the given set | ||
// of params: | ||
// | ||
// { | ||
// ':dog': function (str) { | ||
// ':dog': function (str) { | ||
// return str.replace(/:dog/, 'TARGET'); | ||
@@ -86,5 +86,5 @@ // } | ||
} | ||
return mod === str | ||
? '([a-zA-Z0-9-]+)' | ||
? '([._a-zA-Z0-9-]+)' | ||
: mod; | ||
@@ -94,5 +94,5 @@ } | ||
// | ||
// Helper function for expanding wildcards (*) and | ||
// Helper function for expanding wildcards (*) and | ||
// "named" matches (:whatever) | ||
// | ||
// | ||
function regifyString(str, params) { | ||
@@ -102,6 +102,6 @@ if (~str.indexOf('*')) { | ||
} | ||
var captures = str.match(/:([^\/]+)/ig), | ||
length; | ||
if (captures) { | ||
@@ -113,3 +113,3 @@ length = captures.length; | ||
} | ||
return str; | ||
@@ -121,6 +121,6 @@ } | ||
// #### @routes {Object} **Optional** Routing table for this instance. | ||
// Constuctor function for the Router object responsible for building | ||
// Constuctor function for the Router object responsible for building | ||
// and dispatching from a given routing table. | ||
// | ||
var Router = exports.Router = function (routes) { | ||
var Router = exports.Router = function (routes) { | ||
this.params = {}; | ||
@@ -131,3 +131,3 @@ this.routes = {}; | ||
this._methods = {}; | ||
this.configure(); | ||
@@ -144,7 +144,7 @@ this.mount(routes || {}); | ||
options = options || {}; | ||
for (var i = 0; i < this.methods.length; i++) { | ||
this._methods[this.methods[i]] = true; | ||
} | ||
this.recurse = options.recurse || this.recurse || false; | ||
@@ -174,5 +174,5 @@ this.async = options.async || false; | ||
// Setups up a `params` function which replaces any instance of `token`, | ||
// inside of a given `str` with `matcher`. This is very useful if you | ||
// inside of a given `str` with `matcher`. This is very useful if you | ||
// have a common regular expression throughout your code base which | ||
// you wish to be more DRY. | ||
// you wish to be more DRY. | ||
// | ||
@@ -183,3 +183,3 @@ Router.prototype.param = function (token, matcher) { | ||
} | ||
var compiled = new RegExp(token, 'g'); | ||
@@ -193,4 +193,4 @@ this.params[token] = function (str) { | ||
// ### function on (method, path, route) | ||
// #### @method {string} **Optional** Method to use | ||
// #### @path {string} Path to set this route on. | ||
// #### @method {string} **Optional** Method to use | ||
// #### @path {Array|string} Path to set this route on. | ||
// #### @route {Array|function} Handler for the specified method and path. | ||
@@ -202,7 +202,7 @@ // Adds a new `route` to this instance for the specified `method` | ||
var self = this; | ||
if (!route && typeof path == 'function') { | ||
// | ||
// If only two arguments are supplied then assume this | ||
// `route` was meant to be a generic `on`. | ||
// `route` was meant to be a generic `on`. | ||
// | ||
@@ -213,7 +213,13 @@ route = path; | ||
} | ||
if (Array.isArray(path)) { | ||
return path.forEach(function(p) { | ||
self.on(method, p, route); | ||
}); | ||
} | ||
if (path.source) { | ||
path = path.source.replace(/\\\//ig, '/'); | ||
} | ||
if (Array.isArray(method)) { | ||
@@ -224,3 +230,3 @@ return method.forEach(function (m) { | ||
} | ||
this.insert(method, this.scope.concat(path.split(new RegExp(this.delimiter))), route); | ||
@@ -230,3 +236,3 @@ }; | ||
// | ||
// ### function path (path, routesFn) | ||
// ### function path (path, routesFn) | ||
// #### @path {string|RegExp} Nested scope in which to path | ||
@@ -239,10 +245,10 @@ // #### @routesFn {function} Function to evaluate in the new scope | ||
length = this.scope.length; | ||
if (path.source) { | ||
path = path.source.replace(/\\\//ig, '/'); | ||
} | ||
path = path.split(new RegExp(this.delimiter)); | ||
this.scope = this.scope.concat(path); | ||
routesFn.call(this, this); | ||
@@ -256,5 +262,5 @@ this.scope.splice(length, path.length); | ||
// #### @path {string} Path to dispatch | ||
// #### @callback {function} **Optional** Continuation to respond to for async scenarios. | ||
// #### @callback {function} **Optional** Continuation to respond to for async scenarios. | ||
// Finds a set of functions on the traversal towards | ||
// `method` and `path` in the core routing table then | ||
// `method` and `path` in the core routing table then | ||
// invokes them based on settings in this instance. | ||
@@ -284,3 +290,3 @@ // | ||
self.last = fns.after; | ||
self.invoke(self.runlist(fns), self, callback); | ||
self.invoke(self.runlist(fns), self, callback); | ||
} | ||
@@ -298,4 +304,4 @@ | ||
// | ||
after = this.every && this.every.after | ||
? [this.every.after].concat(this.last) | ||
after = this.every && this.every.after | ||
? [this.every.after].concat(this.last) | ||
: [this.last]; | ||
@@ -331,3 +337,3 @@ | ||
var runlist = this.every && this.every.before | ||
? [this.every.before].concat(_flatten(fns)) | ||
? [this.every.before].concat(_flatten(fns)) | ||
: _flatten(fns); | ||
@@ -338,3 +344,3 @@ | ||
} | ||
runlist.captures = fns.captures; | ||
@@ -350,3 +356,3 @@ runlist.source = fns.source; | ||
// #### @callback {function} **Optional** Continuation to pass control to for async `fns`. | ||
// Invokes the `fns` synchronously or asynchronously depending on the | ||
// Invokes the `fns` synchronously or asynchronously depending on the | ||
// value of `this.async`. Each function must **not** return (or respond) | ||
@@ -369,5 +375,5 @@ // with false, or evaluation will short circuit. | ||
// Ignore the response here. Let the routed take care | ||
// of themselves and eagerly return true. | ||
// of themselves and eagerly return true. | ||
// | ||
if (callback) { | ||
@@ -400,4 +406,4 @@ callback.apply(thisArg, arguments); | ||
// Core routing logic for `director.Router`: traverses the | ||
// specified `path` within `this.routes` looking for `method` | ||
// returning any `fns` that are found. | ||
// specified `path` within `this.routes` looking for `method` | ||
// returning any `fns` that are found. | ||
// | ||
@@ -411,5 +417,5 @@ Router.prototype.traverse = function (method, path, routes, regexp) { | ||
that; | ||
// | ||
// Base Case #1: | ||
// Base Case #1: | ||
// If we are dispatching from the root | ||
@@ -434,5 +440,5 @@ // then only check if the method exists. | ||
// | ||
// Remember to ignore keys (i.e. values of `r`) which | ||
// are actual methods (e.g. `on`, `before`, etc), but | ||
// which are not actual nested route (i.e. JSON literals). | ||
// Remember to ignore keys (i.e. values of `r`) which | ||
// are actual methods (e.g. `on`, `before`, etc), but | ||
// which are not actual nested route (i.e. JSON literals). | ||
// | ||
@@ -443,14 +449,14 @@ if (routes.hasOwnProperty(r) && (!this._methods[r] || | ||
// Attempt to make an exact match for the current route | ||
// which is built from the `regexp` that has been built | ||
// which is built from the `regexp` that has been built | ||
// through recursive iteration. | ||
// | ||
current = exact = regexp + this.delimiter + r; | ||
if (!this.strict) { | ||
exact += '[' + this.delimiter + ']?'; | ||
} | ||
match = path.match(new RegExp('^' + exact)); | ||
if (!match) { | ||
@@ -470,3 +476,3 @@ // | ||
// ### Base case 2: | ||
// If we had a `match` and the capture is the path itself, | ||
// If we had a `match` and the capture is the path itself, | ||
// then we have completed our recursion. | ||
@@ -478,3 +484,3 @@ // | ||
next.captures = match.slice(1); | ||
if (this.recurse && routes === this.routes) { | ||
@@ -484,6 +490,6 @@ next.push([routes['before'], routes['on']].filter(Boolean)); | ||
} | ||
return next; | ||
} | ||
// | ||
@@ -493,3 +499,3 @@ // ### Recursive case: | ||
// attempt to continue matching against the next portion of the | ||
// routing table. | ||
// routing table. | ||
// | ||
@@ -500,3 +506,3 @@ next = this.traverse(method, path, routes[r], current); | ||
// `next.matched` will be true if the depth-first search of the routing | ||
// table from this position was successful. | ||
// table from this position was successful. | ||
// | ||
@@ -515,3 +521,3 @@ if (next.matched) { | ||
next.after = next.after.concat([routes[r].after].filter(Boolean)); | ||
if (routes === this.routes) { | ||
@@ -528,3 +534,3 @@ fns.push([routes['before'], routes['on']].filter(Boolean)); | ||
// | ||
// ### Base case 2: | ||
// ### Base case 2: | ||
// Continue passing the partial tree structure back up the stack. | ||
@@ -537,3 +543,3 @@ // The caller for `dispatch()` will decide what to do with the functions. | ||
} | ||
return false; | ||
@@ -548,3 +554,3 @@ }; | ||
// #### @parent {Object} **Optional** Parent "routes" to insert into. | ||
// Inserts the `route` for the `method` into the routing table for | ||
// Inserts the `route` for the `method` into the routing table for | ||
// this instance at the specified `path` within the `context` provided. | ||
@@ -559,7 +565,7 @@ // If no context is provided then `this.routes` will be used. | ||
part; | ||
path = path.filter(function (p) { | ||
return p && p.length > 0; | ||
}); | ||
parent = parent || this.routes; | ||
@@ -570,8 +576,8 @@ part = path.shift(); | ||
} | ||
if (path.length > 0) { | ||
// | ||
// If this is not the last part left in the `path` | ||
// (e.g. `['cities', 'new-york']`) then recurse into that | ||
// child | ||
// (e.g. `['cities', 'new-york']`) then recurse into that | ||
// child | ||
// | ||
@@ -602,5 +608,5 @@ parent[part] = parent[part] || {}; | ||
} | ||
return; | ||
} | ||
} | ||
@@ -614,3 +620,3 @@ // | ||
isArray = Array.isArray(parent[part]); | ||
if (parent[part] && !isArray && parentType == 'object') { | ||
@@ -637,3 +643,3 @@ methodType = typeof parent[part][method]; | ||
} | ||
throw new Error('Invalid route context: ' + parentType); | ||
@@ -646,10 +652,10 @@ }; | ||
// #### @methods {Array} List of method names to extend this instance with | ||
// Extends this instance with simple helper methods to `this.on` | ||
// for each of the specified `methods` | ||
// Extends this instance with simple helper methods to `this.on` | ||
// for each of the specified `methods` | ||
// | ||
Router.prototype.extend = function(methods) { | ||
var self = this, | ||
len = methods.length, | ||
var self = this, | ||
len = methods.length, | ||
i; | ||
for (i = 0; i < len; i++) { | ||
@@ -662,3 +668,3 @@ (function(method) { | ||
: [method]; | ||
self.on.apply(self, extra.concat(Array.prototype.slice.call(arguments))); | ||
@@ -671,3 +677,3 @@ }; | ||
// | ||
// ### function mount (routes, context) | ||
// ### function mount (routes, context) | ||
// #### @routes {Object} Routes to mount onto this instance | ||
@@ -677,5 +683,5 @@ // Mounts the sanitized `routes` onto the root context for this instance. | ||
// e.g. | ||
// | ||
// | ||
// new Router().mount({ '/foo': { '/bar': function foobar() {} } }) | ||
// | ||
// | ||
// yields | ||
@@ -689,3 +695,3 @@ // | ||
} | ||
var self = this; | ||
@@ -695,14 +701,14 @@ path = path || []; | ||
function insertOrMount(route, local) { | ||
var rename = route, | ||
parts = route.split(self.delimiter), | ||
routeType = typeof routes[route], | ||
isRoute = parts[0] === "" || !self._methods[parts[0]], | ||
var rename = route, | ||
parts = route.split(self.delimiter), | ||
routeType = typeof routes[route], | ||
isRoute = parts[0] === "" || !self._methods[parts[0]], | ||
event = isRoute ? "on" : rename; | ||
if (isRoute) { | ||
rename = rename.slice(self.delimiter.length); | ||
rename = rename.slice((rename.match(new RegExp(self.delimiter)) || [''])[0].length); | ||
parts.shift(); | ||
} | ||
if (isRoute && routeType === 'object' && !Array.isArray(routes[route])) { | ||
if (isRoute && routeType === 'object' && !Array.isArray(routes[route])) { | ||
local = local.concat(parts); | ||
@@ -716,3 +722,3 @@ self.mount(routes[route], local); | ||
} | ||
self.insert(event, local, routes[route]); | ||
@@ -719,0 +725,0 @@ } |
@@ -5,3 +5,3 @@ { | ||
"author": "Nodejitsu Inc <info@nodejitsu.com>", | ||
"version": "1.0.9", | ||
"version": "1.0.10", | ||
"maintainers": [ "hij1nx <hij1nx@me.com>", "indexzero <charlie.robbins@gmail.com>" ], | ||
@@ -15,3 +15,3 @@ "contributors": [ | ||
"type": "git", | ||
"url": "http://github.com/flatiron/director.git" | ||
"url": "http://github.com/flatiron/director.git" | ||
}, | ||
@@ -30,5 +30,5 @@ "keywords": ["URL", "router", "http", "cli", "flatiron", "client side"], | ||
"scripts": { | ||
"test": "vows --spec test/server/*/*-test.js" | ||
"test": "vows test/server/*/*-test.js --spec" | ||
} | ||
} | ||
136
README.md
@@ -1,9 +0,18 @@ | ||
# Director [![Build Status](https://secure.travis-ci.org/flatiron/director.png)](http://travis-ci.org/flatiron/director) | ||
<img src="https://github.com/flatiron/director/raw/master/img/director.png" /> | ||
# Overview | ||
Director is a router. Routing is the process of determining what code to run when a URL is requested. Director works on the client and the server. Director is dependency free, on the client it does not require any other libraries (such as jQuery). | ||
# Synopsis | ||
Director is a router. Routing is the process of determining what code to run when a URL is requested. | ||
# Motivation | ||
A routing library that works in both the browser and node.js environments with as few differences as possible. Simplifies the development of Single Page Apps and Node.js applications. Dependency free (doesn't require jQuery or Express, etc). | ||
# Status | ||
[![Build Status](https://secure.travis-ci.org/flatiron/director.png)](http://travis-ci.org/flatiron/director) | ||
# Features | ||
* [Client-Side Routing](#client-side) | ||
* [Server-Side HTTP Routing](#http-routing) | ||
* [Server-Side CLI Routing](#cli-routing) | ||
# Usage | ||
* [API Documentation](#api-documentation) | ||
@@ -207,2 +216,4 @@ * [Frequently Asked Questions](#faq) | ||
* [Instance Methods](#instance-methods) | ||
* [Attach Properties to `this`](#attach-to-this) | ||
* [HTTP Streaming and Body Parsing](#http-streaming-body-parsing) | ||
@@ -574,2 +585,112 @@ <a name="constructor"></a> | ||
<a name="attach-to-this"></a> | ||
## Attach Properties To `this` | ||
Generally, the `this` object bound to route handlers, will contain the request in `this.req` and the response in `this.res`. One may attach additional properties to `this` with the `router.attach` method: | ||
```js | ||
var director = require('director'); | ||
var router = new director.http.Router().configure(options); | ||
// | ||
// Attach properties to `this` | ||
// | ||
router.attach(function () { | ||
this.data = [1,2,3]; | ||
}); | ||
// | ||
// Access properties attached to `this` in your routes! | ||
// | ||
router.get('/hello', function () { | ||
this.res.writeHead(200, { 'content-type': 'text/plain' }); | ||
// | ||
// Response will be `[1,2,3]`! | ||
// | ||
this.res.end(this.data); | ||
}); | ||
``` | ||
This API may be used to attach convenience methods to the `this` context of route handlers. | ||
<a name="http-streaming-body-parsing"> | ||
## HTTP Streaming and Body Parsing | ||
When you are performing HTTP routing there are two common scenarios: | ||
* Buffer the request body and parse it according to the `Content-Type` header (usually `application/json` or `application/x-www-form-urlencoded`). | ||
* Stream the request body by manually calling `.pipe` or listening to the `data` and `end` events. | ||
By default `director.http.Router()` will attempt to parse either the `.chunks` or `.body` properties set on the request parameter passed to `router.dispatch(request, response, callback)`. The router instance will also wait for the `end` event before firing any routes. | ||
**Default Behavior** | ||
``` js | ||
var director = require('director'); | ||
var router = new director.http.Router(); | ||
router.get('/', function () { | ||
// | ||
// This will not work, because all of the data | ||
// events and the end event have already fired. | ||
// | ||
this.req.on('data', function (chunk) { | ||
console.log(chunk) | ||
}); | ||
}); | ||
``` | ||
In [flatiron][2], `director` is used in conjunction with [union][3] which uses a `BufferedStream` proxy to the raw `http.Request` instance. [union][3] will set the `req.chunks` property for you and director will automatically parse the body. If you wish to perform this buffering yourself directly with `director` you can use a simple request handler in your http server: | ||
``` js | ||
var http = require('http'), | ||
director = require('director'); | ||
var router = new director.http.Router(); | ||
var server = http.createServer(function (req, res) { | ||
req.chunks = []; | ||
req.on('data', function (chunk) { | ||
req.chunks.push(chunk.toString()); | ||
}); | ||
router.dispatch(req, res, function (err) { | ||
if (err) { | ||
res.writeHead(404); | ||
res.end(); | ||
} | ||
console.log('Served ' + req.url); | ||
}); | ||
}); | ||
router.post('/', function () { | ||
this.res.writeHead(200, { 'Content-Type': 'application/json' }) | ||
this.res.end(JSON.stringify(this.req.body)); | ||
}); | ||
``` | ||
**Streaming Support** | ||
If you wish to get access to the request stream before the `end` event is fired, you can pass the `{ stream: true }` options to the route. | ||
``` js | ||
var director = require('director'); | ||
var router = new director.http.Router(); | ||
router.get('/', { stream: true }, function () { | ||
// | ||
// This will work because the route handler is invoked | ||
// immediately without waiting for the `end` event. | ||
// | ||
this.req.on('data', function (chunk) { | ||
console.log(chunk); | ||
}); | ||
}); | ||
``` | ||
<a name="instance-methods"></a> | ||
@@ -620,5 +741,2 @@ ## Instance methods | ||
### getState() | ||
Returns the state object that is relative to the current route. | ||
### getRoute([index]) | ||
@@ -653,6 +771,2 @@ * `index` {Number}: The hash value is divided by forward slashes, each section then has an index, if this is provided, only that section of the route will be returned. | ||
## Is Director compatible with X? | ||
Director is known to be Ender.js compatible. However, the project still needs solid cross-browser testing. | ||
# Licence | ||
@@ -672,1 +786,3 @@ | ||
[1]: https://github.com/flatiron/director/blob/master/build/director-1.0.7.min.js | ||
[2]: http://github.com/flatiron/flatiron | ||
[3]: http://github.com/flatiron/union |
@@ -633,1 +633,16 @@ | ||
}); | ||
createTest('route should accept _ and . within parameters', { | ||
'/:a': { | ||
on: function root() { | ||
shared.fired.push(location.hash); | ||
} | ||
} | ||
}, function() { | ||
shared.fired = []; | ||
this.navigate('/a_complex_route.co.uk', function root() { | ||
deepEqual(shared.fired, ['#/a_complex_route.co.uk']); | ||
this.finish(); | ||
}); | ||
}); | ||
@@ -30,3 +30,3 @@ /* | ||
that.matched['apps'].push('on apps (\\w+\\s\\w+)'); | ||
}) | ||
}); | ||
@@ -33,0 +33,0 @@ return router; |
@@ -13,3 +13,3 @@ /* | ||
vows.describe('director/router/dispatch').addBatch({ | ||
vows.describe('director/core/dispatch').addBatch({ | ||
"An instance of director.Router": { | ||
@@ -16,0 +16,0 @@ topic: function () { |
@@ -13,3 +13,3 @@ /* | ||
vows.describe('director/router/insert').addBatch({ | ||
vows.describe('director/core/insert').addBatch({ | ||
"An instance of director.Router": { | ||
@@ -16,0 +16,0 @@ topic: new director.Router(), |
@@ -23,3 +23,3 @@ /* | ||
vows.describe('director/router/mount').addBatch({ | ||
vows.describe('director/core/mount').addBatch({ | ||
"An instance of director.Router": { | ||
@@ -75,3 +75,3 @@ "with no preconfigured params": { | ||
assertRoute(foostar, ['foo', 'jitsu', 'then', 'now', 'on'], router.routes); | ||
assertRoute(foodog, ['foo', '([a-zA-Z0-9-]+)', 'on'], router.routes); | ||
assertRoute(foodog, ['foo', '([._a-zA-Z0-9-]+)', 'on'], router.routes); | ||
} | ||
@@ -78,0 +78,0 @@ } |
/* | ||
* path-test.js: Tests for the core dispatch method. | ||
* path-test.js: Tests for the core `.path()` method. | ||
* | ||
@@ -13,3 +13,3 @@ * (C) 2011, Nodejitsu Inc. | ||
vows.describe('director/router/path').addBatch({ | ||
vows.describe('director/core/path').addBatch({ | ||
"An instance of director.Router": { | ||
@@ -38,3 +38,3 @@ topic: function () { | ||
assert.isObject(router.routes.regions); | ||
assert.isFunction(router.routes.regions['([a-zA-Z0-9-]+)'].on); | ||
assert.isFunction(router.routes.regions['([._a-zA-Z0-9-]+)'].on); | ||
}, | ||
@@ -41,0 +41,0 @@ "should dispatch the function correctly": function (router) { |
@@ -7,2 +7,38 @@ /* | ||
* | ||
*/ | ||
*/ | ||
var assert = require('assert'), | ||
request = require('request'); | ||
exports.assertGet = function(port, uri, expected) { | ||
var context = { | ||
topic: function () { | ||
request({ uri: 'http://localhost:' + port + '/' + uri }, this.callback); | ||
} | ||
}; | ||
context['should respond with `' + expected + '`'] = function (err, res, body) { | ||
assert.isNull(err); | ||
assert.equal(res.statusCode, 200); | ||
assert.equal(body, expected); | ||
}; | ||
return context; | ||
}; | ||
exports.assertPost = function(port, uri, expected) { | ||
return { | ||
topic: function () { | ||
request({ | ||
method: 'POST', | ||
uri: 'http://localhost:' + port + '/' + uri, | ||
body: JSON.stringify(expected) | ||
}, this.callback); | ||
}, | ||
"should respond with the POST body": function (err, res, body) { | ||
assert.isNull(err); | ||
assert.equal(res.statusCode, 200); | ||
assert.deepEqual(JSON.parse(body), expected); | ||
} | ||
}; | ||
}; |
@@ -13,34 +13,16 @@ /* | ||
request = require('request'), | ||
director = require('../../../lib/director'); | ||
director = require('../../../lib/director'), | ||
helpers = require('../helpers'), | ||
handlers = helpers.handlers, | ||
macros = helpers.macros; | ||
function helloWorld(id) { | ||
this.res.writeHead(200, { 'Content-Type': 'text/plain' }) | ||
this.res.end('hello from (' + id + ')'); | ||
function assertBark(uri) { | ||
return macros.assertGet( | ||
9090, | ||
uri, | ||
'hello from (bark)' | ||
); | ||
} | ||
function createServer (router) { | ||
return http.createServer(function (req, res) { | ||
router.dispatch(req, res, function (err) { | ||
if (err) { | ||
res.writeHead(404); | ||
res.end(); | ||
} | ||
}); | ||
}); | ||
} | ||
function assertGet (uri) { | ||
return { | ||
topic: function () { | ||
request({ uri: 'http://localhost:9090/' + uri }, this.callback); | ||
}, | ||
"should respond with `hello from (bark)`": function (err, res, body) { | ||
assert.isNull(err); | ||
assert.equal(res.statusCode, 200); | ||
assert.equal(body, 'hello from (bark)') | ||
} | ||
} | ||
} | ||
vows.describe('director/server/http').addBatch({ | ||
vows.describe('director/http').addBatch({ | ||
"An instance of director.http.Router": { | ||
@@ -50,3 +32,3 @@ "instantiated with a Routing table": { | ||
'/hello': { | ||
get: helloWorld | ||
get: handlers.respondWithId | ||
} | ||
@@ -60,17 +42,15 @@ }), | ||
topic: function (router) { | ||
router.get(/foo\/bar\/(\w+)/, helloWorld); | ||
router.get(/foo\/update\/(\w+)/, helloWorld); | ||
router.get(/foo\/bar\/(\w+)/, handlers.respondWithId); | ||
router.get(/foo\/update\/(\w+)/, handlers.respondWithId); | ||
router.path(/bar\/bazz\//, function () { | ||
this.get(/(\w+)/, helloWorld) | ||
this.get(/(\w+)/, handlers.respondWithId); | ||
}); | ||
var server = createServer(router), | ||
that = this; | ||
server.listen(9090, this.callback); | ||
helpers.createServer(router) | ||
.listen(9090, this.callback); | ||
}, | ||
"a request to foo/bar/bark": assertGet('foo/bar/bark'), | ||
"a request to foo/update/bark": assertGet('foo/update/bark'), | ||
"a request to bar/bazz/bark": assertGet('bar/bazz/bark'), | ||
"a request to foo/bar/bark?test=test": assertGet('foo/bar/bark?test=test') | ||
"a request to foo/bar/bark": assertBark('foo/bar/bark'), | ||
"a request to foo/update/bark": assertBark('foo/update/bark'), | ||
"a request to bar/bazz/bark": assertBark('bar/bazz/bark'), | ||
"a request to foo/bar/bark?test=test": assertBark('foo/bar/bark?test=test') | ||
} | ||
@@ -77,0 +57,0 @@ } |
@@ -20,4 +20,24 @@ /* | ||
}); | ||
}, | ||
"the path() method": { | ||
topic: new director.http.Router(), | ||
"/resource": { | ||
"should insert nested routes correct": function (router) { | ||
function getResource() {} | ||
function modifyResource() {} | ||
router.path(/\/resource/, function () { | ||
this.get(getResource); | ||
this.put(/\/update/, modifyResource); | ||
this.post(/create/, modifyResource); | ||
}); | ||
assert.equal(router.routes.resource.get, getResource); | ||
assert.equal(router.routes.resource.update.put, modifyResource); | ||
assert.equal(router.routes.resource.create.post, modifyResource); | ||
} | ||
} | ||
} | ||
} | ||
}).export(module); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
260648
39
3991
783
5