This repository has been archived by the owner on Jun 3, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #92 from QwantResearch/directions-rate-limiter
Refactor rate limiter and define limits on directions endpoint
- Loading branch information
Showing
8 changed files
with
153 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import logging | ||
from apistar.exceptions import HTTPException | ||
from contextlib import contextmanager | ||
from redis import RedisError | ||
from redis_rate_limit import RateLimiter, TooManyRequests | ||
from idunn.utils.redis import get_redis_pool, RedisNotConfigured | ||
from idunn import settings | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
TooManyRequestsException = TooManyRequests | ||
|
||
@contextmanager | ||
def dummy_limit(): | ||
yield | ||
|
||
class HTTPTooManyRequests(HTTPException): | ||
default_status_code = 429 | ||
default_detail = 'Too Many Requests' | ||
|
||
class IdunnRateLimiter: | ||
def __init__(self, resource, max_requests, expire): | ||
try: | ||
redis_pool = get_redis_pool(db=settings['RATE_LIMITER_REDIS_DB']) | ||
except RedisNotConfigured: | ||
logger.warning("Redis URL not configured: rate limiter not started") | ||
self._limiter = None | ||
else: | ||
""" | ||
If a redis is configured, | ||
then we use the corresponding redis | ||
service in the rate limiter. | ||
""" | ||
self._limiter = RateLimiter( | ||
resource=resource, | ||
max_requests=max_requests, | ||
expire=expire, | ||
redis_pool=redis_pool | ||
) | ||
|
||
def limit(self, client, ignore_redis_error=False): | ||
if self._limiter is None: | ||
return dummy_limit() | ||
|
||
@contextmanager | ||
def limit(): | ||
try: | ||
with self._limiter.limit(client): | ||
yield | ||
except RedisError as e: | ||
if ignore_redis_error: | ||
logger.warning( | ||
'Ignoring RedisError in rate limiter for %s', | ||
self._limiter.resource, exc_info=True | ||
) | ||
yield | ||
else: | ||
raise | ||
|
||
return limit() | ||
|
||
def check_limit_per_client(self, request): | ||
client_id = request.headers.get('x-client-hash') or 'default' | ||
try: | ||
with self.limit(client=client_id, ignore_redis_error=True): | ||
pass | ||
except TooManyRequestsException: | ||
raise HTTPTooManyRequests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,30 @@ | ||
import logging | ||
from redis import ConnectionPool, RedisError | ||
from idunn import settings | ||
|
||
logger = logging.getLogger(__name__) | ||
REDIS_TIMEOUT = float(settings['REDIS_TIMEOUT']) | ||
|
||
REDIS_URL_SETTING = 'WIKI_API_REDIS_URL' | ||
REDIS_TIMEOUT_SETTING = 'WIKI_REDIS_TIMEOUT' | ||
|
||
class RedisNotConfigured(RedisError): | ||
pass | ||
|
||
def get_redis_pool(settings, db): | ||
redis_url = settings[REDIS_URL_SETTING] | ||
redis_timeout = int(settings[REDIS_TIMEOUT_SETTING]) | ||
def get_redis_pool(db): | ||
redis_url = settings['REDIS_URL'] | ||
if redis_url is None: | ||
# Fallback to old setting name | ||
redis_url = settings['WIKI_API_REDIS_URL'] | ||
if redis_url: | ||
logger.warning('"WIKI_API_REDIS_URL" setting is deprecated. Use REDIS_URL instead') | ||
|
||
if not redis_url: | ||
raise RedisNotConfigured('Missing redis url: %s not set' % REDIS_URL_SETTING) | ||
raise RedisNotConfigured('Redis URL is not set') | ||
|
||
if not redis_url.startswith('redis://'): | ||
redis_url = 'redis://' + redis_url | ||
|
||
return ConnectionPool.from_url( | ||
url=redis_url, | ||
socket_timeout=redis_timeout, | ||
socket_timeout=REDIS_TIMEOUT, | ||
db=db | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.