在上篇我们对 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 相关一般不看
来源: https://www.cnblogs.com/dongxixi/p/11134506.html