Sending 429 Too Many Requests
¶
RFC 6585 introduced a status code specific to rate-limiting situations: HTTP 429 Too Many Requests. Here’s one way to send this status with Django Ratelimit.
Create a custom error view¶
First, create a view that returns the correct type of response (e.g.
content-type, shape, information, etc) for your application. For
example, a JSON API may return something like {"error":
"ratelimited"}
, while other applications may return XML, HTML, etc, as
needed. Or you may need to decide based on the type of request. Set the
status code of the response to 429.
# myapp/views.py
def ratelimited_error(request, exception):
# e.g. to return HTML
return render(request, 'ratelimited.html', status=429)
def ratelimited_error(request, exception):
# or other types:
return JsonResponse({'error': 'ratelimited'}, status=429)
In your app’s settings, install the RatelimitMiddleware
middleware toward the bottom of the list. You
must define RATELIMIT_VIEW
as a dotted-path to your error view:
MIDDLEWARE = (
# ... toward the bottom ...
'ratelimit.middleware.RatelimitMiddleware',
# ...
)
RATELIMIT_VIEW = 'myapp.views.ratelimited_error'
That’s it! If you already have the decorator installed, you’re good to go. Otherwise, you’ll need to install it in order to trigger the error view.
Check the exception type in handler403
¶
Alternatively, if you already have a handler403
view defined, you
can check the exception type and return a specific status code:
from django_ratelimit.exceptions import Ratelimited
def my_403_handler(request, exception):
if isinstance(exception, Ratelimited):
return render(request, '429.html', status=429)
return render(request, '403.html', status=403)
Context¶
Why doesn’t Django Ratelimit handle this itself?
There are a couple of main reasons. The first is that Django has no
built-in concept of a ratelimit exception, but it does have
PermissionDenied
. When a view throws a PermissionDenied
exception, Django has built-in facilities for handling it as a client
error (it returns an HTTP 403) instead of a server error (i.e. a 5xx
status code).
The Ratelimited
exception extends PermissionDenied
so that, if
nothing else, there should already be a way to make sure the application
is sending a 4xx status code—even if it’s not the most-correct status
code available. Ratelimited
should not be treated as a server error
because the server is working correctly. (NB: That also means that the
typical “error”-level logging is not invoked.) There is no way to
convince the built-in handler to send any status besides 403.
Furthermore, it’s impossible for Django Ratelimit to provide a default
view that does a better job guessing at the appropriate response type
than Django’s built-in PermissionDenied
view already does. We could
include a default 429.html
template with as little information as
Django’s built-in 403.html
, but it would only be slightly more
correct.
The correct response for your users will depend on your application. This means creating the right content-type (e.g. JSON, XML, HTML, etc) and content (whether it’s an API error response or a human-readable one). Django Ratelimit can’t guess that, so it’s up to you to define.
Finally, a small historical note. Django Ratelimit actually predates RFC
6585 by about a year. At the time, 403 was as common as any status for
ratelimit situations. Others were creating custom statuses, like
Twitter’s 420 Enhance Your Calm
.