Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Very simple Express-like routing for PhantomJS
// Do things like:
var app = new Routes();
app.use(Routes.static('../htdocs'));
app.use(function(req, res, next) {
req.startTime = new Date().getTime();
next();
});
app.all(/.+/, function(req, res, next) {
console.log('Here comes a request.');
next();
});
app.get('/', function(req, res, next) {
res.send('Hello there.');
})
app.post('/', function(req, res, next) {
res.send(req.post.name);
});
app.get('/test', function(req, res, next) {
res.write('What a test.');
next();
});
app.get('/user/([a-zA-Z0-9]+)', function(req, res, next) {
res.write('You are user: ' + req.params[0]);
next();
});
app.get('/nonexistent', function(req, res) {
res.send(404);
});
app.get('/go_away', function(req, res) {
res.redirect('/');
});
app.use(function(req, res) {
console.log(new Date().getTime() - req.startTime);
res.close();
});
app.listen(8080);
var Routes = (function() {
var _ = {}, ctor = function(){};
_.bind = function bind(func, context) {
var bound, args, slice = Array.prototype.slice;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
var Res = function Res(res) {
this.headers = res.headers || {};
this.res = res;
this.statusCode = res.statusCode;
};
Res.prototype.header = function(h, v) {
if (v) return this.headers[h] = v;
else return this.headers[h];
};
Res.prototype.send = function(body) {
if (arguments.length == 2) {
if (typeof body != 'number' && typeof arguments[1] == 'number') {
this.statusCode = arguments[1];
} else {
this.statusCode = body;
body = arguments[1];
}
}
if (typeof body == 'number') { this.statusCode = body; body = ''; }
else if (typeof body == 'object') body = JSON.stringify(body);
this.close(body);
return this;
};
Res.prototype.write = function(data) {
this.res.write(data);
return this;
};
Res.prototype.redirect = function(url) {
this.header('Location', url);
this.send(301);
};
Res.prototype.close = function(data) {
this.res.statusCode = this.statusCode;
this.res.headers = this.headers || {};
this.write(data);
this.res.close();
return this;
};
var R = function Routes() {
this.server = require('webserver').create();
this.routes = [];
};
R.prototype.preRoute = function(req, res) {
this.router.call(this, req, new Res(res));
};
R.prototype.router = function(req, res, i) {
var i = i || 0;
for (i; i < this.routes.length; i++) {
var route = this.routes[i];
if (route.method == 'ALL' || route.method == req.method) {
var match = req.url.match(route.route);
if (match) {
req.params = match.slice(1);
try {
return route.handler.call(this, req, res, _.bind(this.router, this, req, res, ++i));
} catch (err) {
console.log(err.stack);
return res.send(err.stack, 500);
}
}
}
}
res.send('Not found', 404);
};
R.prototype.addRoute = function(method, route, handler) {
if (!(route instanceof RegExp)) route = new RegExp("^" + route + "$");
this.routes.push({method: method, route: route, handler: handler});
};
R.prototype.all = function(route, handler) {
this.addRoute('ALL', route, handler);
};
R.prototype.get = function(route, handler) {
this.addRoute('GET', route, handler);
};
R.prototype.post = function(route, handler) {
this.addRoute('POST', route, handler);
};
R.prototype.head = function(route, handler) {
this.addRoute('HEAD', route, handler);
};
R.prototype.put = function(route, handler) {
this.addRoute('PUT', route, handler);
};
R.prototype.delete = function(route, handler) {
this.addRoute('DELETE', route, handler);
};
R.prototype.use = function(handler) {
this.addRoute('ALL', /.+/, handler);
};
R.prototype.listen = function(port) {
this.server.listen(port, _.bind(this.preRoute, this));
};
R.static = function(root) {
var fs = require('fs'),
root = fs.absolute(root);
return function(req, res, next) {
if (req.method != 'GET') return next();
var resource = req.url.slice(1),
path = root + '/' + resource;
if (resource && fs.isFile(path) && fs.isReadable(path)) {
var file = fs.read(path);
res.send(file);
} else {
next();
}
}
};
return R;
})();
if (module && module.exports) module.exports = Routes;
@ralphtheninja

This comment has been minimized.

Copy link

ralphtheninja commented Jul 8, 2012

Looks really nice! A lot of meta stuff there with bind etc. Trying to figure out the magic but would like to know what npm module you are using when calling require('webserver')

@ralphtheninja

This comment has been minimized.

Copy link

ralphtheninja commented Jul 8, 2012

I have a hard time trying to figure out lines 7 to 12 in Routes.js, are they there to make sure the bound function works regardless if the function was called with new or called directly?

@trevordixon

This comment has been minimized.

Copy link
Owner Author

trevordixon commented Jul 8, 2012

It's not actually an npm module, but a built-in PhantomJS module. http://code.google.com/p/phantomjs/wiki/Interface#WebServer_Module

I pulled the bind function out of underscore.js, and yes, it makes it possible to use the new keyword with a bound function. http://stackoverflow.com/questions/8552908/understanding-the-code-of-bind

@Aaronius

This comment has been minimized.

Copy link

Aaronius commented Oct 10, 2012

Let's get some documentation up in haaaaaaaar!

@dongyuwei

This comment has been minimized.

Copy link

dongyuwei commented Apr 26, 2015

Bug report:

if we add a route /test, and then request /test?foo=bar. The url(/test?foo=bar) will not match the route: new RegExp("^" + "/test" + "$"), and the req.params will not be set correctly.

//curl -I http://127.0.0.1:3000/test?foo=bar
app.get('/test', function(req, res) {
   //...
}

but in expressjs, the route will work as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.