在上篇我们对Django原生View源码进行了局部解析:https://www.cnblogs.com/dongxixi/p/11130976.html

在前后端分离项目中前面我们也提到了各种认证需要自己来做,那么我们用rest_framework的时候

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

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

我们来看APIView的as_view如何实现的:

通过上篇对View源码的分析我们可以得知,在View的的闭包函数view中调用了dispatch方法,那么我们在找dispatch的时候还是要从self开始找,

此时的self是我们在视图层定义的视图类的对象,视图类并没有定义dispatch,那么就找父类APIView,在APIView中我们找到了dispatch

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

我们继续深入initialize_request()去看看它是怎么封装原生request的:

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

这里我们可以得知,它将原生的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 供判断

至此,频率控制部分的源码我们也学习完了,当我们回过头去看用法的时候会有种豁然开朗的感觉!

看源码小技巧:

1、只看自己看得懂的

2、属性查找一定要缕清继承关系,明确当前的self是谁的对象,然后从它开始查找

3、**kwargs,*args相关一般不看

Django框架rest_framework中APIView的as_view()源码解析、认证、权限、频率控制的更多相关文章

  1. Django框架 之 admin管理工具(源码解析)

    浏览目录 单例模式 admin执行流程 admin源码解析 单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在 ...

  2. 2.CBV和类视图as_view源码解析

    一.FBV和CBV # 视图基于函数开发 FBV: function.base.views # 视图基于类开发 CBV: class .base .views #Python是一个面向对象的编程语言, ...

  3. Laravel框架下路由的使用(源码解析)

    本篇文章给大家带来的内容是关于Laravel框架下路由的使用(源码解析),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 前言 我的解析文章并非深层次多领域的解析攻略.但是参考着开发文 ...

  4. Django中CBV View的as_view()源码解析

    CBV与FBV路由区别 urlpatterns = [ url(r'^publish/$', views.Publishs.as_view()), # CBV写法 url(r'^publish/$', ...

  5. Django框架(十八)—— CBV源码分析、restful规范、restframework框架

    目录 CBV源码分析.restful规范.restframework框架 一.CBV源码分析 1.url层的使用CBV 2.as_view方法 3.view方法 4.dispatch方法(可以在视图层 ...

  6. 6.2 dubbo在spring中自定义xml标签源码解析

    在6.1 如何在spring中自定义xml标签中我们看到了在spring中自定义xml标签的方式.dubbo也是这样来实现的. 一 META_INF/dubbo.xsd 比较长,只列出<dubb ...

  7. 源码解析Django CBV的本质

    Django CBV模式的源码解析 通常来说,http请求的本质就是基于Socket Django的视图函数,可以基于FBV模式,也可以基于CBV模式. 基于FBV的模式就是在Django的路由映射表 ...

  8. Django的View(视图)-settings源码的解析-模板层-模板语法

    FBV与CBV 视图函数并不只是指函数,也可以是类 FBV:基于函数的视图,类似面向函数式编程 CBV:基于类的视图,类似面向对象编程 研究解析render源码: render:返回html页面:并且 ...

  9. [源码解析] 并行分布式框架 Celery 之架构 (1)

    [源码解析] 并行分布式框架 Celery 之架构 (1) 目录 [源码解析] 并行分布式框架 Celery 之架构 (1) 0x00 摘要 0x01 Celery 简介 1.1 什么是 Celery ...

随机推荐

  1. 參数传递(引用,指针,值传递)C++11

    C++中,函数的參数传递方式有值传递.地址传递.传地址有指针和引用方式. 在函数參数中,传地址的理由有: 1.使被调函数能够改动主调函数中的数据对象: 2.传地址能够降低数据拷贝,提高程序运行速度. ...

  2. 新版【CefSharp】 禁用右键菜单 43.00+

    原文:新版[CefSharp] 禁用右键菜单 43.00+ 禁用右键菜单其实是很容易的.主就要是实现一个接口 IMenuHandler ,这个接口有一个  OnBeforeContextMenu 的方 ...

  3. CountDownLatch和CyclicBarrier 专题

    4.Runnable接口和Callable接口的区别 有点深的问题了,也看出一个Java程序员学习知识的广度. Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行ru ...

  4. EF 两种删除方式的比较

    UserInfo user = from u in context.UserInfo where u.Id=343 select u; context.UserInfo.Remove(user); 用 ...

  5. 静态资源(StaticResource)和动态资源(DynamicResource)

    一.文章概述 本演示介绍了WPF的静态资源和动态资源的基本使用,并对两者做了简单的比较. 二.定义并使用资源 <Window x:Class="Demo010.MainWindow&q ...

  6. Java Socket 爬虫

    # 地址 https://github.com/mofadeyunduo/crawler # 前言 1.代码不断优化更新. 2.有建议请留言. # 介绍 1.多线程,基于 ExcutorServcie ...

  7. WPF分辨率适应

    double x = SystemParameters.WorkArea.Width;//得到屏幕工作区域宽度 double y = SystemParameters.WorkArea.Height; ...

  8. qmake 时复制文件(自动在编译前做一些操作,且写在.pro文件里)

    有时在编译前需要准备一些文件,例如修改了 QtCreator 的编译输出目录: Build & Run > Default build directory,使用 Promote 后需要在 ...

  9. Win10《芒果TV - Preview》更新v3.1.31.0,全新播放页蜕变,预加载提速技术

    Win10<芒果TV - Preview>(商店内测版) v3.1.31.0 于2016年11月21日星期一晚上九点半登陆商店 主要是全面升级改造桌面播放页,新增观看互动评论.猜你喜欢功能 ...

  10. css3 pointer-events 让对象如透明般直接响应下层对象的鼠标事件

    引用:http://www.css88.com/book/css/properties/user-interface/pointer-events.htm 语法: pointer-events:aut ...