UNPKG

1.88 kBJavaScriptView Raw
1
2
3// Leaky bucket
4//
5// 14.37 Retry-After
6//
7// Retry-After = "Retry-After" ":" ( HTTP-date | delta-seconds )
8//
9// Retry-After: Fri, 31 Dec 1999 23:59:59 GMT
10// Retry-After: 120
11
12
13module.exports = createRatelimit
14
15function createRatelimit(opts) {
16 opts = opts || {}
17
18 var counters = {}
19 , log = require("../log.js")("server:ratelimit:" + opts.name)
20 , nulls = 0
21 , limit = opts.limit || 1000
22 , time = opts.time || 60*60*1000
23 , steps = opts.steps || 60
24 , field = opts.field || "ip"
25 , penalty = opts.penalty || 5000
26 , tickTime = (time/steps)|0
27 , leak = Math.ceil(limit/steps)
28
29 log("create %o", opts)
30 setInterval(tick, tickTime)
31
32 return ratelimit
33
34 function ratelimit(req, res, next) {
35 var key = req[field]
36 , remaining = limit - (counters[key] || (counters[key] = 0)) - 1
37
38 counters[key]++
39
40 if (remaining < leak) {
41 res.setHeader("Rate-Limit", limit)
42
43 if (remaining < 0) {
44 log.info(field, key)
45 setTimeout(block, penalty, res, remaining)
46 } else {
47 res.setHeader("Rate-Limit-Remaining", remaining)
48 setTimeout(next, Math.ceil((leak - remaining) / leak * penalty))
49 }
50 } else {
51 next()
52 }
53 }
54
55 function block(res, remaining) {
56 res.statusCode = 429
57 res.setHeader("Retry-After", tickTime * Math.ceil(-remaining/leak))
58 res.end("Too Many Requests")
59 }
60
61 function tick() {
62 var key, counter, next
63 , count = 0
64 , clean = 0
65
66 if (nulls > 1000) {
67 next = {}
68 for (key in counters) if (counters[key] > leak) {
69 count++
70 next[key] = counters[key] - leak
71 } else clean++
72 nulls = 0
73 counters = next
74 } else {
75 for (key in counters) if (null !== (counter = counters[key])) {
76 if (counter > leak) {
77 count++
78 counters[key] -= leak
79 } else {
80 clean++
81 counters[key] = null
82 }
83 }
84 nulls += clean
85 }
86 if (clean > 0) log("leak:%s clean:%s size:%s", leak, clean, count)
87 }
88}
89
90
91