var error = require("http-error"); var MAX_EXPIRE_INTERVAL = 5000; /** * In memory rate limiter as connect middleware */ module.exports = ratelimit; function ratelimit(key, duration, max) { var requests = Object.create(null); // in case there handles like 'constructor' setInterval(function() { Object.keys(requests).forEach(expireRequests); }, Math.min(duration * 0.75, MAX_EXPIRE_INTERVAL)); function expireRequests(handle) { var requestsForHandle = requests[handle]; var totalToSplice = 0; var expireTime = Date.now() - duration; /* Requests are already sorted by date as they are appended, so we just loop until we find one that shouldn't have expired and splice them from the list */ for (totalToSplice = 0; totalToSplice < requestsForHandle.length; totalToSplice++) { if (requestsForHandle[totalToSplice] >= expireTime) break; } requests[handle].splice(0, totalToSplice); if (requests[handle].length == 0) { delete requests[handle]; } return true; } return function(req, res, next) { var handle = req.params[key]; requests[handle] = requests[handle] || []; if (requests[handle].length >= max) { var err = new error.TooManyRequests("Rate limit exceeded"); err.retryIn = Math.min(duration, 5000); return next(err); } requests[handle].push(Date.now()); return next(); }; }