express-concurrency-limit
Version:
request concurrency limit for express
176 lines (147 loc) • 3.26 kB
JavaScript
/**
* @file concurrency limit (limit request concurrency process)
* @author liulangyu(liulangyu90316@gmail.com)
* @date 2016-06-15
*/
var events = require('events');
var util = require('util');
/**
* validate conf
*
* @inner
* @param {Array} rules limit rules
* @return {boolean}
*/
function valiConf(rules) {
return Array.isArray(rules)
&& rules.length
&& rules[0].hasOwnProperty('path')
&& rules[0].hasOwnProperty('limit');
}
/**
* concurrency limit
*
* @constructor
* @extends EventEmitter
*/
function ConLimit() {
events.EventEmitter.call(this);
}
util.inherits(ConLimit, events.EventEmitter);
var proto = ConLimit.prototype;
/**
* add concurrency limit rules
*
* @public
* @param {Array} rules limit rules
* @return {ConLimit}
*/
proto.addRules = function (rules) {
if (!valiConf(rules)) {
throw new Error('rules conf wrong~');
}
var self = this;
rules = rules.slice(0);
this.queue = [];
rules.forEach(function (v) {
v.pending = [];
v.current = 0;
self.queue.push(v);
});
return this;
};
/**
* get rule object index by request path
*
* @private
* @param {string} path request path
* @return {number} index
*/
proto.getRuleIndexByPath = function (path) {
var index = -1;
this.queue.forEach(function (item, i) {
if (new RegExp('^' + item.path, 'ig').test(path)) {
index = i;
return false;
}
});
return index;
};
/**
* check if this request match rules
*
* @private
* @param {string} path request path
* @return {boolean}
*/
proto.isInRules = function (path) {
return ~this.getRuleIndexByPath(path);
};
/**
* get rule object by request path
*
* @private
* @param {string} path request path
* @return {Object}
*/
proto.getRuleByPath = function (path) {
return this.queue[this.getRuleIndexByPath(path)];
};
/**
* start resuest handle
*
* @public
* @return {Function}
*/
proto.start = function () {
var self = this;
/**
* return express middleware
*
* @param {Request} req request
* @param {Response} res response
* @param {*} next next handler
*/
return function (req, res, next) {
var path = req.path;
if (!self.isInRules(path)) {
next();
return;
}
// add listeners for each response
res.on('finish', function () {
self.nextPending(path);
});
res.on('close', function () {
self.nextPending(path);
});
var rule = self.getRuleByPath(path);
if (rule.current >= rule.limit) {
rule.pending.push({
req: req,
res: res,
next: next
});
}
else {
next();
rule.current++;
}
};
};
/**
* exec next pending in queue
*
* @private
* @param {string} path request path
*/
proto.nextPending = function (path) {
var rule = this.getRuleByPath(path);
rule.current && rule.current--;
var pending = rule.pending;
if (pending.length) {
pending.shift().next();
rule.current++;
}
};
module.exports = new ConLimit();