Django - DRF - SimpleRateThrottle 频率组件

目录

一、实现自定义访问频率控制逻辑 - BaseThrottle

1-1 访问频率实现思路(一个ip一分钟只能访问3次)

1-2 访问频率逻辑实现

1-3 源码分析

二、DRF 内置频率类 - SimpleRateThrottle

2-1 频率控制实现

 2-2 源码分析

三、频率类的使用配置

3-1 局部配置

3-2 全局配置 及 局部禁用

四、频率错误信息的中文显示 - 即 throttled 方法重写

4-1 源码分析

4-2 重写throttled方法


一、实现自定义访问频率控制逻辑 - BaseThrottle

1-1 访问频率实现思路(一个ip一分钟只能访问3次)

  1. 取出访问者ip - request.MATE.get('REMOTE_ADDR ')
  2. 第一次访问:判断当前ip不在访问字典里添加进去,并且直接返回True,表示第一次访问  - {‘ip1’:[time1,]}
  3. 非第一次访问:判断当前ip在访问字典里,继续往下走 - {‘ip1’:[time1,time2]}
  4. 循环当前ip的列表,若非空,则取出符合 当前时间 - 列表末尾时间 > 60s 的对象时间,进行pop删除操作。- 保证列表中只存在未超过间隔60s的访问时间
  5. 判断操作
    1. 列表长度<3 - 一分钟内访问不超过三次,将当前时间插入列表第一位返回True,表示验证成功
    2. 列表长度>3 - 一分钟内访问次数唱过三次,返回False,表示验证失败

1-2 访问频率逻辑实现

总结:

  • 频率类
    • 必须重写allow_request(self, request, view) 方法
    • 若频率验证通过 : 返回True
    • 若频率验证不通过:返回False
    • 必须重写 wait 方法用于频率验证不通过之后等待
  • 视图类
    • 局部使用 - throttle_classes = [drfAuth.MyThrottle, ]
'''
drfAuth.py - 基于DRF的自定义频率实现
'''

import time


class MyThrottle():
    # 用于存放ip列表
    visitor_dic = {}

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # MATA:请求header内数据
        # REMOTE_ADDR:获取ip地址
        ip = request.META.get('REMOTE_ADDR')

        ctime = time.time()
        # 不在字典中 - 第一次访问
        if ip not in self.visitor_dic:
            self.visitor_dic[ip] = [ctime, ]
            return True

        # 根据当前访问者ip,取出访问的时间列表
        history = self.visitor_dic[ip]
        self.history = history
        # history[-1]:访问时间列表最后一个
        while history and ctime - history[-1] > 60:
            history.pop()

        if len(history) < 3:
            # 把当前时间放到第0个位置上
            history.insert(0, ctime)
            return True

        return False

    # 若前方频率范围Flase则60s后才能进行操作
    def wait(self):
        # 剩余时间
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

'''
view视图函数
'''
class FrequencyTest(APIView):
    throttle_classes = [drfAuth.MyThrottle, ]

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')

1-3 源码分析

'''
check_throttles
    APIView - dispatch - initial - check_throttles
'''
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():
    # 判断指定对象内是否存在allow_request,必须传入request,和基于APIView的试图类
    # 所以,在自己的频率控制中,返回必须为True or False
	if not throttle.allow_request(request, self):
        # 若频率控制返回False 则执行类内的wait()方法抛出异常
		self.throttled(request, throttle.wait())

'''
get_throttles
'''
def get_throttles(self):
	"""
	Instantiates and returns the list of throttles that this view uses.
    实例化并返回视图使用的节流阀列表。
	"""
	return [throttle() for throttle in self.throttle_classes]		

'''
throttled
'''
def throttled(self, request, wait):
	"""
	If request is throttled, determine what kind of exception to raise.
    如果已对请求进行了控制,请确定要引发哪种异常
	"""
	raise exceptions.Throttled(wait)


'''
exceptions.Throttled(wait) - 错误的异常显示
'''
class Throttled(APIException):
    status_code = status.HTTP_429_TOO_MANY_REQUESTS
    default_detail = _('Request was throttled.')
    extra_detail_singular = 'Expected available in {wait} second.'
    extra_detail_plural = 'Expected available in {wait} seconds.'
    default_code = 'throttled'

    def __init__(self, wait=None, detail=None, code=None):
        if detail is None:
            detail = force_text(self.default_detail)
        if wait is not None:
            wait = math.ceil(wait)
            detail = ' '.join((
                detail,
                force_text(ungettext(self.extra_detail_singular.format(wait=wait),
                                     self.extra_detail_plural.format(wait=wait),
                                     wait))))
        self.wait = wait
        super(Throttled, self).__init__(detail, code)

'''
BaseThrottle - wait
'''    
def wait(self):
	"""
	Optionally, return a recommended number of seconds to wait before
	the next request.
    或者,返回在下一个请求之前等待的推荐秒数。
	"""
	return None

 

二、DRF 内置频率类 - SimpleRateThrottle

2-1 频率控制实现

总结:

  • 频率控制类
    • ​​​​​​​必须写入scope
    • 必须重写get_cache_key
      • ​​​​​​​get_cache_key返回值表示存入的查询信息,例如存入ip、存入用户名等
  • settings配置
    • ​​​​​​​REST_FRAMEWORK - 'DEFAULT_THROTTLE_RATES'
       
'''
频率控制类 - SimpleRateThrottle
    - scope - 对应settings内DEFAULT_THROTTLE_RATE的key值,用于获取value
    - get_cache_key - 返回ip
        - get_ident:源码内用于返回ip地址 
           BaseThrottle类 - get_ident(self, request)方法
           返回值 return remote_addr - remote_addr = request.META.get('REMOTE_ADDR')
'''
from rest_framework.throttling import SimpleRateThrottle


class MyThrottle(SimpleRateThrottle):
    scope = 'aaa'

    def get_cache_key(self, request, view):
        # 返回ip地址,下面二者相同
        # return request.META.get('REMOTE_ADDR')
        return self.get_ident(request)

'''
settings.py 配置
DEFAULT_THROTTLE_RATES - 配置scope频率 10次/m每分钟
'''
REST_FRAMEWORK = {
	'DEFAULT_THROTTLE_RATES':{
		'aaa':'10/m'
	}
}

 2-2 源码分析

'''
SimpleRateThrottle
'''
class SimpleRateThrottle(BaseThrottle):
    """
    A simple cache implementation, that only requires `.get_cache_key()`
    to be overridden.

    The rate (requests / seconds) is set by a `rate` attribute on the View
    class.  The attribute is a string of the form 'number_of_requests/period'.

    Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')

    Previous request information used for throttling is stored in the cache.
    """
    cache = default_cache
    timer = time.time
    cache_format = 'throttle_%(scope)s_%(ident)s'
    scope = None
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES

    def __init__(self):
        # 若self内查询不到rate
        if not getattr(self, 'rate', None):
            # 将get_rate方法内返回的值赋给rate (3/m)
            self.rate = self.get_rate()
        self.num_requests, self.duration = self.parse_rate(self.rate)

    def get_rate(self):
        """
        Determine the string representation of the allowed request rate.
        """
        # 若在self内查询不到scope的值,则抛出异常 - 必须在类定义的时候赋值scope
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            # THROTTLE_RATES settings配置文件内 - 获取一个字典,并且其中获取scope的value值
            # 即,返回了settings配置文件内THROTTLE_RATES字典内scope
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)

   def parse_rate(self, rate):
        """
        Given the request rate string, return a two tuple of:
        , 
        """
        if rate is None:
            return (None, None)
        num, period = rate.split('/')
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        return (num_requests, duration)
    
    def allow_request(self, request, view):
        """
        Implement the check to see if the request should be throttled.

        On success calls `throttle_success`.
        On failure calls `throttle_failure`.
        """
        if self.rate is None:
            return True
        # key 为ip
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True
        # history 相当于visitor_dic,放到缓存中
        self.history = self.cache.get(self.key, [])
        # now 当前时间
        self.now = self.timer()

        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            # return false
            return self.throttle_failure()
        return self.throttle_success()

    def wait(self):
        """
        Returns the recommended next request time in seconds.
        """
        if self.history:
            remaining_duration = self.duration - (self.now - self.history[-1])
        else:
            remaining_duration = self.duration

        available_requests = self.num_requests - len(self.history) + 1
        if available_requests <= 0:
            return None

        return remaining_duration / float(available_requests)

'''
BaseThrottle
    - allow_request :若继承BaseThrottle,却没有重写allow_request方法,则直接抛出异常
    - get_ident:用于获取ip地址
    - wait:返回None
'''
class BaseThrottle(object):
    """
    Rate throttling of requests.
    """

    def allow_request(self, request, view):
        """
        Return `True` if the request should be allowed, `False` otherwise.
        如果请求应该被允许,返回“True”,否则返回“False”。
        """
        raise NotImplementedError('.allow_request() must be overridden')

    def get_ident(self, request):
        """
        Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
        if present and number of proxies is > 0. If not use all of
        HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.

        通过解析HTTP_X_FORWARDED_FOR来识别发出请求的机器
        如果当前代理和代理数量为> 0。如果不使用全部
        如果可用,则使用HTTP_X_FORWARDED_FOR,如果不使用REMOTE_ADDR。
        """
        xff = request.META.get('HTTP_X_FORWARDED_FOR')
        remote_addr = request.META.get('REMOTE_ADDR')
        num_proxies = api_settings.NUM_PROXIES

        if num_proxies is not None:
            if num_proxies == 0 or xff is None:
                return remote_addr
            addrs = xff.split(',')
            client_addr = addrs[-min(num_proxies, len(addrs))]
            return client_addr.strip()

        return ''.join(xff.split()) if xff else remote_addr

    def wait(self):
        """
        Optionally, return a recommended number of seconds to wait before
        the next request.
        """
        return None

 

三、频率类的使用配置

 

3-1 局部配置

'''
试图类内 - 局部使用
'''
throttle_classes = [drfAuth.MyThrottle, ]

3-2 全局配置 及 局部禁用

'''settings.py 内全局使用'''
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['app01.drfAuth.MyThrottle', ],
}

'''
试图类内 - 局部禁用
'''
throttle_classes = []

 

四、频率错误信息的中文显示 - 即 throttled 方法重写

4-1 源码分析

总结: 调用 throttled 方法触发异常抛出 exceptions.Throttled(wait) ,所以若在 throttled 方法内重写错误字段,会覆盖Throttled内的同名属性

'''
throttled
'''
def throttled(self, request, wait):
	"""
	If request is throttled, determine what kind of exception to raise.
    如果已对请求进行了控制,请确定要引发哪种异常
	"""
	raise exceptions.Throttled(wait)


'''
exceptions.Throttled(wait) - 错误的异常显示
'''
class Throttled(APIException):
    status_code = status.HTTP_429_TOO_MANY_REQUESTS
    default_detail = _('Request was throttled.')
    extra_detail_singular = 'Expected available in {wait} second.'
    extra_detail_plural = 'Expected available in {wait} seconds.'
    default_code = 'throttled'

    def __init__(self, wait=None, detail=None, code=None):
        if detail is None:
            detail = force_text(self.default_detail)
        if wait is not None:
            wait = math.ceil(wait)
            detail = ' '.join((
                detail,
                force_text(ungettext(self.extra_detail_singular.format(wait=wait),
                                     self.extra_detail_plural.format(wait=wait),
                                     wait))))
        self.wait = wait
        super(Throttled, self).__init__(detail, code)

'''
APIException - 异常类
'''
class APIException(Exception):
    """
    Base class for REST framework exceptions.
    Subclasses should provide `.status_code` and `.default_detail` properties.
    用于REST框架异常的基类。
    子类应该提供'.status_code '和' .default_detail '属性。
    """
    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
    default_detail = _('A server error occurred.')
    default_code = 'error'

    def __init__(self, detail=None, code=None):
        if detail is None:
            detail = self.default_detail
        if code is None:
            code = self.default_code

        self.detail = _get_error_details(detail, code)

    def __str__(self):
        return six.text_type(self.detail)

4-2 重写throttled方法

思路总结:

  • 重写错误信息
    • default_detail = _('Request was throttled.')
    • extra_detail_singular = 'Expected available in {wait} second.'
    • extra_detail_plural = 'Expected available in {wait} seconds.'
  • 最后应该需要和原来的 throttled方法 一样抛出异常
    • 由于源码抛出exceptions.Throttled(wait) - 则我的异常类必须继承 exceptions.Throttled
from rest_framework import exceptions


class FrequencyTest(APIView):
    throttle_classes = [drfAuth.MyThrottle, ]

    def get(self, request):
        return HttpResponse('get')

    def post(self, request):
        return HttpResponse('post')

    def throttled(self, request, wait):
        class MyThrottled(exceptions.Throttled):
            default_detail = '您好'
            extra_detail_singular = '还剩 {wait} 秒.'
            extra_detail_plural = '还剩 {wait} 秒'
        raise MyThrottled(wait)

你可能感兴趣的:(DRF,Django)