Login

Decorator to limit request rates to individual views

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

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months 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.

#

Please login first before commenting.