Browse files

A step towards 1.0.0

Signed-off-by: Tim Smart <tim@fostle.com>
  • Loading branch information...
1 parent b9c5221 commit a79e3581773d072e323936566e57ae43c81c6bc0 @tim-smart tim-smart committed Mar 29, 2013
Showing with 235 additions and 224 deletions.
  1. +23 −7 README.md
  2. +114 −93 index.js
  3. +70 −105 lib/next.js
  4. +17 −14 package.json
  5. +11 −5 test/main.test.js
View
30 README.md
@@ -1,36 +1,48 @@
-Biggie-router is a high performance, extendable router for use in frameworks and applications. It draws inspiration from several popular open source frameworks and libraries, such as [jQuery](http://www.jquery.com/) and [Sinatra](http://www.sinatrarb.com/).
+Biggie-router is a high performance, extendable router for use in frameworks and
+applications. It draws inspiration from several popular open source frameworks
+and libraries, such as [jQuery](http://www.jquery.com/) and
+[Sinatra](http://www.sinatrarb.com/).
+
## License
Biggie-router is released under MIT, in hope you find this software useful.
+
## Installing it
The fastest way to get started with biggie-router is to install it via **npm**.
$ npm install biggie-router
-Otherwise `git clone`, or download the repository and place the contents of the `lib` directory where needed.
+Otherwise `git clone`, or download the repository and place the contents of the
+`lib` directory where needed.
+
+
## Usage
Here are a few basic examples.
Hello world, that listens on port 8080:
+ var http = require('http')
var Router = require('biggie-router');
- var router = new Router();
+ var server = http.createServer()
+ var router = new Router(server);
router.bind(function (request, response, next) {
next.sendBody(200, "Hello World!");
});
- router.listen(8080);
+ server.listen(8080);
---
-Basic routing + chaining. Responds with hello world on root and `/index.html` get requests.
+Basic routing + chaining. Responds with hello world on root and `/index.html`
+get requests.
-Requests that fall through (don't match any conditions) get passed to the next route and are sent a 404.
+Requests that fall through (don't match any conditions) get passed to the next
+route and are sent a 404.
var Router = require('biggie-router');
var router = new Router();
@@ -48,7 +60,10 @@ Requests that fall through (don't match any conditions) get passed to the next r
---
-Modules are functions that return a function, enabling you to do per-route setup. No modules are supplied with biggie-router, however middleware use the same pattern as connect (or express); so generic connect middleware should also be compatible with biggie. The npm `middleware` module is also compatible.
+Modules are functions that return a function, enabling you to do per-route
+setup. No modules are supplied with biggie-router, however middleware use the
+same pattern as connect (or express); so generic connect middleware should also
+be compatible with biggie. The npm `middleware` module is also compatible.
Usage is as follows:
@@ -57,3 +72,4 @@ Usage is as follows:
router.get('/').bind(middleware.sendfile('public/'));
router.post('/users').bind(api('users', 'create'));
+
View
207 index.js
@@ -24,93 +24,112 @@
// THE SOFTWARE.
//
-var http = require('http'),
- url = require('url'),
- next_ext = require('./lib/next');
-
-var log = function log(message) {
- // TODO: Insert date/time
- console.log('[router][' + '] ' + message);
-};
+var http = require('http')
+var https = require('https')
+var url = require('url')
+var next_ext = require('./lib/next')
+var events = require('events')
var noop = function () {};
// The Router prototype
-var Router = function Router(config) {
+var Router = function Router (server, config) {
var self = this;
- // Routes container
- this.routes = [];
+ config || (config = {})
- // The default config
- this.config = {
- headers: {
- 'Server': 'node.js'
- },
- mode: 'production'
- };
+ this.env = config.env || process.env.NODE_ENV || 'development'
+ this.routes = [];
+ this.headers = config.headers || { Server : 'node.js' }
+ this.next = next_ext;
- // Proxy the config over
- if (config) {
- Object.keys(config).forEach(function (key) {
- this.config[key] = config[key];
- });
- }
+ next_ext.setDefaultHeaders(this.headers);
- this.next = next_ext;
- next_ext.setDefaultHeaders(this.config.headers);
+ server.on('request', function (request, response) {
+ if (self.routes.length === 0) {
+ // We got nothing to work with :(
+ response.writeHead(404);
+ return response.end();
+ }
- // The underlying server
- http.Server.call(this, this._onRequest);
+ // Prevent crashes on HEAD requests
+ if (request.method === 'HEAD') {
+ var old_end = response.end;
- return this;
+ response.write = function write(data) {
+ return data.length;
+ };
+ response.end = function end() {
+ return old_end.call(response);
+ };
+ }
+
+ var i = 0
+
+ // One route at a time, unless marked as parallel
+ function next(err) {
+ ;++i
+
+ if (self.routes[i]) {
+ if (err) {
+ while (self.routes[i]) {
+ if (self.routes[i].errorhandler) {
+ return self.routes[i].handle(error, request, response, next);
+ }
+
+ ;++i
+ }
+
+ return next(err)
+ }
+
+ self.routes[i].handle(request, response, next);
+ } else {
+ if (err) {
+ self.emit('error', err)
+ } else {
+ var err = new Error('Resource "' + request.url + '" not found.')
+ err.code = 404
+ err.name = 'Not found'
+
+ self.emit('error', err)
+ }
+ }
+ };
+ next.__proto__ = next_ext;
+ next.response = response;
+ next.request = request
+
+ // Get the party started
+ self.routes[i].handle(request, response, next);
+ })
+
+ server.on('error', function (err) {
+ self.emit('error', err)
+ })
};
Router.next = next_ext;
// Extend http.Server
-Router.prototype.__proto__ = http.Server.prototype;
+Router.prototype.__proto__ = events.EventEmitter.prototype;
-// All requests proxy through this method
-Router.prototype._onRequest = function _onRequest(request, response) {
- if (this.routes.length === 0) {
- // We got nothing to work with :(
- response.writeHead(404);
- return response.end();
+// Configure
+Router.prototype.configure = function configure (env, config) {
+ if ('function' === typeof env) {
+ config = env
+ env = null
}
- // Prevent crashes on HEAD requests
- if (request.method === 'HEAD') {
- var old_end = response.end;
-
- response.write = function write(data) {
- return data.length;
- };
- response.end = function end() {
- return old_end.call(response);
- };
+ if (!env || this.env === env) {
+ config(this)
}
- var i = 0,
- self = this,
- next;
-
- // One route at a time, unless marked as parallel
- next = function next() {
- i++;
- if (self.routes[i]) {
- self.routes[i].handle(request, response, next);
- } else {
- // We have a 404 houston
- // TODO: Re-route this to some listeners
- next.sendText(404, 'Resource "' + request.url + '" not found.');
- }
- };
- next.__proto__ = next_ext;
- next.response = response;
+ return this
+}
- // Get the party started
- this.routes[i].handle(request, response, next);
+// All requests proxy through this method
+Router.prototype._onRequest = function _onRequest(request, response) {
};
// Low level api to manually create Route
@@ -181,11 +200,12 @@ Router.prototype.options = function options() {
// The Route prototype
var Route = function Route(router, config) {
this.router = router;
+
// The default config
- this.config = {
- parallel: false,
- catch_all: false
- };
+ this.parallel = false
+ this.catchall = false
+ this.errorhandler = false
+ this.parse = this.router.parse
// The route table
this.table = {
@@ -203,26 +223,25 @@ var Route = function Route(router, config) {
// Proxy the config over
if (config) {
var self = this;
- Object.keys(config).forEach(function (key) {
- self.config[key] = config[key];
- });
- }
+ var keys = Object.keys(config)
- return this;
-};
+ for (var i = 0, il = keys.length; i < il; i++) {
+ self[keys[i]] = config[keys[i]]
+ }
+ }
+}
// The proxy for routes. Check for a match, then pass through
// the other stuff in order.
Route.prototype.handle = function handle(request, response, callback) {
- if (this.config.parallel && callback) {
+ if (this.parallel && callback) {
callback();
callback = noop;
}
var method = request.method,
self = this,
match = false,
- re_match,
next,
i = 0;
@@ -232,23 +251,25 @@ Route.prototype.handle = function handle(request, response, callback) {
method = 'GET';
}
- if (this.config.catch_all === true) {
+ if (this.catch_all === true) {
match = true;
+ } else if (this.parse) {
+ match = this.parse(request)
} else {
var lower_path = request.url.toLowerCase(),
j = 0,
length = this.table[method].length,
route;
+
for (; j < length; j++) {
route = this.table[method][j];
if (route instanceof RegExp) {
var temp_match;
if (temp_match = lower_path.match(route)) {
- // TODO: Remove shift
+ // TODO: Remove shift. Use queue?
temp_match.shift();
// Keep a reference to the last regexp match
- re_match = temp_match;
- match = true;
+ match = temp_match;
}
} else if (route === lower_path) {
match = true;
@@ -260,12 +281,7 @@ Route.prototype.handle = function handle(request, response, callback) {
// We have a match! Time to go through the processing layers
next = function next(err) {
if (err) {
- if (self.router.config.mode &&
- self.router.config.mode === 'development') {
- throw error;
- } else {
- log('Error: ' + err.message);
- }
+ return callback(err)
}
var args,
@@ -274,16 +290,16 @@ Route.prototype.handle = function handle(request, response, callback) {
if (layer) {
args = [request, response, next];
- // Suffix any regexp results we had
- // then call the layer
- if (re_match) {
- args.push.apply(args, re_match);
+ // Regex match
+ if ('object' === typeof match) {
+ next.match = match
+ } else {
+ next.match = null
}
- args.push.apply(args, arguments);
// Catch layer errors
try {
- layer.apply(null, args);
+ layer(request, response, next)
} catch (error) {
next(error);
}
@@ -295,6 +311,7 @@ Route.prototype.handle = function handle(request, response, callback) {
// Add __proto__ methods.
next.__proto__ = next_ext;
next.response = response;
+ next.request = request
// Start the processing madness
next();
@@ -320,6 +337,10 @@ Route.prototype._checkRoute = function _checkRoute(route) {
Route.prototype.bind = function bind(fn) {
if (typeof fn === 'function') {
this.layers.push(fn);
+
+ if (4 === fn.length) {
+ this.errorhandler = true
+ }
} else {
log('Warning: bind only accepts functions.');
}
View
175 lib/next.js
@@ -22,167 +22,132 @@
var defaultHeaders = {};
-exports.setDefaultHeaders = function setDefaultHeaders(headers) {
+exports.setDefaultHeaders = function setDefaultHeaders (headers) {
defaultHeaders = headers;
};
-// Just in case
-exports.getBody = function getBody(encoding) {
- if (this.body && encoding) {
- return this.body.toString(encoding);
- }
- return this.body;
-};
-
-// Method used to set the body
-exports.setBody = function setBody(to) {
- if (to instanceof Buffer) {
- this.body = to;
- } else {
- if (typeof to !== 'string') {
- to = to.toString();
- }
- this.body = new Buffer(to);
- }
- return this.body;
-};
-
-// Method used to append to the body
-exports.appendBody = function appendBody(data) {
- if (data instanceof Buffer === false) {
- if (typeof data !== 'string') {
- data = data.toString();
- }
- data = new Buffer(data);
- }
-
- // Do a little memcopy magic
- if (this.body) {
- var temp_buffer = new Buffer(this.body.length + data.length);
- this.body.copy(temp_buffer, 0, 0);
- data.copy(temp_buffer, this.body.length, 0);
- this.body = temp_buffer;
- } else {
- this.body = data;
- }
-
- return this.body;
-};
-
// Easy response methods
-var mergeDefaultHeaders;
-
-mergeDefaultHeaders = function getDefaultHeaders(headers) {
+exports.mergeDefaultHeaders = function mergeDefaultHeaders (headers) {
headers = headers || {};
var keys = Object.keys(defaultHeaders),
key;
for (var i = 0, il = keys.length; i < il; i++) {
key = keys[i];
headers[key] || (headers[key] = defaultHeaders[key]);
}
+
+ if (this.response.headers) {
+ keys = Object.keys(this.request.headers)
+
+ for (var i = 0, il = keys.length; i < il; i++) {
+ key = keys[i]
+ headers[key] || (headers[key] = this.request.headers[key])
+ }
+ }
+
+ if (this.headers) {
+ keys = Object.keys(this.headers)
+
+ for (var i = 0, il = keys.length; i < il; i++) {
+ key = keys[i]
+ headers[key] || (headers[key] = this.headers[key])
+ }
+ }
+
return headers;
};
-exports.sendHeaders = function sendHeaders(code, headers, content) {
+exports.set = function set (key, value) {
+ this.headers || (this.headers = {})
+ this.headers[key] = value
+ return this
+}
+
+exports.sendHead = function sendHead (code, headers, content) {
if (typeof code !== 'number') {
content = headers;
headers = code;
code = 200;
}
- headers = mergeDefaultHeaders(headers);
- headers['Date'] = headers['Date'] || new Date().toUTCString();
- if (content) {
- headers['Content-Length'] = headers['Content-Length'] || content.length;
- }
- this.response.writeHead(code, headers);
-};
+ headers || (headers = {})
+ headers['Date'] || (headers['Date'] = new Date().toUTCString())
-exports.send = function send(code, content, headers) {
- if (typeof code !== 'number') {
- headers = content;
- content = code;
- code = 200;
+ if (content) {
+ if (Buffer.isBuffer(content)) {
+ headers['Content-Length'] || (headers['Content-Length'] = content.length)
+ } else {
+ headers['Content-Length'] || (
+ headers['Content-Length'] = Buffer.byteLength(content)
+ )
+ }
}
- this.sendHeaders(code, headers, content);
- this.response.end(content);
-};
-exports.sendRedirect = function sendRedirect(location, content, headers) {
- var default_headers = {
- 'Location': location
- };
- headers = headers || {};
+ this.mergeDefaultHeaders(headers)
- Object.keys(headers).forEach(function (key) {
- default_headers[key] = headers[key];
- });
-
- return this.send(302, content, default_headers);
+ return this.response.writeHead(code, headers);
};
-exports.sendBody = function sendBody(code, content, headers) {
+exports.send = function send (code, content, headers) {
if (typeof code !== 'number') {
headers = content;
content = code;
code = 200;
}
- var default_headers = {};
- headers = headers || {};
+ headers || (headers = {})
if (typeof content === 'string' || content instanceof Buffer) {
- default_headers['Content-Type'] = 'text/html';
+ headers['Content-Type'] || (headers['Content-Type'] = 'text/html')
} else {
content = JSON.stringify(content);
- default_headers['Content-Type'] = 'application/json';
+ headers['Content-Type'] || (headers['Content-Type'] = 'application/json')
}
- Object.keys(headers).forEach(function (key) {
- default_headers[key] = headers[key];
- });
+ this.sendHead(code, headers, content);
+ return this.response.end(content);
+};
- return this.send(code, content, default_headers);
+exports.redirect = function redirect (location, content, headers) {
+ headers || (headers = {})
+ headers['Location'] = location
+
+ return this.send(302, content, headers);
};
-exports.sendJson = function sendJson(code, data, headers) {
+exports.body = function body (code, content, headers) {
if (typeof code !== 'number') {
- headers = data;
- data = code;
+ headers = content;
+ content = code;
code = 200;
}
- var default_headers = {
- 'Content-Type': 'application/json'
- };
- headers = headers || {};
+ this.sendHead(code, headers)
+ return this.response.end(content)
+};
- if (typeof data !== 'string' && data instanceof Buffer === false) {
- data = JSON.stringify(data);
+exports.json = function json (code, data, headers) {
+ if (typeof code !== 'number') {
+ headers = data;
+ data = code;
+ code = 200;
}
- Object.keys(headers).forEach(function (key) {
- default_headers[key] = headers[key];
- });
+ headers || (headers = {})
+ headers['Content-Type'] = 'application/json'
- return this.send(code, data, default_headers);
+ return this.send(code, data, headers);
};
-exports.sendText = function sendText(code, data, headers) {
+exports.text = function text (code, data, headers) {
if (typeof code !== 'number') {
headers = data;
data = code;
code = 200;
}
- var default_headers = {
- 'Content-Type': 'text/plain'
- };
- headers = headers || {};
-
- Object.keys(headers).forEach(function (key) {
- default_headers[key] = headers[key];
- });
+ headers || (headers = {})
+ headers['Content-Type'] = 'text/plain'
- return this.send(code, data, default_headers);
+ return this.send(code, data, headers);
};
View
31 package.json
@@ -1,15 +1,18 @@
-{
- "name": "biggie-router",
- "description": "The router as part of the biggie framework.",
- "version": "0.3.4",
- "author": "Tim Smart",
- "licenses": [{
- "type": "MIT"
- }],
- "repository": {
- "type": "git",
- "url": "http://github.com/biggie/biggie-router.git"
- },
- "engine": [ "node >=0.4.0" ],
- "main": "./"
+{ "name" : "biggie-router"
+, "version" : "1.0.0-pre"
+, "author" : "Tim Smart <tim@fostle.com>"
+, "description" : "A lightweight extensible HTTP router"
+, "engines" :
+ { "node" : ">=0.10"
+ }
+, "homepage" : "https://github.com/tim-smart/biggie-router"
+, "repository" :
+ { "type" : "git"
+ , "url" : "git://github.com/tim-smart/biggie-router.git"
+ }
+, "bugs" : {
+ "email" : "tim@fostle.com"
+ , "url" : "https://github.com/tim-smart/biggie-router/issues"
+ }
+, "main" : "./index.js"
}
View
16 test/main.test.js
@@ -1,14 +1,20 @@
-var assert = require('assert'),
- Router = require('../');
+var assert = require('assert')
+var http = require('http')
+var Router = require('../')
-var r = new Router;
+var s = http.createServer()
+var r = new Router(s);
r.next.bend = function () {
this.send('Bending')
}
+r.get('/json').bind(function (req, res, next) {
+ next.text(new Buffer('testing'))
+})
+
r.bind(function (request, response, next) {
next.bend();
-});
+})
-r.listen(8080);
+s.listen(8080);

0 comments on commit a79e358

Please sign in to comment.