drf断点调试,认证权限频率源码分析,基于APIView编写分页,异常处理

内容回顾

认证的使用,如果要使用多个认证类,那么需要返回None,在最后一个认证类中返回:user对象,token

有些接口需要登录后才能访问
原生django使用auth的user表,auth自带认证
自己登录,使用自定义的用户表
认证类的使用步骤
	1.写一个认证类,继承BaseAuthentication
    2.重写authenticate,在方法中完成认证,认证成功返回当前登录user对象,token,或返回None
		token从地址栏中取
		request.query_params.get('token')
        原生django取前端传入的Cookie
        request.COOKIE.get('sessionid')
        后期如果想从请求头中取
		request.META.get('HTTP_TOKEN')
        如果认证通过,返回两个值,第一个是当前登录用户,第二个是token,或者返回None
    3.局部使用在视图类中配置
		authentication_classes = []
	4.项目settings.py配置
		REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
'app01.authentication.CommenAuthentication'
    ],
	5.drf内置
            

权限的使用

写的权限类不一样:继承BasePermission
重写的方法不一样:has_permission 逻辑可能比较复杂
	ACL:访问控制类表
	rbac:公司内部系统,基于角色的访问控制
    abac:rabc升级版,加了属性认证
        
return True或False
中文提示:self.message='提示信息'
get_字段名_display()  获取到字段的choice参数中的中文
如:choice=((1,'普通用户'),(2,'高级管理'),(3,'超级管理'))

频率的使用

写类继承SimpleRateThrottle
重写get_cache_key,返回什么,就以什么做频率限制
类属性:scope='名字'
配合配置文件中:'DEFAULT_THROTTLE_RATES': {
        '名字':'5/m' # 频率限制s秒,m分中,h小时,d天,只要以这个几个其中的任意一个开头后面不论写多少都能识别,例如mmmmmmmmmmmmm识别的为一分钟内访问多少次不允许访问
    },

过滤的使用(查询所有才有过滤)

内置的:模糊匹配
filter_backends = [SearchFilter]
search_fields = ['name','price']
第三方:精准匹配
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name','price']  # 精准匹配,可以一个一个匹配,也可以两个一起匹配
自定义:
filter_backends = [CommonFilter]
from rest_framework.filters import BaseFilterBackend


class CommonFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):
        price_gt = request.query_params.get('price_gt',None)
        if price_gt:
            #在里面实现过滤,返回qs对象,就是过滤后的数据
            return queryset.filter(price__gt=price_gt)
        return queryset

排序的使用

内置排序OrderingFilter排序类
filter_backends = [OrderingFilter,CommonFilter]
ordering_fields = ['price','id']

分页的使用

三种分页方式:重写三个类
但是一个接口只能有一种分页类
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination

# pc网页端经常使用
class CommonPageNumber(PageNumberPagination):
    page_size = 2  # 默认显示条数
    page_query_param = 'page'  # 分页参数名
    page_size_query_param = 'size'  # 分大小参数名一个页面显示条数
    max_page_size = 5  # 最大一个页面显示条数


class CommonLimitOffset(LimitOffsetPagination):
    default_limit = 2
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    max_limit = 5

# 游标分页效率极高,手机app经常使用
class CommonCursor(CursorPagination):
    cursor_query_param = 'cursor'
    page_size = 3
    ordering = 'id'

 

pagination_class = CommonPageNumber  # 方式一
pagination_class = CommonLimitOffset  # 方式二可以和分页排序一起使用
pagination_class = CommonCursor   # 方式三

原生django的Cookie+session认证底层原理

image-20230208162219993

今日内容

断点调试使用

程序是debug模式允许,可以在任意位置停下,查看当前情况下变量数据的变化情况

pycharm调试程序

  1. 以debug形式允许
    image-20230208162752217
  2. 在左侧空白出,点击加入断点(红圈)
    image-20230208162821285
  3. step over 单步调试,只运行单行
    image-20230208162922707
  4. step into 进入到函数内部运行,可以进入函数或类内不中一行行执行
  5. 快速调到下一个断点,绿色箭头
    image-20230208163105516

认证,权限,频率源码分析(了解)

权限类的执行源码

