- Author:
- jensbreit
- Posted:
- September 24, 2008
- Language:
- Python
- Version:
- 1.0
- Score:
- 5 (after 5 ratings)
Example:
@limit("global", 3, 10, per_ip=True)
def view(request, ...):
The example limits the view to one request every 3 seconds per ip address. The limit is shared by every view that uses the string "global" (first parameter), which is an arbitrary string. Request succeed until the accumulated requested time in seconds (second parameter) exceeds the limit (third parameter).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | from django.db import models
from django.shortcuts import render_to_response
import datetime, random
class RequestRateManager(models.Manager):
def clean_expired(self):
"""Purges old entries from database.
If you use max_value's greater than 600 (seconds, ten minutes),
change the following line."""
l_time = datetime.datetime.now() - datetime.timedelta(seconds = 600)
self.get_query_set().filter(last_update__lt=l_time).delete()
class RequestRate(models.Model):
"""Implements a rate limit per IP.
value is increased at every request by the amount the request specifies,
which is meant to be the average number of seconds until the same
request could be made again.
value decreases at a rate of 1 per second until it is a 0.
"""
name = models.CharField(max_length=20)
ipaddr = models.IPAddressField(blank=True, null=True)
last_update = models.DateTimeField()
value = models.FloatField()
objects = RequestRateManager()
def update(self):
this_update = datetime.datetime.now()
td = this_update - self.last_update
time_delta_sec = (float(td.days) * 3600.0 * 60.0 + float(td.seconds) +
float(td.microseconds) / 1000000.0)
self.value -= time_delta_sec
if self.value < 0:
self.value = 0
self.last_update = this_update
def request(self, seconds, max_value):
self.update()
if self.value + seconds < max_value:
self.value += seconds
self.save()
return True
else:
self.save()
return False
def limit(name, seconds, max_value, per_ip=True, limit_exceeded_view=None,
limit_exceeded_template='connection_limit_exceeded.html'):
"""Makes a rate-limiting decorator"""
def default_limit_exceeded_view(*args, **kwargs):
return render_to_response(limit_exceeded_template)
limit_exceeded_view = limit_exceeded_view or default_limit_exceeded_view
def decorator(view):
def limited_view(request, *args, **kwargs):
if random.random() < 0.05:
RequestRate.objects.clean_expired()
if per_ip:
ipaddr = request.META['REMOTE_ADDR']
else:
ipaddr = None
(l,tmp) = RequestRate.objects.get_or_create(name=name, ipaddr=ipaddr,
defaults={ 'last_update': datetime.datetime.now(), 'value': 0 })
if l.request(seconds, max_value):
return view(request, *args, **kwargs)
else:
return limit_exceeded_view(*args, **kwargs)
return limited_view
return decorator
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
I heard Web servers do request rate limiting for you.
#
Webservers can but this snippet is still pretty cool. If the application itself has the possibility of being deployed to different webservers/appservers then it makes sense ... esp if you make it configurable for the application administrator.
But if your making an app that will only be deployed to one webserver/appserver type and it supports rate limiting then it makes sense to do it at the appserver level.
#
Please login first before commenting.