]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/ratelimit.py
7 class RateLimiter(misc
.Object
):
8 def handle_request(self
, request
, handler
, minutes
, limit
):
9 return RateLimiterRequest(self
.backend
, request
, handler
,
10 minutes
=minutes
, limit
=limit
)
13 class RateLimiterRequest(misc
.Object
):
14 def init(self
, request
, handler
, minutes
, limit
):
15 self
.request
= request
16 self
.handler
= handler
19 self
.minutes
= minutes
22 # What is the current time?
23 self
.now
= datetime
.datetime
.utcnow()
26 self
.expires_at
= self
.now
+ datetime
.timedelta(minutes
=self
.minutes
+ 1)
28 self
.prefix
= "-".join((
29 self
.__class
__.__name
__,
33 self
.request
.remote_ip
,
36 async def is_ratelimited(self
):
38 Returns True if the request is prohibited by the rate limiter
40 counter
= await self
.get_counter()
42 # The client is rate-limited when more requests have been
43 # received than allowed.
44 if counter
>= self
.limit
:
47 # Increment the counter
48 await self
.increment_counter()
50 # If not ratelimited, write some headers
51 self
.write_headers(counter
=counter
)
55 return "%s-%s" % (self
.prefix
, self
.now
.strftime("%Y-%m-%d-%H:%M"))
58 def keys_to_check(self
):
59 for minute
in range(self
.minutes
+ 1):
60 when
= self
.now
- datetime
.timedelta(minutes
=minute
)
62 yield "%s-%s" % (self
.prefix
, when
.strftime("%Y-%m-%d-%H:%M"))
64 async def get_counter(self
):
66 Returns the number of requests that have been done in
69 async with
await self
.backend
.cache
.pipeline() as p
:
70 for key
in self
.keys_to_check
:
74 res
= await p
.execute()
77 return sum((int(e
) for e
in res
if e
))
79 def write_headers(self
, counter
):
80 # Send the limit to the user
81 self
.handler
.set_header("X-Rate-Limit-Limit", self
.limit
)
83 # Send the user how many requests are left for this time window
84 self
.handler
.set_header("X-Rate-Limit-Remaining", self
.limit
- counter
)
86 # Send when the limit resets
87 self
.handler
.set_header("X-Rate-Limit-Reset", self
.expires_at
.strftime("%s"))
89 async def increment_counter(self
):
90 async with
await self
.backend
.cache
.pipeline() as p
:
92 await p
.incr(self
.key
)
95 await p
.expireat(self
.key
, self
.expires_at
)