Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added comments. Refactored.

Signed-off-by: Jason Rudland <knowlecules@gmail.com>
  • Loading branch information...
commit 82af6bfcd21e3aa71952c689c8fef4f0da25a75c 1 parent 132bf14
@knowlecules authored
Showing with 63 additions and 39 deletions.
  1. +63 −39 lib/rapid-rest.js
View
102 lib/rapid-rest.js
@@ -8,12 +8,18 @@
var http = require('http');
var url = require('url');
+
module.exports = function() {
+ /* Syntax to enable the following invocation signature :
+ routes('/my_path')('get', myFunc);
+ */
var rest = function(route) {
return rest.handlers(rest.router.add(route));
};
rest.matchDefs = {};
+ rest.methods = {};
rest.respond = function(res, code, headers, data) {
+ // Handles empty responses more gracefully for feedback purposes
if (code && headers && !data) {
data = headers;
headers = {};
@@ -23,45 +29,49 @@ module.exports = function() {
headers = {};
}
+ // Ready the package
if (typeof data === 'object') {
data = JSON.stringify(data);
headers['Content-type'] = headers['Content-type'] || 'application/json';
}
+ // Playing nice header that's not really required
if (!headers['Content-length'] && typeof data === 'string') {
headers['Content-length'] = data.length;
}
- // TODO: streams, json, html (look for <)
res.writeHead(code, headers);
res.end(data);
- }
+ };
rest.handlers = function(route){
- route.methods = route.methods || {};
-
- route.methods.options = route.methods.options || function(ctx) {
+ // Create options collection if it doesn't exist
+ route.methods.options = route.methods.options || function(req, res, params) {
var methods = Object.keys(route.methods).filter(function(key) {
return key !== 'options';
});
-
- ctx.respond(200, methods);
+ rest.respond(res, 200, methods);
};
+ // Request services are found at the intersection of service pattern and verb name
route.method = function(rawVerb, accept, fn) {
+ // Handle overloads
if (rawVerb && accept && !fn) {
fn = accept;
- accept = null
+ accept = null;
}
var verb = rawVerb.toLowerCase();
- if (verb[0] != 'p') {
+ if (verb[0] !== 'p') {
+ // verbs that don't send data such as GET, SEARCH
route.methods[verb] = function(req, res, segments) {
fn(req, res, segments);
};
}else{
+ // Expecting POST or PUT verb
route.methods[verb] = function(req, res, segments) {
var body = '';
+ // Data accepting event
req.on('data', function (data) {
body += data;
if (body.length > 1e6) {
@@ -70,89 +80,103 @@ module.exports = function() {
}
});
req.on('end', function () {
- //TODO: Switch contentType or pass parser as a parameter
+ var data,files;
+ // For performance reasons will only ever support JSON
+ if(/application\/json/i.test(req.headers["content-type"])){
+ try{
+ // Invoke the callback
+ data = JSON.parse(body);
+ fn(req, res, segments, data);
+ }catch(ejson){
+ res.write("ERROR: Could not parse body as json.\n\n" + body);
+ res.end();
+ }
+ }else{
+ res.write("ERROR: Content-type not supported:" + req.headers["content-type"]);
+ res.end();
- var data = JSON.parse(body);
- fn(req, res, segments, data);
+ }
});
- }
+ };
}
return route.method;
};
return route.method;
};
+ // Routes to be handled
rest.router = (function () {
var routes = {};
return {
add : function (urlPath) {
- var location = routes;
+ //Performance gains are made here by setting up all variables required for the runtime service
var urlDef = url.parse(urlPath, true);
- var urlMatch = urlDef.pathname.replace(/[\-[\]\{\}\(\)\*\+\?\.,\/\\\^\$\|#\s]/g, "\\$&").replace(/(:[^(\\/)]+)/g,"([^/]*)");
+ var urlMatch = urlDef.pathname.replace(/[\-\[\]\{\}\(\)\*\+\?\.,\/\\\^\$\|#\s]/g, "\\$&").replace(/(:[^(\\/)]+)/g,"([^/]*)");
var patternRE = new RegExp(urlMatch);
var paramKeys = (urlPath.indexOf("/:") > -1) ? urlPath.replace(/(\/[^:][^\/]+)/g, '').substr(2).split("/:") :[];
var queryKeys = urlDef.query;
- location[urlMatch] = rest.handlers({});
- rest.matchDefs[urlMatch] = {re:patternRE, paramKeys:paramKeys, queryKeys:queryKeys};
- return location[urlMatch];
+ rest.matchDefs[urlMatch] = rest.matchDefs[urlMatch] || {re:patternRE, paramKeys:paramKeys, queryKeys:queryKeys, methods:{}};
+ return rest.matchDefs[urlMatch];
},
handle : function(req, res) {
+ var paramKeys, urlDef, pattern, key, ikey;
// Handle options
var method = req.method.toLowerCase();
var location = routes;
- var paramKeys;
- var params={};
-
- var urlDef;
var urlPath = req.url;
+ var params={};
//Optimization for URLs that have no query string
- if(req.url.indexOf("?")){
+ if(req.url.indexOf("?")>=-1){
urlDef = url.parse(req.url,true);
urlPath = urlDef.pathname;
req.query = urlDef.query;
}
- // Check for matching pattern
- for (var pattern in rest.matchDefs) {
+ // Search for matching pattern
+ for (pattern in rest.matchDefs) {
var matchDef = rest.matchDefs[pattern];
var urlParams = urlPath.match(matchDef.re);
+ // Successful match
if ( urlParams !== null){
- // Validate required querystring kvps
+ // If required, validate querystring contains the required value pairs
+ var queryMatch = true;
if(urlDef && matchDef.queryKeys){
- for(var kvp in urlDef.query) {
- if(!matchDef.query[kvp]){
- continue;
+ for(key in matchDef.queryKeys) {
+ if(!urlDef.query[key]){
+ queryMatch = false;
}
}
}
- // Align variables in the URL with the REST definition
- var paramKeys = rest.matchDefs[pattern].paramKeys;
- for(var ikey=0; ikey < paramKeys.length; ikey++) {
+
+ // Align parameters in the URL with the names from the service definition
+ paramKeys = rest.matchDefs[pattern].paramKeys;
+ for(ikey=0; ikey < paramKeys.length; ikey++) {
params[paramKeys[ikey]] = urlParams[ikey + 1];
}
- location = routes[pattern];
+
+ // Assign the route if the entire pattern matches
+ if(queryMatch){
+ location = rest.matchDefs[pattern];
+ }
break;
}
}
+ // The service has not been defined
if (!location.methods || !location.methods[method]) {
return rest.respond(res, 501, {'Content-type' : 'text/plain'}, 'Not Implemented');
}
location.methods[method](req, res, params);
- },
-
- respond : function(code, headers, data) {
- rest.respond(res,code, headers, data);
}
- }
- })()
+ };
+ }());
rest.listen = function(host, port, cb) {
http.createServer(rest.router.handle).listen(host, port, cb);
Please sign in to comment.
Something went wrong with that request. Please try again.