django rest 框架中的自定义节流响应

Custom throttling response in django rest framework

我正在将 DRF 用于 rest api,所以现在我正在对我的 api 应用限制。为此,我创建了以下油门范围

  1. userRateThrottle

  2. anonRateThrottle

  3. burstRateThrottle

  4. perViewsThrottles(因视图而异)

目前我收到以下回复:

{"detail":"Request was throttled. Expected available in 32.0 seconds."}

我想要这样的回复:

{"message":"request limit exceeded","availableIn":"32.0 seconds","throttleType":"type"}

DRF 文档中没有任何可定制的内容。如何根据要求自定义我的回复?

为此,您可以实施custom exception handler function that returns the custom response in case of a 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,因为 DRF 在任何 Throttle 类 限制请求的情况下会引发 Throttled 异常。没有信息传递给 Throttled 异常 throttle_class 正在引发该异常。

我知道这是一个旧线程,但添加到 Rahul 的回答中,这里有一种在消息中包含 throttleType 的方法:

您首先需要覆盖 Throttled 异常 class:

  1. 创建名为 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 值执行您希望的操作。我决定 连接详细信息并等待,而是使用原始详细信息。

  2. 接下来,您需要创建一个自定义视图集,将节流器传递给节流异常。创建一个名为 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)
    
  3. 现在您有一个将存储节流实例的自定义异常,以及一个将实例传递给异常的视图集,下一步是实现一个继承该视图集的视图,并且还使用您列出的节流阀 classes 之一。在你的 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"
    
  4. 此时,您的应用将像往常一样检查限制,但也会 传递限制实例。我也已将油门消息覆盖为您想要的。我们现在可以利用 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
    

    此时您可以轻松访问油门 class 的任何其他属性,但您只需要 class 名称。

  5. 最后但同样重要的是,将您的处理程序添加到 DRF 设置中:

    REST_FRAMEWORK = {
        'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
    }
    

您可以通过重写视图的 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"
        })