Django Rest Framework- Throttling

 

Our simple API is up and running: end product.

We have simple protection against gigantic numbers being passed to our is_prime function but we also want to limit number of calls per day since we didn't set up any authentication yet and anyone can use it.

DRF enables us to set up basic Throttling for Anonymous users. This can be set up for Authenticated Users too but at the moment we will focus on Anonymous calls. We will be following this documentation : /api-guide/throttling/

Setup:

First of all let's add Throttling to our settings.py and name our cache :

Note: Django uses LocMemCache as default but below we are naming our cache and making it more visible for us at this point. LocMemCache is far from ideal but for this hobby project will be more than enough. 

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '50/day',
    }
}

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    }
}

We want to specify Throttle type and rates. More on that in documentation. We will start with 50/day for anon users.

In views we will set up our Throttle class, of course writing this in throttles.py would make sense for more advanced set up. We will inherit from default AnonRateThrottle and change our cache to 'default'

from rest_framework.throttling import AnonRateThrottle
from django.core.cache import caches

class CustomAnonRateThrottle(AnonRateThrottle):
    cache = caches['default']

In our AskPrime class view (from previous tutorial) we want to add our throttle_classes:

class AskPrime(views.APIView):
    """API Ask View"""
    throttle_classes = (CustomAnonRateThrottle,)
    name = "Primality Test"
    description = \
    "Ask if number is prime number /prime/<number_to_test>.\
    #...

Let's see if it works:

Now let's test it on development server, we will use slightly modified version of script used to send GET requests to our API from previous tutorial.

import time
import asyncio
import aiohttp


HEADERS = {'Content-Type': 'application/json; charset=utf-8'}
BASE_URL = 'http://127.0.0.1:8000/prime/'
PRIMES = {}

async def fetch(session, url):
    """Fetch json response coroutine"""
    async with session.get(url,headers=HEADERS) as response:
        if response.status == 429:
            print("Too many requests")
            return [{'is_prime':'TOO MANY REQUESTS'}]
        return await response.json()

async def main():
    """Main routine"""
    urls = [BASE_URL+str(i) for i in range(50)]
    tasks = []
    async with aiohttp.ClientSession() as session:
        for url in urls:
            tasks.append(fetch(session, url))

        htmls = await asyncio.gather(*tasks)

        for num, html in enumerate(htmls):
            PRIMES[num] = html[0]['is_prime']

if __name__ == '__main__':
    START = time.time()
    LOOP = asyncio.get_event_loop()
    LOOP.run_until_complete(main())
    END = time.time()-START
    print(END)
    print(PRIMES)


We added a bit that will check if status is 429( that's what DFR responds with). First 50 requests should all have API answers but running it again will result in 429 responses that we will see printed in console. 

Final notes, more to read:

With this simple set-up we enabled throttling for anonymous users of our API. Remember that in real world and scale set up should probably be using different cache such as Redis or Memcached. There are also many other things to take in consideration. More on the topic :

https://www.django-rest-framework.org/api-guide/throttling/

-https://www.django-rest-framework.org/api-guide/caching/

-DRF Source code, throttling.py