zoukankan      html  css  js  c++  java
  • Django Rest framework 之 节流

    一、节流

    1、简介

    节流又叫限流,限制访问。就是通常一个用户在多次请求一个页面,或者点击一个链接的时候,前几次点击是没有问题的,但是一旦连续几次之后,就会出现访问受限,离下一次访问还有50秒等的字样,在django rest framework 中有一个专门的组件来做限制访问。

    2、思路

    一旦一个用户向资源发送请求,那么根据用户的身份就有两种情况,匿名用户和认证用户。那么根据用户的身份怎么做限制访问?就是要找到用户的唯一标识。

    • 匿名用户:对于匿名用户,唯一能用来标识的只有请求的IP
    • 认证用户:认证用户的用户名,或者用户ID等。

    用户标识的问题解决了,假设设置的是每分钟只能访问5次,也就是5次/min。当用户发送请求,可以拿到用户的唯一标识,判断用户是第几次访问。有下面几种情况:

    • 第一到五次:这是可以通过的,返回资源。
      一分钟之内
      • 第六次:请求被禁止,并返回提示信息。
        一分钟之后
      • 第六次:请求别允许,返回资源。

    根据上面的情况可以得出以下思路:
    当一个用户发送请求的时候,我可以在缓存(django rest framework就是这么做的)中生成一个字典,字典的键值对分别是用户的唯一标识和用户的访问时间,例如下面:

    VISIT_RECORD = {
                'weilan': [127,125, 121,110,89,68]  # 标识第一次访问时间是68秒,第二次访问时间是89秒,第三次访问时间是110秒
    }
    

    第一步:当一个用户第一次发送请求的时候,缓存VISIT_RECORD中没有他的键,就会添加一个键是他的表示,值是一个列表,列表中存放他的第一次访问时间为t1。
    第二步:当再次发送请求的时候,会先在缓存VISIT_RECORD中找有没有他的键,如果没有,会返回第一步。如果有,取出列表,查看列表中的最后一次访问值T1,并与本次访问时间Tn比较,如果Tn-T1>60s,则将T1删除,如果Tn-T1<60s,则保留T1,因为要保证一分钟之内的访问次数。
    第三步:判断当前列表中保存的时间的个数,如果小于5个,说明一分钟之内还没有访问5次,将但访问时间Tn插入到列表头。如果个数超过5个,则说明一分钟已经访问过5次,本次访问已经是第6次,则不插人列表。

    这样根据思路就可以写出下面限流类:

    import time
    VISIT_RECORD = {}  # 这里再内存中生成,可以写入缓存
    class VisitThrottle(BaseThrottle):
    
        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) < 5:
                history.insert(0,ctime)
                return True
    
            # return True    # 表示可以继续访问
            # return False # 表示访问频率太高,被限制
    
        def wait(self):
            # 还需要等多少秒才能访问
            ctime = time.time()
            return 60 - (ctime - self.history[-1])
    

    二、示例

    1、目录结构

    同样的我们向认证,权限那样再utils包中定义限流组件

    2、具体限制访问

    对于匿名用户和认证用户做不同的限制访问

    from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
    
    
    class VisitThrottle(SimpleRateThrottle):
        scope = "anonymous"
    
        def get_cache_key(self, request, view):
            return self.get_ident(request)
    
    
    class UserThrottle(SimpleRateThrottle):
        scope = "user"
    
        def get_cache_key(self, request, view):
            return request.user.username
    

    3、配置限流类

    可以再setting.py文件中全局配置,也可以再视图中重写,局部配置,但是访问频率,需要限流类的scope属性定义。
    对于匿名用户,每分钟访问5次,认证用户,每分钟5次

    REST_FRAMEWORK = {
        "DEFAULT_THROTTLE_CLASSES":["api.utils.throttle.UserThrottle"],
        "DEFAULT_THROTTLE_RATES":{
            "anonymous":'5/m',
            "user":'10/m',
        }
    }
    

    4、视图

    from rest_framework.views import APIView
    
    class UserInfoView(APIView):
        authentication_classes = []
        permission_classes = []
        throttle_classes = [throttle.VisitThrottle]  # 标识匿名用户访问
    
        def get(self, request, *args, **kwargs):
            print(request.META.get('REMOTE_ADDR'))  # 这里可以获取到访问的IP
            return HttpResponse('访问成功')
    

    5、请求测试

    使用postman或者浏览器发送请求
    一分钟连续发送5次,正常

    发送第6次时,访问受限

    三、源码分析

    django rest framework 之 认证一样进入,request的请求流程,进入源码查看具体权限的操作

    1、进入dispath()方法

    2、进入initial()方法

    3、进入check_throttles()方法

    4、获取限流类

    获取限流类之后并实例化成对象,使得可以调用具体的方法

    同样的默认的是通过全局配置

    5、原生的限流类

    rest framework中也有相应的限流类,主要使用SimpleRateThrottle,因为在SimpleRateThrottle中的一些方法已经是实现了我们需要的逻辑

    来看一下SimpleRateThrottle具体做了什么

    class SimpleRateThrottle(BaseThrottle):
        cache = default_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)
    
        def get_cache_key(self, request, view):  # 需要返回请求发情用户的唯一标识
    
            raise NotImplementedError('.get_cache_key() must be overridden')
    
        def get_rate(self):  # 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:
                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):  # 解析配置文件中的时间等
            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):  # 这与上面节流的操作相似,是具体的逻辑
            if self.rate is None:
                return 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()
    
            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 throttle_success(self):
            self.history.insert(0, self.now)
            self.cache.set(self.key, self.history, self.duration)
            return True
    
        def throttle_failure(self):   # 请求失败的时候
            return False
    
        def wait(self):  # 返回等待时间
            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)
    

    以上就是节流的流程和源码分析

    四、总结

    节流同样可以通过全局配置和局部配置的方法,影响视图。

    值得注意的是,有一个必须要重写的接口get_cache_key()

    • 当匿名用户的时候,返回值是匿名用户的IP
    • 当为认证用户的时候,可以是用户的任何唯一标识。
      因为在VISIT_RECORD中的键是唯一的。

    scope定义了具体一个节流类怎么节流,在setting.py文件和节流类中都需要定义。SimpleRateThrottle中的parse_rate()方法对scope进行了解析

    • "user":'1/s', 表示一秒访问一次
    • "user":'1/m', 表示一分钟访问一次
    • "user":'1/h', 表示一小时访问一次
    • "user":'1/d', 表示一天访问一次
  • 相关阅读:
    跨域导致FormsAuthentication.Decrypt报错:填充无效,无法被移除
    Php构造函数construct的前下划线是双的_
    DNN学习资源整理
    改进housemenu2使网站导航亲Seo并在新窗口中打开。
    推荐10款非常优秀的 HTML5 开发工具
    Ext.Net系列:安装与使用
    Devexpress 破解方法
    Microsoft Visual Studio 2010 遇到了异常,可能是由某个扩展导致的
    浮躁和互联网
    chrome 默认以 https打开网站
  • 原文地址:https://www.cnblogs.com/welan/p/10138615.html
Copyright © 2011-2022 走看看