断点测试-认证权限频率源码分析-基于APIView编写分页-异常处理
目录
断点测试-认证权限频率源码分析-基于APIView编写分页-异常处理
昨日内容回顾
# 1 认知的使用
有些接口需要登录后才能访问
原生的django如何使用认证:auth的user表 auth自带了认证
自己登录 使用自定义的用户表
认证类的使用步骤
1 写一个类 继承BaseAuthentication
2 重写authenticate 在方法中完成认证 并且获取到当前登录用户 返回
咱们代码从地址栏中取
request.query_params.get('token')
原生django 取出前端传入cookie 从哪取?
request.COOKIE.get('sessionid')
后期如果想从请求头中取
request.META.get('HTTP_TOKEN')
如果认证通过 返回两个值 第一个是当前登录用户 第二个是token 或者返回None
# 2 权限的使用
写的权限类不一样
重写的方法不一样:has_permission 逻辑可能比较复杂
ACL:访问控制列表
rbac:公司内部系统 基于角色的访问控制
abac:rbac升级版 加了属性认证
return True或False
中文提示:self.message
get_字段名_display()
# 3 频率
写类继承SimpleRateThrottle
重写get_cache_key 返回什么 就以什么做频率限制
类属性:scope='名字'
配合配置文件中:DEFAULT_THROTTLE_RATES
局部和全局使用
# 4 过滤(只有查询所有才有过滤)
内置的:模糊匹配
第三方:精确匹配
自定义过滤类
filter_backends
# 5 排序
内置排序即可
# 6 分页
三种分页方式:重写三个类
但是一个接口只能有一种分页类
原生django的cookie+session认证底层原理
今日内容概要
-
0 断点调试使用
-
1 认证 权限 频率源码分析(了解)
- 1.1 权限类的执行源码
- 1.2 认证源码分析
- 1.3 频率源码分析
- 1.4 自定义频率类(了解)
- 1.5 SimpleRateThrottle
-
2 基于APIView编写分页
-
3 异常处理
今日内容详细
0 断点调试的使用
# 程序的 debug模式运行 可以在任意位置停下 查看当前情况下变量数据的变化情况
# pycharm 来调试程序
以debug形式运行
在左侧空白处 点击加入断点(红圈)
step over 单步调试
step into 进入到函数内部运行
快速跳到下一个断点 绿色箭头
1 认证 权限 频率 源码分析(了解)
1.1 权限类的执行源码
# 权限的源码执行流程
写一个权限类 局部使用 配置在视图类中 就会执行权限类的has_permission方法 完成权限校验
# 之前读过:drf的apiview 在执行视图类的方法之前 执行了3大认证---> display方法中的
-497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
# APIView类的399行左右:
def initial(self, request, *args, **kwargs):
# 能够解析的编码 版本控制
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 认证组件的执行位置
self.perform_authentication(request)
# 权限组件【读这个】
self.check_permissions(request)
# 频率组件
self.check_throttles(request)
# APIView的326 左右
def check_permissions(self, request):
# self.get_permissions()---> [CommonPermission(),]
# permission 是我们配置在视图类上权限类的对象
for permission in self.get_permissions():
# 权限类的对象 执行has_permission 这就是为什么我们写的权限类要重写has_permission方法
# self 是视图类的对象 就是咱们权限类的has_permission的view参数
if not permission.has_permission(request, self):
# 如果return的是False 就会走这里 走这里是 没有权限
# 如果配了多个权限类 第一个没通过 直接不会再执行下一个权限类了
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
# APIView的274行左右 get_permissions
def get_permissions(self):
# self.permission_classes 是咱们配置在视图类上的列表 里面是一个个的权限类 没加括号
# permission_classes = [CommonPermission]
# [CommonPermission,] 本质返回了权限类的对象 放到列表中
return [permission() for permission in self.permission_classes]
# 总结:
APIView---> dispatch---> initial---> 倒数第二行self.check_permissions(request)
里面取出来配置在视图类上的权限类 实例化得到对象 一个个执行对象的has_permission方法 如果返回False 就直接结束 不再继续往下执行 权限就认证通过
如果视图类上不配置权限类:permission_classes = [CommonPermission] 会使用配置文件的api_settings.DEFAULT_PERMISSION_CLASSES
优先使用项目配置文件 其次使用drf内置配置文件
3.2 认证源码分析
# 之前读过:drf的apiview 在执行视图类的方法之前 执行了3大认证---> display方法中的
-497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
# APIView类的399行左右
def initial(self, request, *args, **kwargs):
# 能够解析的编码 版本控制
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 认证组件的执行位置【读这个】
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件
self.check_throttles(request)
# APIView的316行左右
def perform_authentication(self, request):
request.user # 咱们觉得它是个属性 其实它是个方法 包装成了数据属性
# Request类的user方法 219行左右
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
# self 是Request的对象 找Request类的self_authenticate() 373行
def _authenticate(self):
# self.authenticators 我们配置在视图类上认证类的一个个对象 放到列表中
# Request类初始化的时候 传入的
for authenticator in self.authenticators:
try:
# 返回了两个值 第一个是当前登录用户 第二个的token 只走这一个认证类 后面的不再走了
# 可以返回None 会继续执行下一个认证类
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
# 解压赋值:
# self.user=当前登录用户 self是当次请求的新的Request的对象
# self.auth=token
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
# self.authenticators 去Request类的init中找 152行左右
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
.....
self.authenticators = authenticators or ()
.....
# 什么时候调用Request的__init__? ---> APIView的dispatch上面的492行的:request = self.initialize_request(request, *args, **kwargs) ---> 385行 --->
def initialize_request(self, request, *args, **kwargs):
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
# 总结:
1 配置在视图类上的认证类 会在执行视图类方法之前执行 在权限认证之前执行
2 自己写的认证类 可以返回两个值或None
3 后续可以从request.user 取出当前登录用户(前提是你要在认证类中返回)
1.3 频率源码分析
# 之前读过:drf的apiview,在执行视图类的方法之前,执行了3大认证----》dispatch方法中的
-497行左右, self.initial(request, *args, **kwargs)---》执行3大认证
# APIView类的399行左右:
def initial(self, request, *args, **kwargs):
# 能够解析的编码,版本控制。。。。
self.format_kwarg = self.get_format_suffix(**kwargs)
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# 认证组件的执行位置
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件【读这个】
self.check_throttles(request)
# APIView的 352行
def check_throttles(self, request):
throttle_durations = []
# self.get_throttles() 配置在视图类上的频率类的对象 放到列表中
# 每次取出一个频率类的对象 执行allow_request方法 如果是False 频率超了 不能再走了
# 如果是True 没有超频率 可以直接往后
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
if throttle_durations:
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
self.throttled(request, duration)
# 总结:
我们写的频率类:继承BaseThrottle 重写allow_request 在内部判断 如果超频了 就返回False 如果没超频 就返回True
1.4 自定义频率类(了解)
class SuperThrottle(BaseThrottle):
VISIT_RECORD = {}
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 自己写逻辑 判断是否超频
# 1 取出访问者ip
ip = request.META.get('REMOTE_ADDR')
import time
ctime = time.time()
# 2 判断当前ip不在访问字典里 添加进去 并且直接返回True 表示第一次访问
if ip not in self.VISIT_RECORD:
self.VISIT_RECORD[ip] = [ctime, ]
return True
# self.history = [时间1]
self.history = self.VISIT_RECORD.get(ip, [])
# 3 循环判断当前ip的列表 有值 并且当前时间减去列表的最后一个时间大于60s 把这种数据pop掉 这样列表中只有60s以内的访问时间
while self.history and ctime - self.history[-1] > 60:
self.history.pop()
# 4 判断 当列表小于3 说明一分钟内访问不足三次 把当前时间插入到列表第一个位置 返回True 顺利通过
# 5 当大于等于3 说明一分钟内访问超过三次 返回False验证失败
if len(self.history) < 5:
self.history.insert(0, ctime)
return True
else:
return False
def wait(self):
import time
ctime = time.time()
return 60 - (ctime - self.history[-1])
1.5 SimpleRateThrottle
# 写一个频率类 重写allow_request方法 在里面实现频率控制
# SimpleRateThrottle---> allow_request
def allow_request(self, request, view):
# 这里就是通过配置文件和scop取出 频率限制是多少 比如一分钟访问5次
if self.rate is None:
return True
# 返回了ip 就以ip
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()
# SimpleRateThrottle的init方法
def __init__(self):
if not getattr(self, 'rate', None):
# self.rate = '5/h'
self.rate = self.get_rate()
# 5 36000
self.num_requests, self.duration = self.parse_rate(self.rate)
# SimpleRateThrottle的get_rate() 方法
def get_rate(self):
if not getattr(self, 'scope', None):
msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
self.__class__.__name__)
raise ImproperlyConfigured(msg)
try:
# self.scope 是lqz 字符串
# return '5/h'
return self.THROTTLE_RATES[self.scope]
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
# SimpleRateThrottle的parse_rate 方法
def parse_rate(self, rate):
# '5/h'
if rate is None:
return (None, None)
# num = 5
# period = 'hour'
num, period = rate.split('/')
# num_requests = 5
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
# (5, 36000)
return (num_requests, duration)
2 基于APIView编写分页
# 分页功能,只有查询所有才有
class BookView(ViewSetMixin, APIView):
def list(self, request):
books = Book.objects.all()
# 使用步骤
# 1 实例化得到一个分页类的对象
paginator = CommonLimitOffsetPagination()
# 2 调用分页类对象的paginate_queryset方法来完成分页,返回的page是 要序列化的数据,分页好的
page = paginator.paginate_queryset(books, request, self)
if page is not None:
serializer = BookSerializer(instance=page, many=True)
# 3 返回数据,调用paginator的get_paginated_response方法
# return paginator.get_paginated_response(serializer.data)
return Response({
'total': paginator.count,
'next': paginator.get_next_link(),
'previous': paginator.get_previous_link(),
'results': serializer.data
})
3 异常处理
# APIView---> dispatch---> 三大认证 视图类的方法 如果出了异常 会被异常捕获 捕获后统一处理
# drf内置了一个函数 只要上面过程出了异常 就会执行这个函数 这个函数只处理drf的异常
主动抛的非drf异常 程序出错了 我们都不会处理 我们的目标 无论主动抛还是程序运行出错 都统一返回规定格式---> 都记录在日志
公司里一般返回 {'code': 999, 'msg': '系统错误 请联系系统管理员'}
# 写一个函数 内部处理异常 在配置文件中配置一下即可
from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc, context):
# exc错误对象
# content:上下文 有view:当前出错的视图类的对象 args和kwargs视图类方法分组出来的参数;request:当次请求的request对象
# 只要走到这里就要记录日志 只有错了 才会执行这个函数
# 记录日志尽量详细
print('时间,登录用户id,用户ip,请求方式,请求地址,执行的视图类,错误原因')
res = exception_handler(exc, context)
if res: # 有值 说明返回了Response对象 没有值说明返回None
# 如果是Response对象 说明是drf的异常 已经处理了 如果是None 表明没有处理 就是非drf的异常
res = Response(data={'code': 1107, 'msg': res.data.get('detail', '请联系系统管理员')})
else:
# res = Response(data={'code': 999, 'msg': str(exc)})
# 记录日志
res = Response(data={'code': 999, 'msg': '系统错误,请联系系统管理员'})
return res
# 在配置文件中配置
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.three_auth.exceptions.common_exception_handler',
}
# 补充
'''
# 异常出现的情况:1 主动抛异常,2 程序出错了
# 程序出错了,异常不是drf的异常
# l = [1, 2, 3]
# print(l[9])
# 5/0
# 主动抛drf的异常
# raise APIException('我是drf异常 你给我停下')
# 主动抛非drf异常
# raise Exception("我是系统异常 请联系管理员")
'''
# 抛异常顺序,视图内配置---> 全局设置---> 内置