权限的源码执行流程

写一个权限类,局部使用,配置在视图类的,就会执行权限的has_permission方法,完成权限校验

之前读过: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)

        # 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.
        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)

读self.check_permissions(request)源码执行

# APIView的326 左右
def check_permissions(self, request):
		# self.get_permissions()----》[CommonPermission(),]
        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 是自己写的视图类对象
    # 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内置配置文件

认证源码分析

APIView中的request都是被包装过的restframework.request中的类产生的对象

# 之前读过: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)

读self.perform_authentication(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
        
@user.setter  # 用于修改类中私有成员变量
def user(self, value):
    self._user = value
    self._request.user = value

    
    
    
# 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 ()
		.....
        
# 什么时候调用Reqeust的__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取出当前登录用户(前提是你要在认证类中返回)

频率源码分析

# 之前读过: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)

读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:
            # Filter out `None` values which may happen in case of config / rate
            # changes, see #1438
            durations = [
                duration for duration in throttle_durations
                if duration is not None
            ]

            duration = max(durations, default=None)
            self.throttled(request, duration)
            

            
            
# APIView 的280行       
def get_throttles(self):
   	# 从视图类中拿出频率类产生对象放到列表中
    return [throttle() for throttle in self.throttle_classes]

总结

我们写的频率类:继承BaseThrottle,重写allow_request,在内部判断,如果超频率了,就返回false,如果没超频率,就返回True

自定义频率类(了解)

class SuperThrottle(BaseThrottle):
    VISIT_RECORD = {}

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 自己写逻辑,判断是否超频
        # (1)取出访问者ip
        # (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走 {ip地址:[时间1,时间2,时间3,时间4]}
        # (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
        # (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
        # (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
        # (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) < 3:
            self.history.insert(0, ctime)
            return True
        else:
           
            return False

    def wait(self):
        import time
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

SimpleRateThrottle源码分析

写一个频率类,重写allow_request方法,在里面实现频率控制

# 写一个频率类,重写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)

基于APIView编码分页

# 分页功能,只有查询所有才有


class BookView(ViewSetMixin, APIView):
    def list(self, request):
        books = Book.objects.all()
        # 过滤函数正常使用
        books = self.filter_queryset(books)
        # 使用步骤
        # 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
            })

分页器源码分析

#切入点为listModelmixin
# listModelmixin类中list方法 37行左右
def list(self, request, *args, **kwargs):
    # 条件不用管他过滤
    queryset = self.filter_queryset(self.get_queryset())
	
    # 这路调用分页找GenericAPIView
    page = self.paginate_queryset(queryset)
    if page is not None:
        # 调用序列化类序列化分页好的数据
        serializer = self.get_serializer(page, many=True)
        # 调用好看的返回方式,带上一页下一页链接
        return self.get_paginated_response(serializer.data)
	
    # 这里是不分页的情况,我们不考虑
    serializer = self.get_serializer(queryset, many=True)
    return Response(serializer.data)


# GenericAPIView类中 165行左右

def paginate_queryset(self, queryset):
	# 这里self.paginator是一个伪装方法在GenericAPIView类中 153行左右
    # 在伪装函数内拿到后分页器对象后调用paginate_queryset(queryset, self.request, view=self)然后去到分页器类中找例如我们写的分页器继承了PageNumberPagination就去这里面找
    if self.paginator is None:
        return None
    return self.paginator.paginate_queryset(queryset, self.request, view=self)



# GenericAPIView类中 153行左右
@property
def paginator(self):
    # 反射拿视图类中分页器类
    if not hasattr(self, '_paginator'):
        # 这里取视图中如果有分页器类则产生分页器对象返回出去
        if self.pagination_class is None:
            self._paginator = None
            else:
                self._paginator = self.pagination_class()
        return self._paginator
    
    
