diff options
| author | markotibold | 2011-06-11 20:34:54 +0200 |
|---|---|---|
| committer | markotibold | 2011-06-11 20:34:54 +0200 |
| commit | 5be359fb29b1042674c18d563575146dc35e4163 (patch) | |
| tree | 509ccf13cc5ef1fafe59bc84aa8d94fd3b554ace /djangorestframework/permissions.py | |
| parent | f0b3b9d7eac61b9920176a18463c00d5c0b8f657 (diff) | |
| download | django-rest-framework-5be359fb29b1042674c18d563575146dc35e4163.tar.bz2 | |
* implemented Tom's nice config string for the trotlle rate e.g. '3/sec'
* We now have per-user, per-view and per-resource throttling
* Added a new exxception class as a convenience to detect pointless throttles
* refactored
Diffstat (limited to 'djangorestframework/permissions.py')
| -rw-r--r-- | djangorestframework/permissions.py | 117 |
1 files changed, 67 insertions, 50 deletions
diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index b3fd212b..b8a5224c 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -29,6 +29,10 @@ _503_THROTTLED_RESPONSE = ErrorResponse( {'detail': 'request was throttled'}) +class ConfigurationException(BaseException): + """To alert for bad configuration desicions as a convenience.""" + pass + class BasePermission(object): """ @@ -87,70 +91,83 @@ class IsUserOrIsAnonReadOnly(BasePermission): self.view.method != 'HEAD'): raise _403_FORBIDDEN_RESPONSE - -class PerUserThrottling(BasePermission): +class BaseThrottle(BasePermission): """ - Rate throttling of requests on a per-user basis. + Rate throttling of requests. The rate (requests / seconds) is set by a :attr:`throttle` attribute on the ``View`` class. - The attribute is a two tuple of the form (number of requests, duration in seconds). - - 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 be used. + The attribute is a string of the form 'number of requests/period'. Period must be an element + of (sec, min, hour, day) Previous request information used for throttling is stored in the cache. - """ + """ - def check_permission(self, user): - (num_requests, duration) = getattr(self.view, 'throttle', (0, 0)) - - if user.is_authenticated(): - ident = str(user) - else: - ident = self.view.request.META.get('REMOTE_ADDR', None) + def get_cache_key(self): + """Should return the cache-key corresponding to the semantics of the class that implements + the throttling behaviour. + """ + pass - key = 'throttle_%s' % ident - history = cache.get(key, []) - now = time.time() + def check_permission(self, auth): + num, period = getattr(self.view, 'throttle', '0/sec').split('/') + self.num_requests = int(num) + self.duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] + self.auth = auth + self.check_throttle() + + def check_throttle(self): + """On success calls `throttle_success`. On failure calls `throttle_failure`. """ + self.key = self.get_cache_key() + self.history = cache.get(self.key, []) + self.now = time.time() # Drop any requests from the history which have now passed the throttle duration - while history and history[0] < now - duration: - history.pop() + while self.history and self.history[0] < self.now - self.duration: + self.history.pop() - if len(history) >= num_requests: - raise _503_THROTTLED_RESPONSE - - history.insert(0, now) - cache.set(key, history, duration) - -class PerResourceThrottling(BasePermission): + if len(self.history) >= self.num_requests: + self.throttle_failure() + else: + self.throttle_success() + + def throttle_success(self): + """Inserts the current request's timesatmp along with the key into the cache.""" + self.history.insert(0, self.now) + cache.set(self.key, self.history, self.duration) + + def throttle_failure(self): + """Raises a 503 """ + raise _503_THROTTLED_RESPONSE + +class PerUserThrottling(BaseThrottle): """ - Rate throttling of requests on a per-resource basis. - - The rate (requests / seconds) is set by a :attr:`throttle` attribute on the ``View`` class. - The attribute is a two tuple of the form (number of requests, duration in seconds). - 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 be used. - - Previous request information used for throttling is stored in the cache. """ - def check_permission(self, ignore): - (num_requests, duration) = getattr(self.view, 'throttle', (0, 0)) - - - key = 'throttle_%s' % self.view.__class__.__name__ - - history = cache.get(key, []) - now = time.time() - - # Drop any requests from the history which have now passed the throttle duration - while history and history[0] < now - duration: - history.pop() + def get_cache_key(self): + if self.auth.is_authenticated(): + ident = str(self.auth) + else: + ident = self.view.request.META.get('REMOTE_ADDR', None) + return 'throttle_%s' % ident - if len(history) >= num_requests: - raise _503_THROTTLED_RESPONSE +class PerViewThrottling(BaseThrottle): + """ + The class name of the cuurent view will be used as a unique identifier. + """ + + def get_cache_key(self): + return 'throttle_%s' % self.view.__class__.__name__ + +class PerResourceThrottling(BaseThrottle): + """ + The class name of the cuurent resource will be used as a unique identifier. + Raises :exc:`ConfigurationException` if no resource attribute is set on the view class. + """ - history.insert(0, now) - cache.set(key, history, duration) + def get_cache_key(self): + if self.view.resource != None: + return 'throttle_%s' % self.view.resource.__class__.__name__ + raise ConfigurationException( + "A per-resource throttle was set to a view that does not have a resource.")
\ No newline at end of file |
