proxkey
Version:
Proxy/Mock server for testing build on top of HAPI.js
565 lines (503 loc) • 21.1 kB
JavaScript
var Proxkey = {};
var http = require('http');
var parseString = require('xml2js').parseString;
var Hapi = require('hapi');
var Errors = require('./error.js');
var _ = require('lodash');
var path = require('path');
var fs = require('fs');
Proxkey.Config = {};
Proxkey.Routes = [];
Proxkey.Responses = {}; // key is the requested url
Proxkey.Global = { Response: false };
Proxkey.Replace = {}; // { string_to_change: current_value }
/*
Path should have:
Required:
- (String) URL to listen on
- (Object) response
- (String) respoonse_type : application/xml, application/json, text/html, text/xml, ...
- (String) data : could be a file or string for ex: ./ for a file or {data: 'Success'} for json or <xml>hi</xml> for xml
Optional
- (String) Method : POST, GET, PUT, DEL (defult value will be GET)
- (Object) params
- (Object) failure : [{"key": "phone_num", "value": "123123123"}] - "key" the param name, "value" the param value
Example: Check out the example directory
*/
/*
Start get args
- setConfiguration(args.configuration)
- setRoutes(args.routes)
{ configuration: {}, routes: [] }
*/
Proxkey.start = function(args) {
// if args.configuration is defined
if (!args.configuration || !args.configuration.port || !args.configuration.host) {
throw new Error(Errors.HostOrPortNotDefined);
}
Proxkey.setConfiguration(args.configuration);
if (!args.routes) {
throw new Error(Errors.RoutesNotDefined);
}
Proxkey.setRoutes(args.routes);
};
/**
* @public
* Set Proxy Configuration
* @param {object} args
* @param {object} args.configuration
* @param {object} args.configuration.port
* @param {object} args.configuration.host
* @param {boolean} [args.configuration.log] - If true, logs various information to console.
* @param {object} [args.configuration.dataDir] - The base directory where
* data responses are located. Defaults to `require.main.filename`;
*/
Proxkey.setConfiguration = function(args) {
Proxkey.Config.PORT = args.port ? args.port : 9191;
Proxkey.Config.HOST = args.host ? args.host : 'localhost';
Proxkey.Config.LOG = args.log ? args.log : true;
Proxkey.Config.dataDir = args.dataDir ? args.dataDir : path.join(__dirname, '../../..');
};
/*
Set Routes (array) args
- set `Proxkey.Responses`
- setHandlerForRoute (passing args.routes[i])
*/
Proxkey.setRoutes = function(args) {
if (!_.isArray(args)) {
throw new Error(Errors.RoutesMustBeArray);
}
for (var i = 0; i < args.length; i++) {
if (!args[i].request_url) {
throw new Error(Errors.RequestURLMustBeDefined);
}
var tmp_obj = {
path: args[i].request_url,
method: args[i].method ? args[i].method : 'GET'
};
Proxkey.Responses[tmp_obj.path] = args[i];
Proxkey.Responses[tmp_obj.path].params = args[i].params ? args[i].params : {};
setHandlerForRoute(args[i], function(handler) {
tmp_obj.handler = handler;
Proxkey.Routes.push(tmp_obj);
if ( (i+1) == args.length ) {
startServer();
}
});
} //eo for loop
};
function checkIfFileExsist(args, cb) {
var dataDir;
if (Proxkey.Config.DATA_DIR) {
dataDir = Proxkey.Config.DATA_DIR;
} else {
dataDir = Path.dirname(require.main.filename);
}
var path = Path.join(dataDir, args.path);
fs.exists(path, function(exists) {
if (exists) {
cb(null, path);
} else {
cb(true, null);
}
});
}
function setHandlerForRoute(args, cb) {
if (!args.response || !args.response.success.data) {
throw new Error(Errors.RouteMustHaveResponseData);
}
//Get different mode
args.params.success = {};
_.forIn(args.params, function(value, key) {
//Check if the data is a file or not
if (args.response[key] && args.response[key].data) {
if (args.response[key].data.substr(args.response[key].data.length - 3) == 'xml' || args.response[key].data.substr(args.response[key].data.length - 4) == 'json' ) {
if (Proxkey.Config.LOG) {
console.log('Loading data from file: ', args.response[key].data);
}
setTimeout(function(){
checkIfFileExsist({ path: args.response[key].data}, function(err, result) {
if (err) {
throw new Error(Errors.FileDoesntExistDataSuccess);
}
Proxkey.Responses[args.request_url].response[key].data = fs.readFileSync(result,'utf8');
});
}, 100);
}
}else {
if (Proxkey.Config.LOG) {
console.log('Setting response different mode, this mode: ' + key + ' need to set data to');
}
}
});
var handler = function (request, reply) {
if (Proxkey.Config.LOG) {
console.log('************************************************');
console.log('Requested URL: ' + request.route.path);
}
var query_params = {};
var response_mode = [];
var xml;
var replace_args = Proxkey.Responses[request.route.path].response.success.replace || false;
var replace_arge_func = function(args){
if (replace_args && replace_args.key && replace_args.val) {
if (replace_args.response && !_.isEmpty(args.query_params)) { //use response
//Check if XML
if (args.xml.mode) {
var xml_map = replace_args.response.split('->');
var tmp_val = args.xml.result;
xml_map.forEach(function(child){
tmp_val = tmp_val[child];
});
if (_.isArray(tmp_val)) {
tmp_val = tmp_val[0];
}
replace_args.val = tmp_val;
}else {
replace_args.val = args[replace_args.response];
}
}
var re = new RegExp(replace_args.key,"gi");
var data = Proxkey.Responses[request.route.path].response.success.data;
Proxkey.Responses[request.route.path].response.success.data = data.replace(re, replace_args.val);
replace_args.key = replace_args.val;
} else { return; }
};
var _reply = function(response, result) {
if (response.delay) {
var delay_amount;
switch (typeof response.delay) {
case 'function':
delay_amount = response.delay(request);
break;
case 'number':
delay_amount = response.delay;
break;
case 'string':
delay_amount = parseInt(response.delay);
break;
default:
if (Proxkey.Config.LOG) {
console.log(Errors.UnknownDelayType);
}
break;
}
if (delay_amount && delay_amount > 0) {
return setTimeout(function() {
reply(result.data).type(result.type).code(result.code);
}, delay_amount);
}
}
// No delay
reply(result.data).type(result.type).code(result.code);
};
Proxkey.Global.Response = false;
//Set query params
if (request && (request.query || request.payload)) {
query_params = _.isEmpty(request.query) ? request.payload : request.query;
//Check if request payload is xml - SOAP
xml = { mode: false };
if (request.headers && request.headers['content-type'] && request.headers['content-type'].indexOf('xml') != -1) {
parseString(query_params, function (err, result) {
if (err) {
console.log(err);
}
xml.mode = true;
xml.result = result;
});
}
}
if (Proxkey.Config.LOG) {
console.log('Requested Params: ', query_params);
}
//Check for response code (defualt is 200)
if (!Proxkey.Responses[request.route.path] || !Proxkey.Responses[request.route.path].code) {
Proxkey.Responses[request.route.path].response.code = 200;
}
if (_.isEmpty(query_params)) {
Proxkey.Responses[request.route.path].response.success.mode = 'success';
replaceArgsInReponse(Proxkey.Responses[request.route.path].response.success, xml, query_params, function(err, result){
if (err && Proxkey.Config.LOG){
console.log('Error:: Replacing Code');
console.log(JSON.stringify(err));
}
return _reply(Proxkey.Responses[request.route.path].response.success, result);
});
} else {
if (Proxkey.Responses[request.route.path].params) {
//Get different mode
_.forIn(Proxkey.Responses[request.route.path].params,function(val1, key1) {
response_mode.push(key1);
});
//remove success from response_mode
_.remove(response_mode, function(mode) { return mode == 'success'; });
var tmp_args = {};
tmp_args.xml = xml;
tmp_args.query_params = query_params;
tmp_args.mode = response_mode;
checkIfParamInMode(tmp_args, request, function(err, result) {
if (err) {
Proxkey.Responses[request.route.path].response.success.mode = 'success';
replaceArgsInReponse(Proxkey.Responses[request.route.path].response.success, xml, query_params, function(err, result){
if (err && Proxkey.Config.LOG){
console.log('Error:: Replacing Code');
console.log(JSON.stringify(err));
}
return _reply(Proxkey.Responses[request.route.path].response.success, result);
});
}
else if (result.code == 'OK' && result.mode == 'production') {
if (!Proxkey.Responses[request.route.path].response.production) {
throw new Error(Errors.RouteProductionMustHaveURLToProxy);
}
if (!Proxkey.Responses[request.route.path].response.production.host || !Proxkey.Responses[request.route.path].response.production.port) {
throw new Error(Errors.RouteProductionMustHaveHostPathPort);
}
//Go and hit Production URL
var args = {
route_path: request.route.path,
host: Proxkey.Responses[request.route.path].response.production.host,
path: Proxkey.Responses[request.route.path].response.production.path,
port: Proxkey.Responses[request.route.path].response.production.port,
method: Proxkey.Responses[request.route.path].response.production.method ? Proxkey.Responses[request.route.path].response.production.method : Proxkey.Responses[request.route.path].method
};
/*
return reply.proxy({
host: Proxkey.Responses[request.route.path].response.production.host,
port: Proxkey.Responses[request.route.path].response.production.port,
protocol: Proxkey.Responses[request.route.path].response.production.protocol });
*/
proxyRoute(args,function(ret){
if (ret.code == 'OK'){
return _reply(Proxkey.Responses[request.route.path].response.production, Proxkey.Responses[request.route.path].response.production);
}
});
}
else if (result.code == 'OK') {
Proxkey.Responses[request.route.path].response[result.mode].mode = result.mode;
replaceArgsInReponse(Proxkey.Responses[request.route.path].response[result.mode], xml, query_params, function(err, result){
_reply(Proxkey.Responses[request.route.path].response[result.mode], result);
});
}
});
}
}
};
cb(handler);
}
/*
args.route_path - route requested
args.host - host to proxy
args.port - port to proxy
args.path - path to proxy
args.method - method to proxy
*/
function proxyRoute(args, cb) {
var options = {
host: args.host,
port: args.port,
path: args.path,
method: args.method
};
var req = http.request(options, function(res) {
Proxkey.Responses[args.route_path].response.production.code = res.statusCode;
Proxkey.Responses[args.route_path].response.production.type = res.headers['content-type'];
res.setEncoding('utf8');
res.on('data', function (chunk) {
Proxkey.Responses[args.route_path].response.production.data += chunk;
});
res.on('end', function() {
return cb({code: 'OK'});
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.end();
}
function checkIfFileExsist(args, cb) {
var local_path = path.join(Proxkey.Config.dataDir, args.path);
fs.exists(local_path, function(exists) {
if (exists) {
cb(null, local_path);
} else {
cb(true, null);
}
});
}
function checkForPattern(args, cb) {
var pattern;
//Check for pattern comparison
if (args.params && args.params.value && args.params.value.indexOf('^') !== -1 ) {
pattern = new RegExp(args.params.value);
if (pattern.test(args.request.value)) {
return cb(args, true);
}else {
return cb(args, false);
}
}
else if (args.request.value == args.params.value) { //simple comparison
return cb(args, true);
}else {
return cb(args, false);
}
}
function checkIfParamInMode (args, request, cb) {
if (args && args.xml && args.xml.mode) {
if (Proxkey.Config.LOG) {
console.log('looking for value in xml');
}
//Looping throw the the differet mode's params.{MODE}
args.mode.forEach(function(mode_value){
_.findKey(Proxkey.Responses[request.route.path].params[mode_value], function(params) {
var tmp_params = {};
tmp_params[params.key] = params.value;
var tmp_key = params.key.split('->');
var tmp_xml = args.xml.result;
tmp_key.forEach(function(xml_key){
if (tmp_xml[xml_key]){
tmp_xml = tmp_xml[xml_key];
}
if (_.isArray(tmp_xml)) {
tmp_xml = tmp_xml[0];
}
});
if (_.isArray(tmp_xml)) {
tmp_xml = tmp_xml[0];
}
//Check if we using regex
var tmp_args = {
params: params,
request: { value: tmp_xml }
};
checkForPattern(tmp_args, function (res, found){
if (found) {
if (Proxkey.Config.LOG) {
console.log('Requested Found: response.' + mode_value);
}
Proxkey.Global.Response = true;
return cb(null, {code: 'OK', mode: mode_value});
}
});
});
});
} else {
if (Proxkey.Config.LOG) {
console.log('looking for value in params');
}
args.mode.forEach(function(mode_value){
//Regular queries - REST API
_.findKey(Proxkey.Responses[request.route.path].params[mode_value], function(params) {
var tmp_params = {};
tmp_params[params.key] = params.value;
//Check if we using regex
var tmp_args = {
params: params,
request: { value: args.query_params[params.key] }
};
checkForPattern(tmp_args, function (res, found){
if (found) {
if (Proxkey.Config.LOG) {
console.log('Requested Found: response.' + mode_value);
}
Proxkey.Global.Response = true;
return cb(null, {code: 'OK', mode: mode_value});
}
});
});
});
}
if (!Proxkey.Global.Response) {
if (Proxkey.Config.LOG) {
console.log('Requested Not Found: return response.success');
}
return cb({Error: 'Coludnt find the params, return defualt'}, null);
}
};
/*
replace String in response with another
1. check if xml || json || query
2. get the value in the key if not exist do nothing
*/
function replaceArgsInReponse(args, xml, query_params, cb) {
if (!args.replace){
return cb(null,args);
}
var err;
if (!args.replace.key) {
err = Errors.ReplceKeyIsRequired;
if (Proxkey.Config.LOG) {
console.log(err);
}
return cb(err,args);
}
if (!Proxkey.Replace[args.mode] || !Proxkey.Replace[args.mode][args.replace.key]) {
Proxkey.Replace[args.mode] = {};
Proxkey.Replace[args.mode][args.replace.key] = {
value: args.replace.val || null,
pattern: args.replace.key
};
}
if (!args.replace.request_key) {
err = Errors.ReplaceRequestKeyIsRequired;
if (Proxkey.Config.LOG) {
console.log(err);
}
return cb(err,args);
}
//Get value - XML, JSON or query
if (xml.mode) { //XML
var tmp_key = args.replace.request_key.split('->');
var tmp_xml = xml.result;
tmp_key.forEach(function(xml_key){
if (tmp_xml[xml_key]){
tmp_xml = tmp_xml[xml_key];
} else {
err = Errors.ReplaceRequestKeyNotMatch;
return cb(err,args);
}
if (_.isArray(tmp_xml)){
tmp_xml = tmp_xml[0];
}
});
if (_.isArray(tmp_xml)){
tmp_xml = tmp_xml[0];
}
Proxkey.Replace[args.mode][args.replace.key].value = tmp_xml;
} else if (_.isEmpty(query_params)) {
err = Errors.QueryParamsIsEmpty;
if (Proxkey.Config.LOG) {
console.log(err);
}
} else {
if (query_params[args.replace.request_key]){
Proxkey.Replace[args.mode][args.replace.key].value = query_params[args.replace.request_key];
}
else {
err = 'Warning: Can\'t find replace.key in request query params';
if (Proxkey.Config.LOG) {
console.log(err);
}
}
}
//Set the value
var re = new RegExp(Proxkey.Replace[args.mode][args.replace.key].pattern,"gi");
args.data = args.data.replace(re, Proxkey.Replace[args.mode][args.replace.key].value);
Proxkey.Replace[args.mode][args.replace.key].pattern = Proxkey.Replace[args.mode][args.replace.key].value;
return cb(null, args);
};
/*
Start server after handler are loaded
*/
function startServer() {
var options = {
cors: true
};
//Start Server
var server = Hapi.createServer(Proxkey.Config.HOST, Proxkey.Config.PORT,options);
server.route(Proxkey.Routes);
server.start();
console.log('Proxkey is running on ' + 'http://' + Proxkey.Config.HOST + ':' + Proxkey.Config.PORT);
}
module.exports = Proxkey;
;