node-duplicate-req
Version:
A lightweight request duplicate check for api side.
187 lines (172 loc) • 5.85 kB
JavaScript
var md5 = require( 'md5' );
var _ = require( 'lodash' );
_.mixin( require( 'lodash-deep' ) );
function DuplicateCheck( redisClient, options ){
if( !options ){
options = {}
}
this.redisClient = redisClient;
this.options = {};
this.options.ttl = options.ttl || 60;
this.options.keyProperty = options.keyProperty || 'req.authorization.credentials';
this.options.prefix = options.prefix || '';
this.options.ignoreEmptyBody = options.ignoreEmptyBody || true;
this.options.ignoreProperties = options.ignoreProperties || [];
this.options.infoLogFunc = options.infoLogFunc || function(){
};
this.options.errorLogFunc = options.errorLogFunc || function(){
};
this.options.ovrLogFunc = options.ovrLogFunc || function(){
};
this.options.dupMsg = options.dupMsg || 'Duplicate request detected';
this.options.errMsg = options.errMsg || 'Internal server error has occurred';
}
DuplicateCheck.prototype.middleware = function( options ){
var _this = this;
if( !options ){
options = this.options;
}
else{
options = {
ttl: options.ttl || this.options.ttl,
keyProperty: options.keyProperty || this.options.keyProperty,
prefix: options.prefix || this.options.prefix,
ignoreEmptyBody: options.ignoreEmptyBody || this.options.ignoreEmptyBody,
ignoreProperties: options.ignoreProperties || this.options.ignoreProperties,
infoLogFunc: options.infoLogFunc || function(){
},
errorLogFunc: options.errorLogFunc || function(){
},
ovrLogFunc: options.ovrLogFunc || function(){
},
dupMsg: options.dupMsg || 'Duplicate request detected',
errMsg: options.errMsg || 'Internal server error has occurred'
}
}
return function( req, res, next ){
if( req.headers.hasOwnProperty( 'x-override-dupcheck' ) ){
options.ovrLogFunc( req );
return next();
}
else{
var allowOneKey;
var redisKey = '';
var hash;
try{
hash = _this.buildHash( req, options );
allowOneKey = req.route.method + '-' + req.route.path + '-' + eval( options.keyProperty );
if( options.prefix ){
redisKey += options.prefix
}
redisKey += eval( options.keyProperty );
redisKey += '-' + req.route.method + '-' + req.href();
}
catch( err ){
options.errorLogFunc( err );
return res.send( 500, { errors: [ options.errMsg ] } );
}
_this.raiseFlag( allowOneKey, function( err ){
if( !!err ){
return _this.handleError( req, next, allowOneKey, err );
}
_this.checkHash( redisKey, hash, allowOneKey, function( error, isDuplicate ){
if( !!error ){
return _this.handleError( req, next, allowOneKey, error );
}
else if( isDuplicate ){
_this.redisClient.del( allowOneKey );
options.infoLogFunc( req );
return res.send( 409, { errors: [ options.dupMsg ] } );
}
else{
_this.setHash( redisKey, hash, options.ttl, next );
_this.redisClient.del( allowOneKey );
}
} );
} );
}
}
};
DuplicateCheck.prototype.handleError = function( req, next, allowOneKey, err ){
this.redisClient.del( allowOneKey );
this.options.errorLogFunc( err, req );
return next();
};
DuplicateCheck.prototype.raiseFlag = function( key, cb ){
var _this = this;
return this.redisClient.getset( key, true, function( err, previousRes ){
if( !!err ){
cb( err );
}
else if( previousRes ){
_this.wait( key, cb );
}
else{
cb( null );
}
} );
};
DuplicateCheck.prototype.checkHash = function( redisKey, hash, allowOneKey, cb ){
this.redisClient.get( redisKey, function( err, redisRes ){
if( !!err ){
cb( err, null );
}
else if( hash === redisRes ){
cb( null, true );
}
else{
cb( null, false );
}
} );
};
DuplicateCheck.prototype.wait = function( key, cb ){
var _this = this;
process.nextTick( function(){
_this.raiseFlag( key, cb );
} );
};
DuplicateCheck.prototype.buildHash = function( req, options ){
var _this = this;
var request;
var string;
if( !_.isEmpty( req.body ) ){
request = _.cloneDeep( req.body );
}
else{
request = {};
}
if( options.ignoreProperties.length > 0 ){
options.ignoreProperties.forEach( function( item ){
eval( 'delete request.' + item );
} );
}
if( _.isEmpty( request ) && options.ignoreEmptyBody ){
return '';
}
try{
string = JSON.stringify( request );
}
catch( err ){
options.errorLogFunc( err, req );
}
return md5( string );
};
DuplicateCheck.prototype.setHash = function( redisKey, hash, ttl, next ){
var _this = this;
if( hash ){
this.redisClient.set( redisKey, hash, function( err ){
if( !!err ){
_this.options.errorLogFunc( err );
return next();
}
else{
_this.redisClient.expire( redisKey, ttl );
return next();
}
} );
}
else{
return next();
}
};
module.exports = DuplicateCheck;