Blog
Throttling in Web application: with code — Django app
- May 2, 2022
- Posted by: techjediadmin
- Category: Performance Python Web application
What is Throttling?
Throttling is a mechanism created in server-side to control the rate of requests that clients can make to an API or web servers. Typically it is done as a defensive mechanism to improve security and performance. Sometimes throttling is also referred to as rate-limiting, in general throttling is a bit broader than just limiting requests to server resources. Also, this happens at every layer of network — In this post we will focus on Application layer.
Why do we need Throttling?
- Prevent starvation: Throttling ensure availability of services by avoiding resource starvation. Many denial-of-service attack incidents (intentional or unintentional) can be prevented.
- Manage fair usage & quotas: It is common in the SaaS world, where service is shared between its users or consumers. Only with rate-limiting per user we can ensure a fair and reasonable use. Also, API monetization is possible with different paid/free packages with different quotas.
- Deliver reliable service: We can deliver consistent response to every client without getting suffocated. This will drastically improve the end-user experience.
- Avoid excess costs: We can use throttling to check expense of system — for example, if an underlying resource is capable of auto-scaling to meet demand. It is very common that we don’t anticipate peak traffic (genuine or a hack) and end in excessive usage and huge bills.
- Design robust API system: You can control user authentication and access by rate limiting APIs at various levels — resource, API or application.
Throttled requests — HTTP Response Headers
Many API/Service providers respond to requests exceeding limit with following HTTP header information
- X-Ratelimit-Remaining: No.of available requests per quota.
- X-Ratelimit-Limit: Maximum requests allowed per window.
- X-Ratelimit-Reset: Remaining time (in ms) to end current quota window.
Throttling in Django application
We know the entry point for web applications built in Django is ‘views’ (views are called immediately after the routing is resolved). Django has a module ‘Django-Ratelimit’ already available for throttling purpose. All we have to do is add the @ratelimit
decorator on Django views.
Example:
from ratelimit.decorators import ratelimit@ratelimit(key='ip')
def view1(request):
# ...@ratelimit(key='ip', rate='100/h')
def view2(request):
# ...
Note: One thing to keep in mind is – Django uses ‘cache backend’ for storing rate data. We need to configure the cache system properly to supports both persistent and work across multiple deployment workers.
Class-Based Views
If you are using a class-based views, then we have to use @method_decorator
:
from django.utils.decorators import method_decorator
from django.views.generic import Viewclass MyView(View):
@method_decorator(ratelimit(key='ip', rate='1/m', method='GET'))
def get(self, request):
pass
@method_decorator(ratelimit(key='ip', rate='1/m', method='GET'), name='get')
class MyOtherView(View):
def get(self, request):
pass
It is also possible to wrap a whole view and have it at routing definition like:
from django.urls import path
from myapp.views import MyView
from ratelimit.decorators import ratelimiturlpatterns = [
path('/', ratelimit(key='ip', method='GET', rate='1/m')(MyView.as_view())),
]
Types of Throttling
Typically service providers throttle to support different business needs like monetization, authentication, security, governance, performance, availability, etc. Throttling can be applied at multiple levels:
- API-level throttling
- Application-level throttling
- User-level throttling
- Account-level throttling
More options for rate-limiting
@ratelimit(key='ip', rate='5/m')
def myview(request):
# Will be true if the same IP makes more than 5 POST
# requests/minute.
was_limited = getattr(request, 'limited', False)
return HttpResponse()@ratelimit(key='ip', rate='5/m', block=True)
def myview(request):
# If the same IP makes >5 reqs/min, will raise Ratelimited
return HttpResponse()@ratelimit(key='post:username', rate='5/m', method=['GET', 'POST'])
def login(request):
# If the same username is used >5 times/min, this will be True.
# The `username` value will come from GET or POST, determined by the
# request method.
was_limited = getattr(request, 'limited', False)
return HttpResponse()@ratelimit(key='post:username', rate='5/m')
@ratelimit(key='post:tenant', rate='5/m')
def login(request):
# Use multiple keys by stacking decorators.
return HttpResponse()@ratelimit(key='get:q', rate='5/m')
@ratelimit(key='post:q', rate='5/m')
def search(request):
# These two decorators combine to form one rate limit: the same search
# query can only be tried 5 times a minute, regardless of the request
# method (GET or POST)
return HttpResponse()@ratelimit(key='ip', rate='4/h')
def slow(request):
# Allow 4 reqs/hour.
return HttpResponse()@ratelimit(key='ip', rate='4/h')
def slow(request):
# Allow 4 reqs/hour.
return HttpResponse()rate = lambda g, r: None if r.user.is_authenticated else '100/h'
@ratelimit(key='ip', rate=rate)
def skipif1(request):
# Only rate limit anonymous requests
return HttpResponse()@ratelimit(key='user_or_ip', rate='10/s')
@ratelimit(key='user_or_ip', rate='100/m')
def burst_limit(request):
# Implement a separate burst limit.
return HttpResponse()@ratelimit(group='expensive', key='user_or_ip', rate='10/h')
def expensive_view_a(request):
return something_expensive()@ratelimit(group='expensive', key='user_or_ip', rate='10/h')
def expensive_view_b(request):
# Shares a counter with expensive_view_a
return something_else_expensive()@ratelimit(key='header:x-cluster-client-ip')
def post(request):
# Uses the X-Cluster-Client-IP header value.
return HttpResponse()@ratelimit(key=lambda g, r: r.META.get('HTTP_X_CLUSTER_CLIENT_IP',
r.META['REMOTE_ADDR'])
def myview(request):
# Use `X-Cluster-Client-IP` but fall back to REMOTE_ADDR.
return HttpResponse()
Reference
https://django-ratelimit.readthedocs.io/en/stable/
Read Similar Blogs: