Django框架rest_framework中APIView的as_view()源码解析、认证、权限、频率控制

在先后端分离项目中前面咱们也提到了各类认证须要本身来作,那么咱们用rest_framework的时候前端

rest_framework也为咱们提供相应的接口,rest_framework中的APIView实现了和Django原生View  as_view()同样的功能json

并在此基础上实现了原生request的封装、认证、权限、视图等等功能后端

 

 

咱们来看APIView的as_view如何实现的:api

 

 

 经过上篇对View源码的分析咱们能够得知,在View的的闭包函数view中调用了dispatch方法,那么咱们在找dispatch的时候仍是要从self开始找,缓存

此时的self是咱们在视图层定义的视图类的对象,视图类并无定义dispatch,那么就找父类APIView,在APIView中咱们找到了dispatch闭包

 

那么意味着APIView对dispatch进行了重写,咱们来看看APIView怎么封装的dispatch方法:app

 

 咱们继续深刻initialize_request()去看看它是怎么封装原生request的:前后端分离

 

 到这里,咱们能够知道,它将原生的request和认证等组件给到Request类实例化返回,咱们仍然须要进一步去看Request的源码:ide

 

 这里咱们能够得知,它将原生的request封装到了新的request对象的_request属性中,那么你就会想了,那原生request 的数据和方法都到哪里去了呢?函数

别着急,它也给你作了封装,继续看:

GET请求的数据:

 

 它将原生GET请求的数据放到了query_parms里面,咱们在视图类中经过request.query_parms就能够取到

 

POST请求数据:

 

 

这里的data不只仅是POST的数据,全部请求方式的键值对数据的都会被放入data里面,支持urlencoded、form-data、json(application/json)

 

 

 

FILES数据:

 

对USER的封装:

 

 此外还封装了auth等其余功能,这些功能不只在新的request里面有,它也一样存在原生的request里,在该方法的解释上,它讲明了它支持Django底层contrib,并将user设置在了Django原生的HttpRequest实例中,以保证在任何Django原生中间件均可以经过校验,因此咱们在使用APIView时能够几乎不用考虑兼容性问题

 

 好了,咱们刚刚看完了initialize_request()源码,了解了APIView对原生request进行如何进行封装以后

接下来咱们来看initial是如何帮咱们作认证、权限等校验的

 

 

认证校验

 首先咱们来看认证校验

按住Ctrl点进去以后发现它就一行代码

request.user

那此时咱们继续找Request类中的user,

 

 发现它执行了_authenticate(),而后因为好奇心,咱继续往下点:

 

 经过上图的分析咱们得知,self.authenticators 这个里面装着一个个的认证类对象,那么这些认证类对象是哪里来的呢?咱们继续探究:

 咱们回到Request封装原生request实例化的地方看传入的是什么东西

 

咱们继续找 get_authenticators()方法!

 

 发现是从self.authentication_classes中拿到的,果真返回的是一个装有对象的列表

咱们继续找authentication_classes

咱们在视图类中没有定义authentication_classes属性,那么继续往上找发现:

 

 这时候看到它是从api_settings里找的,咱们或多或少应该明白了,它会从用户配置中去找这个属性,可是咱们并无早配置文件中配置这个属性,也就意味self.get_authenticators()拿到的是空列表,刚才全部的流程都没有走,意味着APIView没有任何的认证措施,那么这些认证措施就须要咱们本身来作了。。。。

 

如何自定义认证类?

怎么作呢?缺什么补什么,在找authentication_classes的时候它会先去咱们的视图类中找,那么咱们就在视图类中配置这么个属性,刚才也推导过了,它是个列表或者元祖,那么咱们就用列表好了,进一步反推,它会for循环这个列表并拿出一个个类.authenticate(),那么咱们就先写一个类并实现authenticate方法,将类名放到该列表中,刚才咱们推理得出,authenticate方法能够没有返回值,也能够返回两个值,一个是user对象,一个是auth对象,若是有返回值,它将user对象赋值给了request.user,这时候咱们明白了,它的内部是在认证完了后将用户对象塞给了request,此时的request就拥有了user这个全局属性

我顺便去搂了一眼官方文档,咱们须要本身定义认证类而且继承BaseAuthentication,那么接下来咱们认证的写代码:

 

 那么至此,咱们在用APIView的时候自定义认证就作完了,authenticate中的认证逻辑是能够根据自身公司和项目须要去作的,这里是给出最简单的示范。

固然,若是刚才你的思路一直走过来你会发现,咱们除了在视图类中配置authentication_classes之外还能够在配置文件中配置,配置方法以下:

REST_FRAMEWORK={
                "DEFAULT_AUTHENTICATION_CLASSES":["app01.views.MyAuth",]
            }

若是这样配置了的话,在全局的视图类都会生效,显然对于网站注册登录主页进行校验用户认证是不合理的,那咱们须要怎么作呢

因为源码中查找authentication_classes的顺序是先从视图类找,视图类没有而后找配置文件,那么咱们能够在不须要校验的视图类中定义

authentication_classes = []

在相应视图类中将它配置为空列表便可。

下面咱们来总结下用法:

1、定义认证类(项目中通常在应用里单开py文件存放)
2、在认证类中实现authenticate方法,实现具体的认证逻辑
3、认证经过返回user_obj和认证对象,这样request就拥有了全局的user,认证不给前端抛异常

4、配置
  -在视图类中配置
  -在全局配置,局部禁用

刚才说了APIView除了有认证,还有权限和频率控制

 

权限校验

权限校验的源码和认证几乎如出一辙的思路,用法如出一辙

