Decorator to limit request rates to individual views

 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

  1. limit view request rate decorator by anatoliy.larin 4 years, 11 months ago
  2. caching parsed templates by forgems 6 years, 4 months ago
  3. Retrieve Latitude & Longitude for an Address from Google Geocoder V3 by whardeman 3 years, 6 months ago
  4. Global custom permissions (no model association) by miracle2k 6 years, 9 months ago
  5. PK->objects in view signature by AdamKG 6 years ago

Comments

ludvig.ericson (on September 24, 2008):

I heard Web servers do request rate limiting for you.

#

ashcrow (on September 28, 2008):

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.

#

(Forgotten your password?)