280 lines
7.9 KiB
ReStructuredText
280 lines
7.9 KiB
ReStructuredText
.. _usage-chapter:
|
|
|
|
======================
|
|
Using Django Ratelimit
|
|
======================
|
|
|
|
|
|
.. _usage-decorator:
|
|
|
|
Use as a decorator
|
|
==================
|
|
|
|
Import::
|
|
|
|
from ratelimit.decorators import ratelimit
|
|
|
|
|
|
.. py:decorator:: ratelimit(group=None, key=, rate=None, method=ALL, block=False)
|
|
|
|
:arg group:
|
|
*None* A group of rate limits to count together. Defaults to the
|
|
dotted name of the view.
|
|
|
|
:arg key:
|
|
What key to use, see :ref:`Keys <keys-chapter>`.
|
|
|
|
:arg rate:
|
|
*'5/m'* The number of requests per unit time allowed. Valid
|
|
units are:
|
|
|
|
* ``s`` - seconds
|
|
* ``m`` - minutes
|
|
* ``h`` - hours
|
|
* ``d`` - days
|
|
|
|
Also accepts callables. See :ref:`Rates <rates-chapter>`.
|
|
|
|
:arg method:
|
|
*ALL* Which HTTP method(s) to rate-limit. May be a string, a
|
|
list/tuple of strings, or the special values for ``ALL`` or
|
|
``UNSAFE`` (which includes ``POST``, ``PUT``, ``DELETE`` and
|
|
``PATCH``).
|
|
|
|
:arg block:
|
|
*False* Whether to block the request instead of annotating.
|
|
|
|
|
|
HTTP Methods
|
|
------------
|
|
|
|
Each decorator can be limited to one or more HTTP methods. The
|
|
``method=`` argument accepts a method name (e.g. ``'GET'``) or a list or
|
|
tuple of strings (e.g. ``('GET', 'OPTIONS')``).
|
|
|
|
There are two special shortcuts values, both accessible from the
|
|
``ratelimit`` decorator, the ``RatelimitMixin`` class, or the
|
|
``is_ratelimited`` helper, as well as on the root ``ratelimit`` module::
|
|
|
|
from ratelimit.decorators import ratelimit
|
|
|
|
@ratelimit(key='ip', method=ratelimit.ALL)
|
|
@ratelimit(key='ip', method=ratelimit.UNSAFE)
|
|
def myview(request):
|
|
pass
|
|
|
|
``ratelimit.ALL`` applies to all HTTP methods. ``ratelimit.UNSAFE``
|
|
is a shortcut for ``('POST', 'PUT', 'PATCH', 'DELETE')``.
|
|
|
|
|
|
Examples
|
|
--------
|
|
|
|
|
|
::
|
|
|
|
@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()
|
|
|
|
rate = lambda r: None if request.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 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()
|
|
|
|
|
|
Class-Based Views
|
|
-----------------
|
|
|
|
.. versionadded:: 0.5
|
|
|
|
The ``@ratelimit`` decorator also works on class-based view methods,
|
|
though *make sure the ``method`` argument matches the decorator*::
|
|
|
|
class MyView(View):
|
|
@ratelimit(key='ip', method='POST')
|
|
def post(self, request, *args):
|
|
# Something expensive...
|
|
|
|
.. note::
|
|
Unless given an explicit ``group`` argument, different methods of a
|
|
class-based view will be limited separate.
|
|
|
|
|
|
.. _usage-mixin:
|
|
|
|
Class-Based View Mixin
|
|
======================
|
|
|
|
.. py:class:: ratelimit.mixins.RatelimitMixin
|
|
|
|
.. versionadded:: 0.4
|
|
|
|
Ratelimits can also be applied to class-based views with the
|
|
``ratelimit.mixins.RatelimitMixin`` mixin. They are configured via class
|
|
attributes that are the same as the :ref:`decorator <usage-decorator>`,
|
|
prefixed with ``ratelimit_``, e.g.::
|
|
|
|
class MyView(RatelimitMixin, View):
|
|
ratelimit_key = 'ip'
|
|
ratelimit_rate = '10/m'
|
|
ratelimit_block = False
|
|
ratelimit_method = 'GET'
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
# Calculate expensive report...
|
|
|
|
.. versionchanged:: 0.5
|
|
The name of the mixin changed from ``RateLimitMixin`` to
|
|
``RatelimitMixin`` for consistency.
|
|
|
|
|
|
.. _usage-helper:
|
|
|
|
Helper Function
|
|
===============
|
|
|
|
In some cases the decorator is not flexible enough. If this is an
|
|
issue you use the ``is_ratelimited`` helper function. It's similar to
|
|
the decorator.
|
|
|
|
Import::
|
|
|
|
from ratelimit.utils import is_ratelimited
|
|
|
|
|
|
.. py:function:: is_ratelimited(request, group=None, key=, rate=None, method=ALL, increment=False)
|
|
|
|
:arg request:
|
|
*None* The HTTPRequest object.
|
|
|
|
:arg group:
|
|
*None* A group of rate limits to count together. Defaults to the
|
|
dotted name of the view.
|
|
|
|
:arg key:
|
|
What key to use, see :ref:`Keys <keys-chapter>`.
|
|
|
|
:arg rate:
|
|
*'5/m'* The number of requests per unit time allowed. Valid
|
|
units are:
|
|
|
|
* ``s`` - seconds
|
|
* ``m`` - minutes
|
|
* ``h`` - hours
|
|
* ``d`` - days
|
|
|
|
Also accepts callables. See :ref:`Rates <rates-chapter>`.
|
|
|
|
:arg method:
|
|
*ALL* Which HTTP method(s) to rate-limit. May be a string, a
|
|
list/tuple, or ``None`` for all methods.
|
|
|
|
:arg increment:
|
|
*False* Whether to increment the count or just check.
|
|
|
|
|
|
.. _usage-exception:
|
|
|
|
Exceptions
|
|
==========
|
|
|
|
.. py:class:: ratelimit.exceptions.Ratelimited
|
|
|
|
If a request is ratelimited and ``block`` is set to ``True``,
|
|
Ratelimit will raise ``ratelimit.exceptions.Ratelimited``.
|
|
|
|
This is a subclass of Django's ``PermissionDenied`` exception, so
|
|
if you don't need any special handling beyond the built-in 403
|
|
processing, you don't have to do anything.
|
|
|
|
If you are setting |handler403|_ in your root URLconf, you can catch this
|
|
exception in your custom view to return a different response, for example:
|
|
|
|
.. code-block:: python
|
|
|
|
def handler403(request, exception=None):
|
|
if isinstance(exception, Ratelimited):
|
|
return HttpResponse('Sorry you are blocked', status=429)
|
|
return HttpResponseForbidden('Forbidden')
|
|
|
|
.. |handler403| replace:: ``handler403``
|
|
.. _handler403: https://docs.djangoproject.com/en/2.1/topics/http/urls/#error-handling
|
|
|
|
.. _usage-middleware:
|
|
|
|
Middleware
|
|
==========
|
|
|
|
There is optional middleware to use a custom view to handle ``Ratelimited``
|
|
exceptions.
|
|
|
|
To use it, add ``ratelimit.middleware.RatelimitMiddleware`` to your
|
|
``MIDDLEWARE_CLASSES`` (toward the bottom of the list) and set
|
|
``RATELIMIT_VIEW`` to the full path of a view you want to use.
|
|
|
|
The view specified in ``RATELIMIT_VIEW`` will get two arguments, the
|
|
``request`` object (after ratelimit processing) and the exception.
|