Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
133 lines (104 sloc) 4.45 KB
import time
from django.core.cache import cache
class BaseThrottle(object):
"""
A simplified, swappable base class for throttling.
Does nothing save for simulating the throttling API and implementing
some common bits for the subclasses.
Accepts a number of optional kwargs::
* ``throttle_at`` - the number of requests at which the user should
be throttled. Default is 150 requests.
* ``timeframe`` - the length of time (in seconds) in which the user
make up to the ``throttle_at`` requests. Default is 3600 seconds (
1 hour).
* ``expiration`` - the length of time to retain the times the user
has accessed the api in the cache. Default is 604800 (1 week).
"""
def __init__(self, throttle_at=150, timeframe=3600, expiration=None):
self.throttle_at = throttle_at
# In seconds, please.
self.timeframe = timeframe
if expiration is None:
# Expire in a week.
expiration = 604800
self.expiration = int(expiration)
def convert_identifier_to_key(self, identifier):
"""
Takes an identifier (like a username or IP address) and converts it
into a key usable by the cache system.
"""
bits = []
for char in identifier:
if char.isalnum() or char in ['_', '.', '-']:
bits.append(char)
safe_string = ''.join(bits)
return "%s_accesses" % safe_string
def should_be_throttled(self, identifier, **kwargs):
"""
Returns whether or not the user has exceeded their throttle limit.
Always returns ``False``, as this implementation does not actually
throttle the user.
"""
return False
def accessed(self, identifier, **kwargs):
"""
Handles recording the user's access.
Does nothing in this implementation.
"""
pass
class CacheThrottle(BaseThrottle):
"""
A throttling mechanism that uses just the cache.
"""
def should_be_throttled(self, identifier, **kwargs):
"""
Returns whether or not the user has exceeded their throttle limit.
Maintains a list of timestamps when the user accessed the api within
the cache.
Returns ``False`` if the user should NOT be throttled or ``True`` if
the user should be throttled.
"""
key = self.convert_identifier_to_key(identifier)
# Make sure something is there.
cache.add(key, [])
# Weed out anything older than the timeframe.
minimum_time = int(time.time()) - int(self.timeframe)
times_accessed = [access for access in cache.get(key) if access >= minimum_time]
cache.set(key, times_accessed, self.expiration)
if len(times_accessed) >= int(self.throttle_at):
# Throttle them.
return True
# Let them through.
return False
def accessed(self, identifier, **kwargs):
"""
Handles recording the user's access.
Stores the current timestamp in the "accesses" list within the cache.
"""
key = self.convert_identifier_to_key(identifier)
times_accessed = cache.get(key, [])
times_accessed.append(int(time.time()))
cache.set(key, times_accessed, self.expiration)
class CacheDBThrottle(CacheThrottle):
"""
A throttling mechanism that uses the cache for actual throttling but
writes-through to the database.
This is useful for tracking/aggregating usage through time, to possibly
build a statistics interface or a billing mechanism.
"""
def accessed(self, identifier, **kwargs):
"""
Handles recording the user's access.
Does everything the ``CacheThrottle`` class does, plus logs the
access within the database using the ``ApiAccess`` model.
"""
# Do the import here, instead of top-level, so that the model is
# only required when using this throttling mechanism.
from tastypie.models import ApiAccess
super(CacheDBThrottle, self).accessed(identifier, **kwargs)
# Write out the access to the DB for logging purposes.
ApiAccess.objects.create(
identifier=identifier,
url=kwargs.get('url', ''),
request_method=kwargs.get('request_method', '')
)