也是须要自定义权限类,实现has_perission()方法,代码以下:

from rest_framework.permissions import BasePermission   # 导入BasePermission

class MyPermision(BasePermission):      # 继承BasePermission
    message = '不是超级用户,查看不了'     # 自定义返回给前端的访问错误信息
    def has_permission(self,request,view):
        if request.user.user_type==1:
            return True
        else:
            return False


class Book(APIView):                     
    permission_classes = [MyPermision,]    # 视图类配置
    def get(self,request):
        pass

也能够全局配置,局部禁用:

在配置文件REST_FRAMEWORK中加上配置

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['app01.views.MyAuth',],
    "DEFAULT_PERMISSION_CLASSES" : ['app01.views.MyPermission',]  ## ---加上这一行便可
}

局部禁用方式同样是在视图类中配置 

permission_classes = []

 

频率控制

-使用:
        -第一步,写一个频率类,继承SimpleRateThrottle
            from rest_framework.throttling import SimpleRateThrottle
            #重写get_cache_key,返回self.get_ident(request)
            #必定要记住配置一个scop=字符串
            class MyThrottle(SimpleRateThrottle):
                scope = 'lxx'      
                def get_cache_key(self, request, view):
                    return self.get_ident(request)    # 这里是频率控制的条件,是以访问IP来控制仍是以用户ID控制均可以,暴露的配置接口,
                                 #这里调用的父类get_ident
f方法
        -第二步:在setting中配置
                REST_FRAMEWORK = {
                   'DEFAULT_THROTTLE_RATES':{
                        'lxx':'3/m'            # 这里的lxx 即在自定义频率类中定义的scope
                   }
                }
        -局部使用
            -在视图类中配置:
                throttle_classes=[MyThrottle,]
        -全局使用
            -在setting中配置    
                'DEFAULT_THROTTLE_CLASSES':['本身定义的频率类'],
        -局部禁用
            throttle_classes=[]        

咱们来看源码:

仍是从initial()这里进

 

点进去看

 

 这段是频率校验的核心代码,self.get_throttles()走的是和上面认证、权限同样的路子

也是去自定义频率类、而后配置文件里找相应频率控制类而且直接加()实例化了

 

 如今每次for循环出来的 throttle 都是一个实例化的对象,

if not throttle.allow_request(request, self):

allow_request()从字面意思上咱们也能判断出它应该是校验是否对本次访问放行的,那么它的返回值应该就是True或者False

那么这句判断语句的意思就是:若是频率校验不经过,那么就走if块内部代码

self.throttled(request, throttle.wait())

这句代码的意思就是针对  throttle.wait()获得的不一样限制结果抛出不一样异常给前端用户

点进去看源代码人家也是这么干的

 def throttled(self, request, wait):
        """
        If request is throttled, determine what kind of exception to raise.
        """
        raise exceptions.Throttled(wait)

那么频率校验不经过的状况咱们知道了,咱们就要关注它内部是如何实现对频率的校验的

进到allow_request里面去看,因为咱们自定义的频率校验类没有定义该方法,那么就向它的父类找

 

 

咱们来看源码人家是怎么作的

# settings配置
"""
 REST_FRAMEWORK = {
                
                     'DEFAULT_THROTTLE_RATES':{
                        'lxx':'3/m'     # 频率配置   每分钟3次
                     }
                 }   
""" 

#如下源码+我的注释

def allow_request(self, request, view):
        if self.rate is None:   # self.rate 是 get_rate() 经过咱们声明的scope = 'lxx' 去拿到的配置文件中频率配置中频率值 '3/m'  ,
                   # 须要咱们在频率控制类中声明scope = 'lxx',也就是配置中字典的键,
                   # 键是经过反射scope拿到的,详见 get_rate()源码
            return True

        self.key = self.get_cache_key(request, view)   # self是自定义频率类的对象,那么get_cache_key将优先从自定义频率类找
                                                       # 获得的是频率控制的依据,是用户IP\ID或者其余信息,由重写get_cache_key肯定
        if self.key is None:
            return True

        self.history = self.cache.get(self.key, [])  # 从缓存中拿到当前用户的访问记录
        self.now = self.timer()   # 获取当前时间戳
        
     #self.now 是当前执行时间,self.duration是访问频率分母,以上述例子为例 这里就是60
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()  # 将访问列表超时的去掉   history =[第三次访问时间,第二次访问时间,第一次访问时间]

        # self.num_requests  访问频率分子,设置 '3/m' 表示每分钟3次,以上例子为例,这里就是 3
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()   # 返回访问失败信息
        return self.throttle_success()   # 访问成功

以上的self.duration 和self.num_requests在访问时自定义频率控制类实例化的时候,已经经过父类的__init__产生,源码以下:

这里又进一步调用了self.parse_rate()方法,传入了自定义频率类中的scope属性值 :'3/m', 那么这里咱们大概就能猜到parse_rate()

干了件什么事儿,对!那就是拆分这个字符串,并返回  限制次数,限制时间长度,好比  3次,60秒

 一块儿来看parse_rate()源码:

 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)

        # 对 '3/m' 进行切分  num_requests=3,duration = 60
        num, period = rate.split('/')
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]  # period[0]取到切分后字符串 'm' 的第一个元素
        return (num_requests, duration)   # (3, 60)

以上allow_request()若是校验不经过,那么直接调用 throttle.failure()直接返回false

若是校验经过,它应该还须要将本次访问添加到访问记录中并保存了,那么咱们猜 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   # 返回访问成功 True 供判断
相关文章
相关标签/搜索