1 |
|
2 | module.exports = createRatelimit
|
3 |
|
4 | function createRatelimit(opts) {
|
5 | opts = opts || {}
|
6 |
|
7 | var counters = {}
|
8 | , limit = opts.limit || 1000
|
9 | , steps = opts.steps || 60
|
10 | , field = opts.field || "ip"
|
11 | , penalty = opts.penalty || 5000
|
12 | , tickTime = ((opts.time || 60*60*1000)/steps)|0
|
13 | , leak = Math.ceil(limit/steps)
|
14 | , penaltyLeak = Math.ceil(penalty/leak)
|
15 | , nulled = 0
|
16 | , warn = limit - leak
|
17 |
|
18 | setInterval(tick, tickTime).unref()
|
19 |
|
20 | return function ratelimit(req, res, next) {
|
21 | var remaining
|
22 | , key = req[field]
|
23 |
|
24 | if (warn < (counters[key] > 0 ? ++counters[key] : (counters[key] = 1))) {
|
25 | res.setHeader("Rate-Limit", limit)
|
26 |
|
27 | remaining = limit - counters[key]
|
28 | if (remaining < 0) {
|
29 | res.setHeader("Retry-After", tickTime * Math.ceil(-remaining/leak))
|
30 | setTimeout(block, penalty, res)
|
31 | } else {
|
32 | res.setHeader("Rate-Limit-Remaining", remaining)
|
33 | setTimeout(next, penalty - penaltyLeak - remaining * penaltyLeak)
|
34 | }
|
35 | } else {
|
36 | next()
|
37 | }
|
38 | }
|
39 |
|
40 | function block(res) {
|
41 | res.statusCode = 429
|
42 | res.end("Too Many Requests")
|
43 | }
|
44 |
|
45 | function tick() {
|
46 | var key, next
|
47 | , curr = counters
|
48 |
|
49 | if (nulled > 1000) {
|
50 | nulled = 0
|
51 | counters = next = {}
|
52 | for (key in curr) if (curr[key] > leak) {
|
53 | next[key] = curr[key] - leak
|
54 | }
|
55 | } else {
|
56 | for (key in curr) if (curr[key] > 0) {
|
57 | if ((curr[key] -= leak) <= 0) nulled++
|
58 | }
|
59 | }
|
60 | }
|
61 | }
|
62 |
|
63 |
|
64 |
|