Hook is a simple node package used for counting unique occurances (by key) in a window of time that performs and has a low memory footprint. It works with multiple instances of node if used in conjunction with redis.
This allows for throttling and statistics tracking over a period of time.
- tracking request count by url, ip, etc.. over a specific amount of time (minute, day, hour, etc...)
- throttling users based on the same principles.
middleware
Throttle by ip address
var hook = require('hook');
var connect = require('connect');
var IP_LIMIT = 1000;
var IP_PER = "day"; // hour or minute
var app = connect(
hook.middleware.throttleIP(IP_LIMIT, IP_PER)
);
Count urls by day and generate a realtime report:
var app = express.createServer(
hook.middleware.countURL("day")
);
app.get('/stats', function(req, res, next) {
req.writeHead(200, { "Content-type": "text/html" });
req.end(hook.middleware.htmlTable();
});
More specific throttling
// throttle by url.
// A url can only be visited 10 times in an hour
// hour on minute granularity
connect(
hook.middleware.throttle({
limit: 10,
buckets: 60,
seconds: 60,
prefix: "ip"
}, function (req) { return req.url; })
);
Range counters are used for collecting data in a sliding window. They are not meant to be persistent. Instead, they are a snapshot of statistical data. They are a few concepts that need to be defined:
- buckets: this is the number of containers which the counters keep. They represent a period of time in which counts/keys fall into.
- time per bucket: this is the number of seconds each bucket should represent. For instance: 24 buckets at 3600 seconds each will keep track of data for a day in hourly increments.
Example
Say we want to limit the number of times a user can log in a 10 minute time period:
var redis = require('redis').createClient();
var buckets = 10;
var seconds = 60;
var prefix = "login:users"; // for redis
var users = new hook.RangeCounter(buckets, seconds, prefix);
var limit = 100;
function userLoggedIn(username) {
users.inc(username);
}
function canUserLogin(username) {
return users.getCount(username) < limit;
}
// sync counter data to redis (prefix key is "login:users");
// if you have multiple node instances running, they will sync up
// to each other every 5 seconds
setInterval(function () { hook.RangeCounter.sync(redis, users) }, 5000);
new hook.RangeCounter(buckets, seconds, prefix)
Instantiates a range counter. Inputs:
- buckets: the number of buckets to store counts (more for more granularity
- seconds: the number of seconds each bucket represents
- prefix: the prefix used for keys in redis
counter.inc(key)
Increments counter for a specific key (eg. ip address, url)
counter.getCount(key)
Returns integer for the count of a key
counter.getCounts()
Returns a hash where the keys are the keys and the counts are the values.