Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add codeowners file #5

Open
wants to merge 54 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
bcaa208
Added reset check
Sep 12, 2018
f5361bd
Added custom exception message changes
Sep 12, 2018
4f2ca1d
changes _init()_ method args
Sep 12, 2018
50dfec8
Added exception message
Sep 12, 2018
780c62c
fixes
Sep 12, 2018
473b81d
revert changes
Sep 13, 2018
0ea7204
Added an optional reset callable that indicates if the current counte…
Sep 13, 2018
3595bee
Added header 'HTTP_X_FORWARDED_FOR' for ip key
Sep 17, 2018
b78e616
fix
Sep 17, 2018
fcacf50
print simple key attrs
Sep 17, 2018
62e7a8a
sliding window algo added
Jun 24, 2019
9ecf524
version upgraded
Jun 24, 2019
ce9dfe7
redis object issue fix
Jun 24, 2019
7e28fe0
redis connection issue
Jun 25, 2019
3c7e1a1
key expiry added
Jun 25, 2019
b6a54d5
Block if exceeded certain limit
p-v Jul 18, 2019
e7aa895
Add missing param
p-v Jul 18, 2019
900397d
Update count on offence
p-v Jul 18, 2019
05130ec
Increment count only if max offence rate is present
p-v Jul 18, 2019
ab07c9f
Update version
p-v Jul 18, 2019
2a6c25f
added rate limiting on ip by considering different attributes of the …
veeruravi Sep 12, 2019
e05a325
added utils functions to __all__
veeruravi Sep 12, 2019
7e8b849
added a util function to check if rate is valid or not
veeruravi Sep 12, 2019
79d5ff9
indentation
veeruravi Sep 12, 2019
da52b26
using pipeline
veeruravi Sep 16, 2019
854b369
removed some useless util functions
veeruravi Sep 16, 2019
4fe8349
error fix
veeruravi Sep 16, 2019
8758025
added a util function for getting the ip from the request
veeruravi Sep 16, 2019
eacad1d
considering the custom header
veeruravi Oct 15, 2019
c1d5d7a
changed the version
veeruravi Oct 15, 2019
9152987
changed the util function to get the custom ip
veeruravi Oct 16, 2019
3d958b3
considering the request url for making cache key for ip blocking
veeruravi Oct 16, 2019
0fb22f2
not updating the ttl for add to set
veeruravi Oct 16, 2019
b631aac
removed the not used arguments
veeruravi Oct 16, 2019
4701249
key error fix
veeruravi Oct 16, 2019
7f3c542
error fix
veeruravi Oct 16, 2019
9702c49
added an util function to get the right most public ip
veeruravi Oct 16, 2019
64c01ae
changed the cache header to upper case
veeruravi Oct 16, 2019
721c536
removed the ttl check
veeruravi Oct 16, 2019
f9e3a94
error fix
veeruravi Oct 16, 2019
f425e5d
using custom header everywhere for ratelimiting
veeruravi Oct 17, 2019
9fbcbf5
revertred the version
veeruravi Oct 17, 2019
3696f87
changed the version
veeruravi Oct 17, 2019
c3e54af
changed the cache key
veeruravi Oct 17, 2019
8894cad
added utils for region based ratelimiting
veeruravi Apr 2, 2020
374b5cf
changed the version
veeruravi Apr 2, 2020
c243e75
use redis cluster
alokmaurya825 Sep 2, 2021
1b5b615
Update __init__.py
alokmaurya825 Sep 2, 2021
1ab3975
cluster issue fix
alokmaurya825 Sep 2, 2021
6d7bfa5
version update
alokmaurya825 Sep 2, 2021
d777448
port variable removed
alokmaurya825 Sep 2, 2021
bad1f5b
version update
alokmaurya825 Sep 2, 2021
d5ed174
redis connection and redis cluster connection
sagaryadaviitk Sep 3, 2021
10f6124
Added codeowners file.
adityakumarsarvaiya Jun 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist
*.egg-info
docs/_build/*
.tox/
.idea/
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.fabric/ @unacademy/sre
2 changes: 1 addition & 1 deletion ratelimit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION = (1, 1, 0)
VERSION = (1, 3, 7)
__version__ = '.'.join(map(str, VERSION))

ALL = (None,) # Sentinel value for all HTTP methods.
Expand Down
6 changes: 3 additions & 3 deletions ratelimit/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
__all__ = ['ratelimit']


def ratelimit(group=None, key=None, rate=None, method=ALL, block=False):
def ratelimit(group=None, key=None, rate=None, method=ALL, block=False, reset=None):
def decorator(fn):
@wraps(fn)
def _wrapped(*args, **kw):
Expand All @@ -24,9 +24,9 @@ def _wrapped(*args, **kw):
request.limited = getattr(request, 'limited', False)
ratelimited = is_ratelimited(request=request, group=group, fn=fn,
key=key, rate=rate, method=method,
increment=True)
increment=True, reset=reset)
if ratelimited and block:
raise Ratelimited()
raise Ratelimited("Too many requests", 429)
return fn(*args, **kw)
return _wrapped
return decorator
Expand Down
17 changes: 17 additions & 0 deletions ratelimit/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,20 @@

class Ratelimited(PermissionDenied):
pass


class CustomException(Exception):
def __init__(self, message):
super(CustomException, self).__init__(message)


class DatastoreConnectionError(CustomException):
def __init__(self):
message = 'Could not establish connection to data store'
super(DatastoreConnectionError, self).__init__(message)


class RateLimited(CustomException):
def __init__(self):
message = 'Rate limited'
super(RateLimited, self).__init__(message)
141 changes: 141 additions & 0 deletions ratelimit/redis_rate_limit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import time
import redis

from functools import wraps
from django.conf import settings
from rediscluster import StrictRedisCluster
from .exceptions import RateLimited, DatastoreConnectionError

__author__ = 'vikaschahal'


class RateLimiter(object):
"""Base class for Rate Limiting"""

def __init__(self, limit, window, connection, key):
"""
:param limit: number of requests allowed
:type limit: int
:param window: window in secs in which :limit number requests allowed
:type window: int
"""
self._connection = connection
self._key = key
self._limit = limit
self._window = window

def is_allowed(self, log_current_request=True):
"""
:param log_current_request: Consider the call for rate limiting
:type log_current_request: bool
:return: Whether a requests is allowed or rate limited.
:rtype: bool
"""
raise NotImplementedError

@property
def remaining_requests(self):
raise NotImplementedError

def limit(self, func):
"""Decorator to check the rate limit."""

@wraps(func)
def decorated(*args, **kwargs):
if self.is_allowed():
return func(*args, **kwargs)
raise RateLimited

return decorated


class RedisRateLimiterConnection(object):
def __init__(self, host=None, port=None, db=0, connection=None):
self.connection = None
if host:
if settings.REDIS_HOST_INTERNAL_NEW_IS_CLUSTER:
connection = StrictRedisCluster(startup_nodes=[{'host': host, 'port': 6379}], skip_full_coverage_check=True)
else:
connection = redis.StrictRedis(host, port, db)
if not connection.ping():
raise DatastoreConnectionError
self.connection = connection
elif connection:
if not connection.ping():
raise DatastoreConnectionError
self.connection = connection
else:
raise DatastoreConnectionError


class RedisRateLimiter(RateLimiter):
def __init__(self, limit, window, connection, key):
super(RedisRateLimiter, self).__init__(limit, window, connection, key)
self._pipeline = self._connection.connection.pipeline()

def _increment_request(self):
key_value = int(time.time()) + self._window
self._pipeline.zadd(
self._key, key_value, key_value
)
self._pipeline.expire(self._key, self._window) # set key expiry
self._pipeline.execute()

def is_allowed(self, log_current_request=True):
if log_current_request:
self._increment_request()
current_time = time.time()
self._pipeline.zremrangebyscore(self._key, '-inf', current_time)
self._pipeline.zcount(self._key, '-inf', '+inf')
result = self._pipeline.execute()
return result[-1] <= self._limit

def count(self, log_current_request=True):
if log_current_request:
self._increment_request()
current_time = time.time()
self._pipeline.zremrangebyscore(self._key, '-inf', current_time)
self._pipeline.zcount(self._key, '-inf', '+inf')
result = self._pipeline.execute()
return result[-1]


class IpRateLimiter(RateLimiter):
def __init__(self, limit, window, connection, key):
super(IpRateLimiter, self).__init__(limit, window, connection, key)
self._pipeline = self._connection.connection.pipeline()

def add(self, value):
self._pipeline.exists(self._key)
results = self._pipeline.execute()
self._pipeline.sadd(self._key, value)
if not results[0]:
self._pipeline.expire(self._key, self._window)
self._pipeline.execute()

def count(self):
self._pipeline.scard(self._key)
result = self._pipeline.execute()
return result[0]

def delete(self):
self._pipeline.delete(self._key)
self._pipeline.execute()

def is_allowed(self):
self._pipeline.exists(self._key)
self._pipeline.scard(self._key)
results = self._pipeline.execute()
if not results[0]:
return True
return results[1] < self._limit

@property
def remaining_requests(self):
return self._limit - self.count()


if settings.REDIS_HOST_INTERNAL_NEW_IS_CLUSTER:
redis_connection = RedisRateLimiterConnection(host=settings.REDIS_HOST_INTERNAL_NEW)
else:
redis_connection = RedisRateLimiterConnection(host=settings.REDIS_HOST_INTERNAL, port=6379, db=0)
Loading