【问题标题】:Custom throttling response in django rest frameworkdjango rest框架中的自定义节流响应
【发布时间】:2016-01-01 03:37:09
【问题描述】:
我将 DRF 用于休息 api,所以现在我正在对我的 api 应用节流。为此,我创建了以下油门范围
userRateThrottle
anonRateThrottle
burstRateThrottle
perViewsThrottles(因视图而异)
目前我得到以下回应:
{"detail":"Request was throttled. Expected available in 32.0 seconds."}
我想要这样的回应:
{"message":"request limit exceeded","availableIn":"32.0 seconds","throttleType":"type"}
DRF 文档中没有任何可定制的内容。如何根据要求自定义回复?
【问题讨论】:
标签:
python
django
rest
django-rest-framework
throttling
【解决方案1】:
为此,您可以实现一个custom exception handler function,在出现Throttled 异常时返回自定义响应。
from rest_framework.views import exception_handler
from rest_framework.exceptions import Throttled
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if isinstance(exc, Throttled): # check that a Throttled exception is raised
custom_response_data = { # prepare custom response data
'message': 'request limit exceeded',
'availableIn': '%d seconds'%exc.wait
}
response.data = custom_response_data # set the custom response data on response object
return response
然后,您需要将此自定义异常处理程序添加到您的 DRF 设置中。
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
我认为在不更改某些 DRF 代码的情况下知道 throttleType 会有些困难,因为在任何 Throttle 类限制请求的情况下,DRF 会引发 Throttled 异常。没有信息传递给Throttled 异常,throttle_class 引发了该异常。
【解决方案2】:
您可以通过覆盖视图的throttled 方法来更改限制响应的消息。例如:
from rest_framework.exceptions import Throttled
class SomeView(APIView):
def throttled(self, request, wait):
raise Throttled(detail={
"message":"request limit exceeded",
"availableIn":f"{wait} seconds",
"throttleType":"type"
})
【解决方案3】:
我知道这是一个旧线程,但除了 Rahul 的回答之外,还有一种在消息中包含throttleType 的方法:
您首先需要重写 Throttled 异常类:
-
创建一个名为 rest_exceptions.py 的文件,并创建以下内容:
import math
import inspect
from django.utils.encoding import force_text
from django.utils.translation import ungettext
from rest_framework import exceptions, throttling
class CustomThrottled(exceptions.Throttled):
def __init__(self, wait=None, detail=None, throttle_instance=None):
if throttle_instance is None:
self.throttle_instance = None
else:
self.throttle_instance = throttle_instance
if detail is not None:
self.detail = force_text(detail)
else:
self.detail = force_text(self.default_detail)
if wait is None:
self.wait = None
else:
self.wait = math.ceil(wait)
在这里,您为引发异常的油门实例添加一个 kwarg(如果提供)。您还可以覆盖详细消息的行为,并使用 wait 值做您想做的事情。我决定不连接详细信息并等待,而是使用原始详细信息。
-
接下来,您需要创建一个自定义视图集,将节流器传递给受节流的异常。创建一个名为 rest_viewsets.py 的文件并创建以下内容:
from rest_framework import viewsets
from .rest_exceptions import CustomThrottled
class ThrottledViewSet(viewsets.ViewSet):
"""
Adds customizability to the throtted method for better clarity.
"""
throttled_exception_class = CustomThrottled
def throttled(self, request, wait, throttle_instance=None):
"""
If request is throttled, determine what kind of exception to raise.
"""
raise self.get_throttled_exception_class()(wait, detail=self.get_throttled_message(request),
throttle_instance=throttle_instance)
def get_throttled_message(self, request):
"""
Add a custom throttled exception message to pass to the user.
Note that this does not account for the wait message, which will be added at the
end of this message.
"""
return None
def get_throttled_exception_class(self):
"""
Return the throttled exception class to use.
"""
return self.throttled_exception_class
def check_throttles(self, request):
"""
Check if request should be throttled.
Raises an appropriate exception if the request is throttled.
"""
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
self.throttled(request, throttle.wait(), throttle_instance=throttle)
-
既然您有一个将存储油门实例的自定义异常和一个将实例传递给异常的视图集,下一步是实现一个继承此视图集的视图,并使用其中一个油门类你已经列出了。在您的views.py 中,在预期的视图下(由于您没有提供,我将称之为MyViewset):
from .rest_viewsets import ThrottledViewSet
from rest_framework import throttling
class MyViewset(ThrottledViewSet):
throttle_classes = (throttling.userRateThrottle,) # Add more here as you wish
throttled_exception_class = CustomThrottled # This is the default already, but let's be specific anyway
def get_throttled_message(self, request):
"""Add a custom message to the throttled error."""
return "request limit exceeded"
-
此时,您的应用将像往常一样检查节流阀,但也会传递节流阀实例。我还将油门消息覆盖为您想要的。我们现在可以利用 Rahul 提供的解决方案,并进行一些修改。创建自定义异常处理程序:
from rest_framework.views import exception_handler
from .rest_exceptions import CustomThrottled
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if isinstance(exc, CustomThrottled): # check that a CustomThrottled exception is raised
custom_response_data = { # prepare custom response data
'message': exc.detail,
'availableIn': '%d seconds'%exc.wait,
'throttleType': type(exc.throttle_instance).__name__
}
response.data = custom_response_data # set the custom response data on response object
return response
此时您可以轻松访问油门类的任何其他属性,但您只需要类名。
-
最后但同样重要的是,将您的处理程序添加到 DRF 设置中:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}