From f514e1156996a3b33123d968e438ecfb54f7e07e Mon Sep 17 00:00:00 2001 From: Marek Stasikowski and Simon Adcock Date: Mon, 20 Jul 2015 16:42:53 +0100 Subject: [PATCH 1/5] Extract route-map into module --- lib/middleware/route-handlers.js | 169 +++++++------------------------ lib/middleware/route-map.js | 116 +++++++++++++++++++++ 2 files changed, 150 insertions(+), 135 deletions(-) create mode 100644 lib/middleware/route-map.js diff --git a/lib/middleware/route-handlers.js b/lib/middleware/route-handlers.js index 2729bf4..49d3cb6 100644 --- a/lib/middleware/route-handlers.js +++ b/lib/middleware/route-handlers.js @@ -1,151 +1,50 @@ -var glob = require('glob'); -var fs = require('fs'); -var protagonist = require('protagonist'); -var async = require('async'); + var pathToRegexp = require('path-to-regexp'); -var _ = require('lodash'); -var urlParser = require('../url-parser'); -var route = require('../route'); var queryComparator = require('../query-comparator'); +var buildRouteMap = require('./route-map'); -var ROUTE_MAP = null; -var autoOptions; -var autoOptionsAction = require('../json/auto-options-action.json'); - -var parseAction = function(uriTemplate, action) { - var parsedUrl = urlParser.parse(uriTemplate); - var key = parsedUrl.url; - - ROUTE_MAP[key] = ROUTE_MAP[key] || { urlExpression: parsedUrl.url, methods: {} }; - ROUTE_MAP[key].methods[action.method] = ROUTE_MAP[key].methods[action.method] || []; - var routeHandlers = route.getRouteHandlers(key, parsedUrl, action); - Array.prototype.push.apply(ROUTE_MAP[key].methods[action.method], routeHandlers); - - console.log('[LOG]'.white, 'Setup Route:', action.method.green, key.yellow, action.name.blue); -}; +module.exports = function(options, cb) { + buildRouteMap(options, function(err, routeMap) { + if (err) { + cb(err); + } + var middleware = function(req, res, next) { + var handlers = null; -var parseBlueprint = function(filePath) { - return function(cb) { - var data = fs.readFileSync(filePath, {encoding: 'utf8'}); - protagonist.parse(data, function(err, result) { - if (err) { - console.log(err); - return; - } + Object.keys(routeMap).forEach(function(urlPattern) { + if (handlers) { + return; // continue if we've already go handlers + } + var regex = pathToRegexp(urlPattern); - var allRoutesList = []; - result.ast.resourceGroups.forEach(function(resourceGroup){ - resourceGroup.resources.forEach(function(resource){ - resource.actions.forEach(function(action){ - parseAction(resource.uriTemplate, action); - saveRouteToTheList(resource, action); - }); - }); + // req.path allows us to delegate query string handling to the route handler functions + var match = regex.exec(req.path); + if (match) { + handlers = routeMap[urlPattern].methods[req.method.toUpperCase()]; + } }); - // add OPTIONS route where its missing - this must be done after all routes are parsed - if (autoOptions) { - addOptionsRoutesWhereMissing(allRoutesList); - } - - cb(); - - /** - * Adds route and its action to the allRoutesList. It appends the action when route already exists in the list. - * @param resource Route URI - * @param action HTTP action - */ - function saveRouteToTheList(resource, action) { - // used to add options routes later - if (typeof allRoutesList[resource.uriTemplate] === 'undefined') { - allRoutesList[resource.uriTemplate] = []; + if (handlers) { + var queryParams = Object.keys(req.query); + if (queryParams.length === 0){ + handlers.sort(queryComparator.noParamComparator); + } else { + queryComparator.countMatchingQueryParms(handlers, queryParams); + handlers.sort(queryComparator.queryParameterComparator); } - allRoutesList[resource.uriTemplate].push(action); - } - function addOptionsRoutesWhereMissing(allRoutes) { - var routesWithoutOptions = []; - // extracts only routes without OPTIONS - _.forIn(allRoutes, function (actions, route) { - var containsOptions = _.reduce(actions, function (previousResult, iteratedAction) { - return previousResult || (iteratedAction.method === 'OPTIONS'); - }, false); - if (!containsOptions) { - routesWithoutOptions.push(route); + var requestHandled = false; + handlers.forEach(function (handler) { + if (requestHandled) { + return; } + requestHandled = handler.execute(req, res); }); - - _.forEach(routesWithoutOptions, function (uriTemplate) { - // adds prepared OPTIONS action for route - parseAction(uriTemplate, autoOptionsAction); - }); - } - }); - }; -}; - -var setup = function(sourceFiles, cb) { - ROUTE_MAP = {}; - glob(sourceFiles, {} , function (err, files) { - if (err) { - console.error('Failed to parse contracts path.', err); - process.exit(1); - } - - var asyncFunctions = []; - - files.forEach(function(filePath) { - asyncFunctions.push(parseBlueprint(filePath)); - }); - - async.series(asyncFunctions, function(err) { - cb(err); - }); - }); - -}; - -module.exports = function(options, cb) { - var sourceFiles = options.sourceFiles; - autoOptions = options.autoOptions; - var middleware = function(req, res, next) { - var handlers = null; - Object.keys(ROUTE_MAP).forEach(function(urlPattern) { - if (handlers) { - return; // continue if we've already go handlers - } - var regex = pathToRegexp(urlPattern); - - // req.path allows us to delegate query string handling to the route handler functions - var match = regex.exec(req.path); - if (match) { - handlers = ROUTE_MAP[urlPattern].methods[req.method.toUpperCase()]; - } - }); - - if (handlers) { - var queryParams = Object.keys(req.query); - if (queryParams.length === 0){ - handlers.sort(queryComparator.noParamComparator); - } else { - queryComparator.countMatchingQueryParms(handlers, queryParams); - handlers.sort(queryComparator.queryParameterComparator); } - - var requestHandled = false; - handlers.forEach(function (handler) { - if (requestHandled) { - return; - } - requestHandled = handler.execute(req, res); - }); - } - next(); - }; - - setup(sourceFiles, function(err) { - cb(err, middleware); + next(); + }; + cb(null, middleware); }); }; diff --git a/lib/middleware/route-map.js b/lib/middleware/route-map.js new file mode 100644 index 0000000..18787b7 --- /dev/null +++ b/lib/middleware/route-map.js @@ -0,0 +1,116 @@ +var glob = require('glob'); +var fs = require('fs'); +var _ = require('lodash'); +var protagonist = require('protagonist'); +var async = require('async'); +var urlParser = require('../url-parser'); +var route = require('../route'); + +var autoOptionsAction = require('../json/auto-options-action.json'); +var ROUTE_MAP = null; + +var parseAction = function(uriTemplate, action) { + var parsedUrl = urlParser.parse(uriTemplate); + var key = parsedUrl.url; + + ROUTE_MAP[key] = ROUTE_MAP[key] || { urlExpression: parsedUrl.url, methods: {} }; + ROUTE_MAP[key].methods[action.method] = ROUTE_MAP[key].methods[action.method] || []; + + var routeHandlers = route.getRouteHandlers(key, parsedUrl, action); + Array.prototype.push.apply(ROUTE_MAP[key].methods[action.method], routeHandlers); + + console.log('[LOG]'.white, 'Setup Route:', action.method.green, key.yellow, action.name.blue); +}; + +var parseBlueprint = function(filePath, autoOptions) { + return function(cb) { + var data = fs.readFileSync(filePath, {encoding: 'utf8'}); + protagonist.parse(data, function(err, result) { + if (err) { + console.log(err); + return; + } + + var allRoutesList = []; + result.ast.resourceGroups.forEach(function(resourceGroup){ + resourceGroup.resources.forEach(function(resource){ + resource.actions.forEach(function(action){ + parseAction(resource.uriTemplate, action); + saveRouteToTheList(resource, action); + }); + }); + }); + + // add OPTIONS route where its missing - this must be done after all routes are parsed + if (autoOptions) { + addOptionsRoutesWhereMissing(allRoutesList); + } + + cb(); + + /** + * Adds route and its action to the allRoutesList. It appends the action when route already exists in the list. + * @param resource Route URI + * @param action HTTP action + */ + function saveRouteToTheList(resource, action) { + // used to add options routes later + if (typeof allRoutesList[resource.uriTemplate] === 'undefined') { + allRoutesList[resource.uriTemplate] = []; + } + allRoutesList[resource.uriTemplate].push(action); + } + + function addOptionsRoutesWhereMissing(allRoutes) { + var routesWithoutOptions = []; + // extracts only routes without OPTIONS + _.forIn(allRoutes, function (actions, route) { + var containsOptions = _.reduce(actions, function (previousResult, iteratedAction) { + return previousResult || (iteratedAction.method === 'OPTIONS'); + }, false); + if (!containsOptions) { + routesWithoutOptions.push(route); + } + }); + + _.forEach(routesWithoutOptions, function (uriTemplate) { + // adds prepared OPTIONS action for route + parseAction(uriTemplate, autoOptionsAction); + }); + } + }); + }; +}; + +var setup = function(options, cb) { + var sourceFiles = options.sourceFiles; + var autoOptions = options.autoOptions; + glob(sourceFiles, {} , function (err, files) { + if (err) { + console.error('Failed to parse contracts path.', err); + process.exit(1); + } + + var asyncFunctions = []; + + files.forEach(function(filePath) { + asyncFunctions.push(parseBlueprint(filePath, autoOptions)); + }); + + async.series(asyncFunctions, function(err) { + cb(err); + }); + }); +}; + +module.exports = function(options, cb) { + if (ROUTE_MAP) { + cb(null, ROUTE_MAP); + } + else { + ROUTE_MAP = {}; + setup(options, function(err) { + cb(err, ROUTE_MAP); + }); + } +}; From f004806ca0c2f4bb3eb8d47301aa6dfb908c37cd Mon Sep 17 00:00:00 2001 From: Marek Stasikowski and Simon Adcock Date: Tue, 21 Jul 2015 10:50:15 +0100 Subject: [PATCH 2/5] Always reset ROUTE_MAP --- lib/middleware/route-map.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/middleware/route-map.js b/lib/middleware/route-map.js index 18787b7..402a53f 100644 --- a/lib/middleware/route-map.js +++ b/lib/middleware/route-map.js @@ -104,13 +104,8 @@ var setup = function(options, cb) { }; module.exports = function(options, cb) { - if (ROUTE_MAP) { - cb(null, ROUTE_MAP); - } - else { - ROUTE_MAP = {}; - setup(options, function(err) { - cb(err, ROUTE_MAP); - }); - } + ROUTE_MAP = {}; + setup(options, function(err) { + cb(err, ROUTE_MAP); + }); }; From 6eac8222d250b48b2fd9acd3f18f1c3c0966e689 Mon Sep 17 00:00:00 2001 From: Marek Stasikowski and Simon Adcock Date: Tue, 21 Jul 2015 10:52:38 +0100 Subject: [PATCH 3/5] extracted parseAction module --- lib/middleware/route-map.js | 19 +++---------------- lib/parse-action.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 lib/parse-action.js diff --git a/lib/middleware/route-map.js b/lib/middleware/route-map.js index 402a53f..8d4cd46 100644 --- a/lib/middleware/route-map.js +++ b/lib/middleware/route-map.js @@ -3,24 +3,11 @@ var fs = require('fs'); var _ = require('lodash'); var protagonist = require('protagonist'); var async = require('async'); -var urlParser = require('../url-parser'); -var route = require('../route'); var autoOptionsAction = require('../json/auto-options-action.json'); var ROUTE_MAP = null; -var parseAction = function(uriTemplate, action) { - var parsedUrl = urlParser.parse(uriTemplate); - var key = parsedUrl.url; - - ROUTE_MAP[key] = ROUTE_MAP[key] || { urlExpression: parsedUrl.url, methods: {} }; - ROUTE_MAP[key].methods[action.method] = ROUTE_MAP[key].methods[action.method] || []; - - var routeHandlers = route.getRouteHandlers(key, parsedUrl, action); - Array.prototype.push.apply(ROUTE_MAP[key].methods[action.method], routeHandlers); - - console.log('[LOG]'.white, 'Setup Route:', action.method.green, key.yellow, action.name.blue); -}; +var parseAction = require('../parse-action'); var parseBlueprint = function(filePath, autoOptions) { return function(cb) { @@ -35,7 +22,7 @@ var parseBlueprint = function(filePath, autoOptions) { result.ast.resourceGroups.forEach(function(resourceGroup){ resourceGroup.resources.forEach(function(resource){ resource.actions.forEach(function(action){ - parseAction(resource.uriTemplate, action); + parseAction(resource.uriTemplate, action, ROUTE_MAP); saveRouteToTheList(resource, action); }); }); @@ -75,7 +62,7 @@ var parseBlueprint = function(filePath, autoOptions) { _.forEach(routesWithoutOptions, function (uriTemplate) { // adds prepared OPTIONS action for route - parseAction(uriTemplate, autoOptionsAction); + parseAction(uriTemplate, autoOptionsAction, ROUTE_MAP); }); } }); diff --git a/lib/parse-action.js b/lib/parse-action.js new file mode 100644 index 0000000..446db56 --- /dev/null +++ b/lib/parse-action.js @@ -0,0 +1,15 @@ +var urlParser = require('./url-parser'); +var route = require('./route'); + +module.exports = function(uriTemplate, action, routeMap) { + var parsedUrl = urlParser.parse(uriTemplate); + var key = parsedUrl.url; + + routeMap[key] = routeMap[key] || { urlExpression: parsedUrl.url, methods: {} }; + routeMap[key].methods[action.method] = routeMap[key].methods[action.method] || []; + + var routeHandlers = route.getRouteHandlers(key, parsedUrl, action); + Array.prototype.push.apply(routeMap[key].methods[action.method], routeHandlers); + + console.log('[LOG]'.white, 'Setup Route:', action.method.green, key.yellow, action.name.blue); +}; From d749681fd990f6008dd8c3ced2176d58346f97bf Mon Sep 17 00:00:00 2001 From: Marek Stasikowski and Simon Adcock Date: Tue, 21 Jul 2015 11:05:55 +0100 Subject: [PATCH 4/5] extracted parse-blueprint module --- lib/middleware/route-map.js | 68 ++----------------------------------- lib/parse-blueprint.js | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 66 deletions(-) create mode 100644 lib/parse-blueprint.js diff --git a/lib/middleware/route-map.js b/lib/middleware/route-map.js index 8d4cd46..b0fc0d2 100644 --- a/lib/middleware/route-map.js +++ b/lib/middleware/route-map.js @@ -1,73 +1,9 @@ var glob = require('glob'); -var fs = require('fs'); -var _ = require('lodash'); -var protagonist = require('protagonist'); var async = require('async'); -var autoOptionsAction = require('../json/auto-options-action.json'); var ROUTE_MAP = null; -var parseAction = require('../parse-action'); - -var parseBlueprint = function(filePath, autoOptions) { - return function(cb) { - var data = fs.readFileSync(filePath, {encoding: 'utf8'}); - protagonist.parse(data, function(err, result) { - if (err) { - console.log(err); - return; - } - - var allRoutesList = []; - result.ast.resourceGroups.forEach(function(resourceGroup){ - resourceGroup.resources.forEach(function(resource){ - resource.actions.forEach(function(action){ - parseAction(resource.uriTemplate, action, ROUTE_MAP); - saveRouteToTheList(resource, action); - }); - }); - }); - - // add OPTIONS route where its missing - this must be done after all routes are parsed - if (autoOptions) { - addOptionsRoutesWhereMissing(allRoutesList); - } - - cb(); - - /** - * Adds route and its action to the allRoutesList. It appends the action when route already exists in the list. - * @param resource Route URI - * @param action HTTP action - */ - function saveRouteToTheList(resource, action) { - // used to add options routes later - if (typeof allRoutesList[resource.uriTemplate] === 'undefined') { - allRoutesList[resource.uriTemplate] = []; - } - allRoutesList[resource.uriTemplate].push(action); - } - - function addOptionsRoutesWhereMissing(allRoutes) { - var routesWithoutOptions = []; - // extracts only routes without OPTIONS - _.forIn(allRoutes, function (actions, route) { - var containsOptions = _.reduce(actions, function (previousResult, iteratedAction) { - return previousResult || (iteratedAction.method === 'OPTIONS'); - }, false); - if (!containsOptions) { - routesWithoutOptions.push(route); - } - }); - - _.forEach(routesWithoutOptions, function (uriTemplate) { - // adds prepared OPTIONS action for route - parseAction(uriTemplate, autoOptionsAction, ROUTE_MAP); - }); - } - }); - }; -}; +var parseBlueprint = require('../parse-blueprint'); var setup = function(options, cb) { var sourceFiles = options.sourceFiles; @@ -81,7 +17,7 @@ var setup = function(options, cb) { var asyncFunctions = []; files.forEach(function(filePath) { - asyncFunctions.push(parseBlueprint(filePath, autoOptions)); + asyncFunctions.push(parseBlueprint(filePath, autoOptions, ROUTE_MAP)); }); async.series(asyncFunctions, function(err) { diff --git a/lib/parse-blueprint.js b/lib/parse-blueprint.js new file mode 100644 index 0000000..daf0b79 --- /dev/null +++ b/lib/parse-blueprint.js @@ -0,0 +1,65 @@ +var fs = require('fs'); +var protagonist = require('protagonist'); +var _ = require('lodash'); +var parseAction = require('./parse-action'); +var autoOptionsAction = require('./json/auto-options-action.json'); + +module.exports = function(filePath, autoOptions, routeMap) { + return function(cb) { + var data = fs.readFileSync(filePath, {encoding: 'utf8'}); + protagonist.parse(data, function(err, result) { + if (err) { + console.log(err); + return; + } + + var allRoutesList = []; + result.ast.resourceGroups.forEach(function(resourceGroup){ + resourceGroup.resources.forEach(function(resource){ + resource.actions.forEach(function(action){ + parseAction(resource.uriTemplate, action, routeMap); + saveRouteToTheList(resource, action); + }); + }); + }); + + // add OPTIONS route where its missing - this must be done after all routes are parsed + if (autoOptions) { + addOptionsRoutesWhereMissing(allRoutesList); + } + + cb(); + + /** + * Adds route and its action to the allRoutesList. It appends the action when route already exists in the list. + * @param resource Route URI + * @param action HTTP action + */ + function saveRouteToTheList(resource, action) { + // used to add options routes later + if (typeof allRoutesList[resource.uriTemplate] === 'undefined') { + allRoutesList[resource.uriTemplate] = []; + } + allRoutesList[resource.uriTemplate].push(action); + } + + function addOptionsRoutesWhereMissing(allRoutes) { + var routesWithoutOptions = []; + // extracts only routes without OPTIONS + _.forIn(allRoutes, function (actions, route) { + var containsOptions = _.reduce(actions, function (previousResult, iteratedAction) { + return previousResult || (iteratedAction.method === 'OPTIONS'); + }, false); + if (!containsOptions) { + routesWithoutOptions.push(route); + } + }); + + _.forEach(routesWithoutOptions, function (uriTemplate) { + // adds prepared OPTIONS action for route + parseAction(uriTemplate, autoOptionsAction, routeMap); + }); + } + }); + }; +}; From eae1f95d9c13bef5ddba532ad3412b4c69dd53da Mon Sep 17 00:00:00 2001 From: Marek Stasikowski and Simon Adcock Date: Tue, 21 Jul 2015 11:18:38 +0100 Subject: [PATCH 5/5] Inline setup method --- lib/middleware/route-map.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/lib/middleware/route-map.js b/lib/middleware/route-map.js index b0fc0d2..582f3a0 100644 --- a/lib/middleware/route-map.js +++ b/lib/middleware/route-map.js @@ -1,13 +1,12 @@ var glob = require('glob'); var async = require('async'); - -var ROUTE_MAP = null; - var parseBlueprint = require('../parse-blueprint'); -var setup = function(options, cb) { +module.exports = function(options, cb) { var sourceFiles = options.sourceFiles; var autoOptions = options.autoOptions; + var routeMap = {}; + glob(sourceFiles, {} , function (err, files) { if (err) { console.error('Failed to parse contracts path.', err); @@ -17,18 +16,11 @@ var setup = function(options, cb) { var asyncFunctions = []; files.forEach(function(filePath) { - asyncFunctions.push(parseBlueprint(filePath, autoOptions, ROUTE_MAP)); + asyncFunctions.push(parseBlueprint(filePath, autoOptions, routeMap)); }); async.series(asyncFunctions, function(err) { - cb(err); + cb(err, routeMap); }); }); }; - -module.exports = function(options, cb) { - ROUTE_MAP = {}; - setup(options, function(err) { - cb(err, ROUTE_MAP); - }); -};