# PageNumberPagination类中   191行左右
# 这里面实现了分页功能返回了列表套对象的数据
def paginate_queryset(self, queryset, request, view=None):
    """
        Paginate a queryset if required, either returning a
        page object, or `None` if pagination is not configured for this view.
        """
    page_size = self.get_page_size(request)
    if not page_size:
        return None

    paginator = self.django_paginator_class(queryset, page_size)
    page_number = self.get_page_number(request, paginator)

    try:
        self.page = paginator.page(page_number)
        except InvalidPage as exc:
            msg = self.invalid_page_message.format(
                page_number=page_number, message=str(exc)
            )
            raise NotFound(msg)

            if paginator.num_pages > 1 and self.template is not None:
                # The browsable API should display pagination controls.
                self.display_page_controls = True

                self.request = request
                return list(self.page)
            
这样就拿到了分好页的数据
最后调了在list方法中
return self.get_paginated_response(serializer.data)

# 有调了GenericAPIView中的 173行左右

def get_paginated_response(self, data):
	# 断言有这个分页器对象
    assert self.paginator is not None
    # 调了分页器对象里的方法
    return self.paginator.get_paginated_response(data)

# 在PageNumberPagination类中 224行
# 在这里面就把处理好的数据返回出去了
def get_paginated_response(self, data):
    return Response(OrderedDict([
        ('count', self.page.paginator.count),
        ('next', self.get_next_link()),
        ('previous', self.get_previous_link()),
        ('results', data)
    ]))


异常处理源码分析

# APIView--->dispatch--->三大认证,视图类的方法,如果出了一场,会被一场捕获,捕获后统一处理
# drf 内置了一个函数,只要上面过程出了异常,就会执行这个函数,这个函数只处理的drf的异常
	-主动抛的非drf异常
    -程序出错了 
    都不会被处理
    我们的目标,无论主动抛还是程序运行出错,都同意返回规定格式--》能记录日志
    公司里一般返回   {code:999,'msg':'系统错误,请联系系统管理员'}
# 切入点APIView中的dispatch方法中的
#APIView类中dispatch 485行左右
如果捕捉到异常了就会执行这个函数
self.handle_exception(exc)

    def dispatch(self, request, *args, **kwargs):
        try:
			。。。。
        except Exception as exc:
            出错了会走这个函数
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response


    
    
# 找到APIView类中 448行
def handle_exception(self, exc):
    # 这里是只处理了属于NotAuthenticated,AuthenticationFailed与这个两个异常类
    if isinstance(exc, (exceptions.NotAuthenticated,
                        exceptions.AuthenticationFailed)):
        # WWW-Authenticate header for 401 responses, else coerce to 403
        auth_header = self.get_authenticate_header(self.request)

        if auth_header:
            exc.auth_header = auth_header
            else:
                exc.status_code = status.HTTP_403_FORBIDDEN
		# 去哪项目配置文件中的异常错误函数名
        exception_handler = self.get_exception_handler()

        context = self.get_exception_handler_context()
        # 执行异常函数名然后返回处理的数据
        response = exception_handler(exc, context)

        if response is None:
            self.raise_uncaught_exception(exc)

            response.exception = True
        # 最后在返回出去
        return response
    
    
# 自己写的异常处理函数
# myxception.py
# 两种一种不调用原来的了
from rest_framework.exceptions import APIException
from rest_framework.response import Response


def mytotalException(exc, context):
    # print(exc, context)
	# 这里查看是不是drf异常,如果是返回一种,不是则返回另外一种
    if isinstance(exc, APIException):
        res = {'code': 666, 'msg': '请求出错', 'error': exc.detail}

    else:
        res = {'code': 999, 'msg': '系统出错请联系管理员', 'error': str(exc)}
    # 这里直接返回给前端不需要再执行了
    return Response(res)

# 这种是调用原来的
from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.views import exception_handler


def mytotalException(exc, context):
    # print(exc, context)
    exc = exception_handler(exc, context)
    if exc:
        # 这里可能会出错
        res = {'code': 666, 'msg': '请求出错', 'error': exc.data.get('detail')}
    else:
        res = {'code': 999, 'msg': '系统出错请联系管理员', 'error': str(exc)}
    return Response(res)


# 在配置文件中配置
REST_FRAMEWORK = {
	'EXCEPTION_HANDLER':
        'app01.myexception.mytotalException',
}

image-20230208223733956

image-20230208223823213

posted @ 2023-02-08 22:40  clever-cat  阅读(36)  评论(0编辑  收藏  举报