zoukankan      html  css  js  c++  java
  • Django rest framework 限制访问频率(源码分析)

    基于 http://www.cnblogs.com/ctztake/p/8419059.html 

    当用发出请求时 首先执行dispatch函数,当执行当第二部时:

       #2.处理版本信息 处理认证信息 处理权限信息 对用户的访问频率进行限制
                self.initial(request, *args, **kwargs)

    进入到initial方法:

      def initial(self, request, *args, **kwargs):
            """
            Runs anything that needs to occur prior to calling the method handler.
            """
            self.format_kwarg = self.get_format_suffix(**kwargs)
    
            # Perform content negotiation and store the accepted info on the request
            neg = self.perform_content_negotiation(request)
            request.accepted_renderer, request.accepted_media_type = neg
    
            # Determine the API version, if versioning is in use.
            #2.1处理版本信息
            version, scheme = self.determine_version(request, *args, **kwargs)
            request.version, request.versioning_scheme = version, scheme
    
            # Ensure that the incoming request is permitted
            #2.2处理认证信息
            self.perform_authentication(request)
            #2.3处理权限信息
            self.check_permissions(request)
            #2.4对用户的访问频率进行限制
            self.check_throttles(request)
     #2.4对用户的访问频率进行限制
            self.check_throttles(request)

    下面 开始 限流的具体分析:

    一、执行check_throttles方法

        def check_throttles(self, request):
            """
            Check if request should be throttled.
            Raises an appropriate exception if the request is throttled.
            """
            #遍历throttle对象列表
            for throttle in self.get_throttles():
                #根据allow_request()的返回值进行下一步操作,返回True的话不执行下面代码,标识不限流,返回False的话执行下面代码,还可以抛出异常
                if not throttle.allow_request(request, self):
                    #返回False的话执行
                    self.throttled(request, throttle.wait())

    二、执行allow_request方法

    首先找到BaseThrottle类,有好多类继承了该类,并且都有allow_request方法,至于执行那个类中的allow_request方法,取决于我们自定义的Throttle这个类继承谁

    class BaseThrottle(object):
        """
        Rate throttling of requests.
        """
    
        def allow_request(self, request, view):
            """
            Return `True` if the request should be allowed, `False` otherwise.
            """
            raise NotImplementedError('.allow_request() must be overridden')
        #获取唯一标识,匿名用户用ip地址,认证用户用自己的user信息
        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.
            """
            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

    三、具体分析以具有代表性的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 `throttle` 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):
            #判断是否有rate,实际上rate就是我们定义的scope的值
            if not getattr(self, 'rate', None):
                #没有调用get_rate()获取
                self.rate = self.get_rate()
            #num_requests代表具体的次数 duration代表具体的时间单位
            self.num_requests, self.duration = self.parse_rate(self.rate)
    
        def get_cache_key(self, request, view):
            """
            Should return a unique cache-key which can be used for throttling.
            Must be overridden.
    
            May return `None` if the request should not be throttled.
            """
            raise NotImplementedError('.get_cache_key() must be overridden')
    
        def get_rate(self):
            """
            Determine the string representation of the allowed request rate.
            """
            #判断当前类中我们定义的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:
                #有的话直接返回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:
            <allowed number of requests>, <period of time in seconds>
            """
            if rate is None:
                return (None, None)
            #rate实际就是我们自定义的scope的值 如10/m(代表10次每分钟)
            #按'/'切分num代表次数,period代表时间
            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`.
            """
            #如果没有rate也就是scope没有值
            #如果没有则表示不限流,
            if self.rate is None:
                return True
    
            #get_cache_key必须自己重写,如果没有则表示不限流
            #key一般是唯一标示如用户名密码 或者登陆者的ip地址
            self.key = self.get_cache_key(request, view)
            if self.key is None:
                return True
            #获取当前key所对应的时间列表,也就是用户每一次访问的时间都放到该列表中
            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 False
                return self.throttle_failure()
            #return True
            return self.throttle_success()
    
        def throttle_success(self):
            """
            Inserts the current request's timestamp along with the key
            into the cache.
            """
            self.history.insert(0, self.now)
            self.cache.set(self.key, self.history, self.duration)
            return True
    
        def throttle_failure(self):
            """
            Called when a request to the API has failed due to throttling.
            """
            return False
    
        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)

    四、回到第一步,执行allow_request方法

     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`.
            """
            #如果没有rate也就是scope没有值
            #如果没有则表示不限流,
            if self.rate is None:
                return True
    
            #get_cache_key必须自己重写,如果没有则表示不限流
            #key一般是唯一标示如用户名密码 或者登陆者的ip地址
            self.key = self.get_cache_key(request, view)
            if self.key is None:
                return True
            #获取当前key所对应的时间列表,也就是用户每一次访问的时间都放到该列表中
            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 False
                return self.throttle_failure()
            #return True
            return self.throttle_success()

    根据allow_request方法的返回值确定具体执行,返回False的话执行

    #根据allow_request()的返回值进行下一步操作,返回True的话不执行下面代码,标识不限流,返回False的话执行下面代码,还可以抛出异常
                if not throttle.allow_request(request, self):
                    #返回False的话执行
                    self.throttled(request, throttle.wait())
     def throttled(self, request, wait):
            """
            If request is throttled, determine what kind of exception to raise.
            """
            raise 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

      url(r'^limit/', app03_views.Limitview.as_view()),
    import time
    
    from django.shortcuts import render
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.throttling import BaseThrottle
    from rest_framework import exceptions
    # Create your views here.
    
    
    AllOW={}
    
    class MyThrottle(BaseThrottle):
        '''
        要求每分钟只能访问十次
        '''
        ip = '1.1.1.1'
        def allow_request(self, request, view):
    
            ctime=time.time()
            ip=self.ip
            if ip not in AllOW:
                AllOW[ip]=[ctime,]
            else:
                time_list=AllOW[ip]
                while True:
                    if ctime-60>time_list[-1]:
                        time_list.pop()
                    else :
                        break
                if len(AllOW[ip])>10:
                    return False
                AllOW[ip].insert(0,ctime)
            return True
    
        def wait(self):
            ip=self.ip
            ctime=time.time()
            first_in_time=AllOW[ip][-1]
            wt=60-(ctime-first_in_time)
            return wt
    
    
    
    
    
    class Limitview(APIView):
        throttle_classes=[MyThrottle,]
        def get(self,reques):
            self.dispatch()
            return Response('欢迎访问')
    
        def throttled(self,request,wait):
            class InnerThrottled(exceptions.Throttled):
                default_detail = '请求被限制.'
                extra_detail_singular = 'Expected available in {wait} second.'
                extra_detail_plural = '还需要再等待{wait}秒'
    
            raise InnerThrottled(wait)
    Views
    REST_FRAMEWORK = {
        'UNAUTHENTICATED_USER': None,
        'UNAUTHENTICATED_TOKEN': None,
        "DEFAULT_AUTHENTICATION_CLASSES": [
          # "app01.utils.MyAuthentication",
            "app02.utils.MyAuthentication",
        ],
        "DEFAULT_PERMISSION_CLASSES":[
           "app02.utils.MyPermission",
           "app02.utils.AdminPermission",
        ],
    }
    settings

    seetings中没有配置跟Throttle有的的信息

    继承SimpleRateThrottle

      url(r'^limit/', app03_views.Limitview.as_view()),
    urls
    import time
    
    from django.shortcuts import render
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.throttling import BaseThrottle
    from rest_framework.throttling import SimpleRateThrottle
    from rest_framework import exceptions
    # Create your views here.
    
    
    AllOW={}
    
    class MyThrottle(BaseThrottle):
        '''
        要求每分钟只能访问十次
        '''
        ip = '1.1.1.1'
        def allow_request(self, request, view):
    
            ctime=time.time()
            ip=self.ip
            if ip not in AllOW:
                AllOW[ip]=[ctime,]
            else:
                time_list=AllOW[ip]
                while True:
                    if ctime-60>time_list[-1]:
                        time_list.pop()
                    else :
                        break
                if len(AllOW[ip])>10:
                    return False
                AllOW[ip].insert(0,ctime)
            return True
    
        def wait(self):
            ip=self.ip
            ctime=time.time()
            first_in_time=AllOW[ip][-1]
            wt=60-(ctime-first_in_time)
            return wt
    
    
    class MySimpleRateThrottle(SimpleRateThrottle):
        #必须要写因为SimpleRateThrottle中要求了必须写
        '''
         def get_rate(self):
            """
            Determine the string representation of the allowed request rate.
            """
            #判断当前类中我们定义的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)
        '''
        scope = 'tiga'
    
        #也必须必须要写
        '''
            def get_cache_key(self, request, view):
            """
            Should return a unique cache-key which can be used for throttling.
            Must be overridden.
    
            May return `None` if the request should not be throttled.
            """
            raise NotImplementedError('.get_cache_key() must be overridden')
        '''
        def get_cache_key(self, request, view):
            return self.get_ident(request)
    
    
    
    class Limitview(APIView):
        throttle_classes=[MySimpleRateThrottle,]
        def get(self,reques):
            return Response('欢迎访问')
    
        def throttled(self,request,wait):
            class InnerThrottled(exceptions.Throttled):
                default_detail = '请求被限制.'
                extra_detail_singular = 'Expected available in {wait} second.'
                extra_detail_plural = '还需要再等待{wait}秒'
    
            raise InnerThrottled(wait)
    Views
    from django.db import models
    
    # Create your models here.
    class Userinfo(models.Model):
        name=models.CharField(max_length=32,verbose_name='用户名')
        pwd=models.CharField(max_length=32,verbose_name='密码')
        token=models.CharField(max_length=64,null=True)
    
        def __str__(self):
            return self.name
    View Code
    REST_FRAMEWORK = {
        'UNAUTHENTICATED_USER': None,
        'UNAUTHENTICATED_TOKEN': None,
        "DEFAULT_AUTHENTICATION_CLASSES": [
          # "app01.utils.MyAuthentication",
            "app02.utils.MyAuthentication",
        ],
        "DEFAULT_PERMISSION_CLASSES":[
           "app02.utils.MyPermission",
           "app02.utils.AdminPermission",
        ],
        "DEFAULT_THROTTLE_RATES":{
           'tiga':'10/m',
        }
    }
    settings

    认证,权限,限流综合联系

        url(r'^index/', app04_views.IndexView.as_view()),
        url(r'^user/', app04_views.UserView.as_view()),
        url(r'^manage/', app04_views.ManageView.as_view()),
    urls
    from django.shortcuts import render
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.authentication import BaseAuthentication
    from rest_framework.permissions import BasePermission
    from rest_framework.throttling import SimpleRateThrottle
    from rest_framework import exceptions
    
    from app01 import models
    
    # Create your views here.
    class MyAuthentication(BaseAuthentication):
        def authenticate(self, request):
            token=request.query_params.get('token')
            user=models.Userinfo.objects.filter(token=token).first()
            if user:
                return (user.name,user)
            return None
    
    class UserPermission(BasePermission):
        message='登录用户才可以访问'
        def has_permission(self, request, view):
            if request.user:
                return True
            return False
    class AdminPermission(BasePermission):
        message='管理员才能访问'
        def has_permission(self, request, view):
            if request.user =='ctz':
                return True
            return False
    
    
    class AnnoThrottle(SimpleRateThrottle):
        scope = 'anno'
        def get_cache_key(self, request, view):
            #如果是匿名用户则执行
            if not request.user:
                return self.get_ident(request)
            #如果不是匿名用户则让他执行
            return None
    
    class UserThrottle(SimpleRateThrottle):
        scope = 'user'
    
        def get_cache_key(self, request, view):
            #当前用户登陆了,并且当前用户不是管理员
            if request.user and request.user!='ctz':
                return self.get_ident(request)
            #如果是匿名用户和管理员 则让他继续执行
            return None
    
    class AdminThrottle(SimpleRateThrottle):
        scope = 'admin'
    
        def get_cache_key(self, request, view):
            #如果是管理员
            if request.user=='ctz':
                return self.get_ident(request)
            #不是管理员
            return  None
    
    class IndexView(APIView):
        '''
        要求,所有用户都能访问,匿名用户5/m,普通用户10/m,管理员不限
        '''
        authentication_classes = [MyAuthentication,]
        permission_classes = []
        throttle_classes = [AnnoThrottle,UserThrottle,AdminThrottle]
        def get(self,request):
            return Response('首页')
    
        def throttled(self, request, wait):
            class UserInnerThrottled(exceptions.Throttled):
                default_detail = '请求被限制.'
                extra_detail_singular = 'Expected available in {wait} second.'
                extra_detail_plural = '还需要再等待{wait}秒'
            raise UserInnerThrottled(wait)
    
    
    
    class UserView(APIView):
        '''
        要求:登录用户能访问,普通用户10/m,管理员20/m
        '''
        authentication_classes = [MyAuthentication,]
        permission_classes = [UserPermission,]
        throttle_classes = [UserThrottle,AdminThrottle]
        def get(self,request):
            return Response('用户界面')
    
        def permission_denied(self, request, message=None):
            """
            If request is not permitted, determine what kind of exception to raise.
            """
    
            if request.authenticators and not request.successful_authenticator:
                raise exceptions.NotAuthenticated('无权访问')
            raise exceptions.PermissionDenied(detail=message)
    
    
        def throttled(self, request, wait):
            class UserInnerThrottled(exceptions.Throttled):
                default_detail = '请求被限制.'
                extra_detail_singular = 'Expected available in {wait} second.'
                extra_detail_plural = '还需要再等待{wait}秒'
            raise UserInnerThrottled(wait)
    
    class ManageView(APIView):
        '''
        要求:只有管理园能访问,5/m
        '''
        authentication_classes = [MyAuthentication,]
        permission_classes = [AdminPermission,]
        throttle_classes = [AdminThrottle]
        def get(self,request):
            return Response('管理员界面')
    
        def permission_denied(self, request, message=None):
            """
            If request is not permitted, determine what kind of exception to raise.
            """
    
            if request.authenticators and not request.successful_authenticator:
                raise exceptions.NotAuthenticated('无权访问')
            raise exceptions.PermissionDenied(detail=message)
    
        def throttled(self, request, wait):
            class UserInnerThrottled(exceptions.Throttled):
                default_detail = '请求被限制.'
                extra_detail_singular = 'Expected available in {wait} second.'
                extra_detail_plural = '还需要再等待{wait}秒'
            raise UserInnerThrottled(wait)
    Views
    from django.db import models
    
    # Create your models here.
    class Userinfo(models.Model):
        name=models.CharField(max_length=32,verbose_name='用户名')
        pwd=models.CharField(max_length=32,verbose_name='密码')
        token=models.CharField(max_length=64,null=True)
    
        def __str__(self):
            return self.name
    models
    REST_FRAMEWORK = {
        'UNAUTHENTICATED_USER': None,
        'UNAUTHENTICATED_TOKEN': None,
        "DEFAULT_AUTHENTICATION_CLASSES": [
          # "app01.utils.MyAuthentication",
            "app02.utils.MyAuthentication",
        ],
        "DEFAULT_PERMISSION_CLASSES":[
           "app02.utils.MyPermission",
           "app02.utils.AdminPermission",
        ],
        "DEFAULT_THROTTLE_RATES":{
           'tiga':'10/m',
           'anno':'5/m',
           'user':'10/m',
           'admin':'20/m',
        }
    }
    settings
  • 相关阅读:
    Codeforces 992C(数学)
    Codeforces 990C (思维)
    Codeforces 989C (构造)
    POJ 1511 Invitation Cards(链式前向星,dij,反向建边)
    Codeforces 1335E2 Three Blocks Palindrome (hard version)(暴力)
    POJ 3273 Monthly Expense(二分)
    POJ 2566 Bound Found(尺取前缀和)
    POJ 1321 棋盘问题(dfs)
    HDU 1506 Largest Rectangle in a Histogram(单调栈)
    POJ 2823 Sliding Window(单调队列)
  • 原文地址:https://www.cnblogs.com/ctztake/p/8423815.html
Copyright © 2011-2022 走看看