Skip to content

Instantly share code, notes, and snippets.

@Raynos
Created October 11, 2011 08:46
Show Gist options
  • Save Raynos/1277598 to your computer and use it in GitHub Desktop.
Save Raynos/1277598 to your computer and use it in GitHub Desktop.
Node vanilla REST server
/*
A utility function to turn an object into a property descriptor hash
@param Object obj - the Object of keys & values to turn into an object of
keys and PropertyDescriptor(value)
*/
var pd = function _pd (obj) {
var o = {};
Object.keys(obj).forEach(function (key) {
var pd = Object.getOwnPropertyDescriptor(obj, key);
o[key] = pd;
});
return o;
};
module.exports = pd;
var http = require("http"),
pd = require("./pd.js")
Stack = require("./stack.js");
var Resource = {
/*
Constructs a resource
@param Object props - A set of properties that will be mixed into the
resource
@return Resource
*/
constructor: function _constructor(props) {
var r = Object.create(Resource, pd(props));
r.stack = Stack.constructor();
r.stack.use(function _handleResource(req, res, next) {
var cb = r.methods[req.method];
if (cb) {
cb(req, res, next)
} else {
next();
}
});
return r;
}
};
/*
Methods for the HTTP verbs
Examples:
resource.get(function(req, res) { res.end("GET!"); });
@param Function cb - callback to be trigger when a http request
is made on the server for this verb
*/
["get", "post", "put", "delete", "head", "options"].forEach(function (method) {
Resource[method] = function _method(cb) {
this.methods[method.toUpperCase()] = cb;
}
});
var Rest = {
/*
creates an error handling piece of middleware
@return Function
*/
error: function _error() {
return (function (req, res) {
res.statusCode = 500;
res.end("unexpected server error");
});
},
/*
Generates a new Rest server
@return Rest
*/
constructor: function _constructor() {
var r = Object.create(Rest, pd({
stack: Stack.constructor(),
_resources: []
}));
r._server = http.createServer(function _handleRequest(req, res) {
r.stack.handle({
data: [req, res]
});
});
return r;
},
/*
Listen to port
@param Number port - port to listen to
*/
listen: function _listen(port) {
this._server.listen(port);
},
/*
Adds a resource for an uri regular expression and
adds the methods to the resource
Examples:
rest.resource(/^\/blog$/, {
GET: function () { res.end("GET /blog"); }
});
@param RegExp uri - A regular expression to match the url of
the incoming request to
@param Object methods - A hash of methods and callbacks
@return Resource
*/
resource: function _resource(uri, methods) {
var resource = Resource.constructor({ methods: (methods || {}) });
this._resources.push([uri, resource]);
return resource;
},
/*
Generates a router for resources as middleware, intended
to be added to the server stack.
Examples:
rest.stack.use(server.router());
@return Function
*/
router: function _router() {
return (function _router(req, res, next) {
var pathname = req.url;
for (var i = 0, len = this._resources.length; i < len; i++) {
var tuple = this._resources[i],
regexp = tuple[0],
resource = tuple[1];
if (regexp.test(pathname)) {
return resource.stack;
}
}
next();
}).bind(this);
}
};
module.exports = {
Rest: Rest,
Resource: Resource
};
var rest = require("./lib/rest.js").Rest.constructor();
// register router & error handler
rest.stack.use(rest.router());
rest.stack.use(rest.error());
// register GET /
rest.resource(/^\/$/, {
GET: function (req, res) {
res.end("hello world");
}
});
// register GET /trolls
rest.resource(/^\/trolls$/).get(function (req, res) {
res.end("trolls");
});
// start rest server
rest.listen(8080);
console.log("listen to port 8080");
var toArray = function _toArray(arr) {
var ret = [];
for (var i = 0, len = arr.length; i < len; i++) {
ret[i] = arr[i];
}
return ret;
}
var Stack = {
/*
constructor: Generates a new middleware stack
@arguments: Array - an array of middleware functions or stacks
@return Stack
*/
constructor: function _constructor() {
var s = Object.create(Stack);
s._stack = [];
var middlewares = toArray(arguments);
middlewares.forEach(function (middleware) {
s.use(middleware);
});
return s;
},
/*
have the stack handle some data
Examples:
stack.handle({
data: [req, res],
floor: next
});
@param Object hash - a hash of information
{
Array data - an array of data to pass to middlewares
Function ceil - a ceiling function for the stack,
this will be called first
Function floor - a floor function for the stack,
this will be called last
}
*/
handle: function _handle(hash) {
var data = hash.data,
stack = toArray(this._stack);
hash.ceil && stack.unshift(hash.ceil);
hash.floor && stack.push(hash.floor);
(function _next() {
var handler = stack.shift();
if (!handler) {
return;
}
if (handler.apply) {
handler = handler.apply(null, data.concat(_next));
}
if (handler && handler.handle) {
handler.handle({
data: data,
floor: _next
});
}
})();
},
/*
Add a piece of middleware to the stack
@param Function|Stack middleware - a middleware stack or function to add
*/
use: function _use(middleware) {
this._stack.push(middleware);
}
};
module.exports = Stack;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment