【DRF-06】rest-framework之节流

  • 1.自定义节流类,基于用户IP限制访问频率
    • 1.1:自定义节流类
import time
VISIT_RECORD = {}
class VisitThrottle(BaseThrottle):
    '''
        #(1)取出访问者ip
        #(2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
        #(3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        #(4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        #(5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
     '''

    def __init__(self):
        self.history = None

    def allow_request(self,request,view):
        # 1. 获取用户IP
        remote_addr = self.get_ident(request)

        ctime = time.time()
        if remote_addr not in VISIT_RECORD:
            VISIT_RECORD[remote_addr] = [ctime,]
            return True
        history = VISIT_RECORD.get(remote_addr)
        self.history = history

        while history and history[-1] < ctime - 60:
            history.pop()

        if len(history) < 3:
            history.insert(0,ctime)
            return True

        # return True    # 表示可以继续访问
        # return False # 表示访问频率太高,被限制

    def wait(self):
        # 还需要等多少秒才能访问
        ctime = time.time()
        return 60 - (ctime - self.history[-1])
  • 1.2:使用
class AuthView(APIView):
    """
    用于用户登录认证
    """
    authentication_classes = []
    permission_classes = []
    throttle_classes = [VisitThrottle,]
    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            print(user,pwd)
            print(request._request.POST)
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = "用户名或密码错误"
            # 为登录用户创建token
            token = md5(user)
            # 存在就更新,不存在就创建
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '请求异常'

        return JsonResponse(ret)
  • 1.3:效果

  • 2.内置节流类

    • 2.1:自定义类继承:SimpleRateThrottle,实现:get_cache_key、scope = "keyvalue"(配置文件中的key)
    • 2.2:定义
from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):
    scope = "no_login"

    def get_cache_key(self, request, view):
        return self.get_ident(request)
  • 2.3:setting.py
REST_FRAMEWORK = {
    # 全局使用的认证类
    "DEFAULT_AUTHENTICATION_CLASSES":['app01.utils.auth.FirstAuthtication','app01.utils.auth.Authtication', ],
    # "DEFAULT_AUTHENTICATION_CLASSES":['app01.utils.auth.FirstAuthtication', ],
    # "UNAUTHENTICATED_USER":lambda :"匿名用户"
    "UNAUTHENTICATED_USER":None, # 匿名,request.user = None
    "UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None
    "DEFAULT_PERMISSION_CLASSES":['app01.utils.permission.SVIPPermission',],
    "DEFAULT_THROTTLE_CLASSES":["app01.utils.throttle.VisitThrottle"],
    "DEFAULT_THROTTLE_RATES":{
        "NoLogin":'3/m',
    },
}
  • 3.节流源码流程
def check_throttles(self, request):
    """
    Check if request should be throttled.
    Raises an appropriate exception if the request is throttled.
    """
    throttle_durations = []
    for throttle in self.get_throttles():
        if not throttle.allow_request(request, self):
            throttle_durations.append(throttle.wait())
  • 4.内置节流类源码
    • 4.1:源码节流类
    • 4.2:请求进来,先执行SimpleRateThrottle的__init__()方法
class SimpleRateThrottle(BaseThrottle):
    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):
        if not getattr(self, 'rate', None):
            self.rate = self.get_rate()
        self.num_requests, self.duration = self.parse_rate(self.rate)
  • 4.3:执行get_rate()赋值给rate
 def get_rate(self):
    """
    Determine the string representation of the allowed request rate.
    """
    if not getattr(self, 'scope', None):
        msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
               self.__class__.__name__)
        raise ImproperlyConfigured(msg)

    try:
        return self.THROTTLE_RATES[self.scope]     # 全局配置DEFAULT_THROTTLE_RATES
    except KeyError:
        msg = "No default throttle rate set for '%s' scope" % self.scope
        raise ImproperlyConfigured(msg)
  • 4.3:执行parse_rate方法,赋值给self.num_requests, self.duration,分别为次数,时间
 def parse_rate(self, rate):
    """
    Given the request rate string, return a two tuple of:
    <allowed number of requests>, <period of time in seconds>
    """
    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)
  • 4.4:执行allow_request和await方法
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

    # 如果没有get_cache_key方法,直接返回True,继续下一个节流校验
    self.key = self.get_cache_key(request, view)
    if self.key is None:
        return True
    
    # 如果有,取历史记录
    self.history = self.cache.get(self.key, [])
    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 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
  • 4.5:节流类,在setting中最好不要配置多个自定义类,例如:匿名用户控制3/m,登录用户10/m,只会走一个。
posted @ 2024-05-27 22:37  Tony_xiao  阅读(14)  评论(0编辑  收藏  举报