diff options
Diffstat (limited to 'djangorestframework/throttling.py')
| -rw-r--r-- | djangorestframework/throttling.py | 131 |
1 files changed, 105 insertions, 26 deletions
diff --git a/djangorestframework/throttling.py b/djangorestframework/throttling.py index a096eab7..f8b098d7 100644 --- a/djangorestframework/throttling.py +++ b/djangorestframework/throttling.py @@ -1,4 +1,5 @@ from django.core.cache import cache +from djangorestframework.settings import api_settings import time @@ -13,11 +14,11 @@ class BaseThrottle(object): """ self.view = view - def check_throttle(self, request): + def allow_request(self, request): """ Return `True` if the request should be allowed, `False` otherwise. """ - raise NotImplementedError('.check_throttle() must be overridden') + raise NotImplementedError('.allow_request() must be overridden') def wait(self): """ @@ -27,7 +28,7 @@ class BaseThrottle(object): return None -class SimpleCachingThrottle(BaseThrottle): +class SimpleRateThottle(BaseThrottle): """ A simple cache implementation, that only requires `.get_cache_key()` to be overridden. @@ -41,33 +42,51 @@ class SimpleCachingThrottle(BaseThrottle): Previous request information used for throttling is stored in the cache. """ - attr_name = 'rate' - rate = '1000/day' timer = time.time + settings = api_settings + cache_format = '%(class)s_%(scope)s_%(ident)s' + scope = None def __init__(self, view): - """ - Check the throttling. - Return `None` or raise an :exc:`.ImmediateResponse`. - """ - super(SimpleCachingThrottle, self).__init__(view) - num, period = getattr(view, self.attr_name, self.rate).split('/') - self.num_requests = int(num) - self.duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] + super(SimpleRateThottle, self).__init__(view) + rate = self.get_rate_description() + self.num_requests, self.duration = self.parse_rate_description(rate) def get_cache_key(self, request): """ Should return a unique cache-key which can be used for throttling. Must be overridden. + + May return `None` if the request should not be throttled. """ raise NotImplementedError('.get_cache_key() must be overridden') - def check_throttle(self, request): + def get_rate_description(self): + """ + Determine the string representation of the allowed request rate. + """ + try: + return self.rate + except AttributeError: + return self.settings.DEFAULT_THROTTLE_RATES.get(self.scope) + + def parse_rate_description(self, rate): + """ + Given the request rate string, return a two tuple of: + <allowed number of requests>, <period of time in seconds> + """ + assert rate, "No throttle rate set for '%s'" % self.__class__.__name__ + num, period = rate.split('/') + num_requests = int(num) + duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] + return (num_requests, duration) + + def allow_request(self, request): """ Implement the check to see if the request should be throttled. - On success calls :meth:`throttle_success`. - On failure calls :meth:`throttle_failure`. + On success calls `throttle_success`. + On failure calls `throttle_failure`. """ self.key = self.get_cache_key(request) self.history = cache.get(self.key, []) @@ -110,30 +129,90 @@ class SimpleCachingThrottle(BaseThrottle): return remaining_duration / float(available_requests) -class PerUserThrottling(SimpleCachingThrottle): +class AnonRateThrottle(SimpleRateThottle): + """ + Limits the rate of API calls that may be made by a anonymous users. + + The IP address of the request will be used as the unqiue cache key. + """ + scope = 'anon' + + def get_cache_key(self, request): + if request.user.is_authenticated(): + return None # Only throttle unauthenticated requests. + + ident = request.META.get('REMOTE_ADDR', None) + + return self.cache_format % { + 'class': self.__class__.__name__, + 'scope': self.scope, + 'ident': ident + } + + +class UserRateThrottle(SimpleRateThottle): """ Limits the rate of API calls that may be made by a given user. - The user id will be used as a unique identifier if the user is - authenticated. For anonymous requests, the IP address of the client will + The user id will be used as a unique cache key if the user is + authenticated. For anonymous requests, the IP address of the request will be used. """ + scope = 'user' def get_cache_key(self, request): if request.user.is_authenticated(): ident = request.user.id else: ident = request.META.get('REMOTE_ADDR', None) - return 'throttle_user_%s' % ident + return self.cache_format % { + 'class': self.__class__.__name__, + 'scope': self.scope, + 'ident': ident + } -class PerViewThrottling(SimpleCachingThrottle): - """ - Limits the rate of API calls that may be used on a given view. - The class name of the view is used as a unique identifier to - throttle against. +class ScopedRateThrottle(SimpleRateThottle): + """ + Limits the rate of API calls by different amounts for various parts of + the API. Any view that has the `throttle_scope` property set will be + throttled. The unique cache key will be generated by concatenating the + user id of the request, and the scope of the view being accessed. """ + def __init__(self, view): + """ + Scope is determined from the view being accessed. + """ + self.scope = getattr(self.view, 'throttle_scope', None) + super(ScopedRateThrottle, self).__init__(view) + + def parse_rate_description(self, rate): + """ + Subclassed so that we don't fail if `view.throttle_scope` is not set. + """ + if not rate: + return (None, None) + return super(ScopedRateThrottle, self).parse_rate_description(rate) + def get_cache_key(self, request): - return 'throttle_view_%s' % self.view.__class__.__name__ + """ + If `view.throttle_scope` is not set, don't apply this throttle. + + Otherwise generate the unique cache key by concatenating the user id + with the '.throttle_scope` property of the view. + """ + if not self.scope: + return None # Only throttle views with `.throttle_scope` set. + + if request.user.is_authenticated(): + ident = request.user.id + else: + ident = request.META.get('REMOTE_ADDR', None) + + return self.cache_format % { + 'class': self.__class__.__name__, + 'scope': self.scope, + 'ident': ident + } |
