• Restrramework源码(包含组件)分析


    1.总体流程分析

    rest_framework/view.py

    请求通过url分发,触发as_view方法,该方法在ViewSetMixin类下

    点进去查看as_view源码说明,可以看到它在正常情况下是zhix执行了self.dispatch(request, *args, **kwargs)方法

     @classonlymethod
        def as_view(cls, actions=None, **initkwargs):
            """
            Because of the way class based views create a closure around the
            instantiated view, we need to totally reimplement `.as_view`,
            and slightly modify the view function that is created and returned.
            """
            # The suffix initkwarg is reserved for displaying the viewset type.
            # eg. 'List' or 'Instance'.
            cls.suffix = None
    
            # The detail initkwarg is reserved for introspecting the viewset type.
            cls.detail = None
    
            # Setting a basename allows a view to reverse its action urls. This
            # value is provided by the router through the initkwargs.
            cls.basename = None
    
            # actions must not be empty
            if not actions:
                raise TypeError("The `actions` argument must be provided when "
                                "calling `.as_view()` on a ViewSet. For example "
                                "`.as_view({'get': 'list'})`")
    
            # sanitize keyword arguments
            for key in initkwargs:
                if key in cls.http_method_names:
                    raise TypeError("You tried to pass in the %s method name as a "
                                    "keyword argument to %s(). Don't do that."
                                    % (key, cls.__name__))
                if not hasattr(cls, key):
                    raise TypeError("%s() received an invalid keyword %r" % (
                        cls.__name__, key))
    
            def view(request, *args, **kwargs):
                self = cls(**initkwargs)
                # We also store the mapping of request methods to actions,
                # so that we can later set the action attribute.
                # eg. `self.action = 'list'` on an incoming GET request.
                self.action_map = actions
    
                # Bind methods to actions
                # This is the bit that's different to a standard view
                for method, action in actions.items():
                    handler = getattr(self, action)
                    setattr(self, method, handler)
    
                if hasattr(self, 'get') and not hasattr(self, 'head'):
                    self.head = self.get
    
                self.request = request
                self.args = args
                self.kwargs = kwargs
    
                # And continue as usual
                return self.dispatch(request, *args, **kwargs)
    
            # take name and docstring from class
            update_wrapper(view, cls, updated=())
    
            # and possible attributes set by decorators
            # like csrf_exempt from dispatch
            update_wrapper(view, cls.dispatch, assigned=())
    
            # We need to set these on the view function, so that breadcrumb
            # generation can pick out these bits of information from a
            # resolved URL.
            view.cls = cls
            view.initkwargs = initkwargs
            view.suffix = initkwargs.get('suffix', None)
            view.actions = actions
            return csrf_exempt(view)
    View Code

     通过查找可以看到dispatch方法在这个class APIView(View)类里

    class APIView(View):
    
        # The following policies may be set at either globally, or per-view.
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
        permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
        content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
        metadata_class = api_settings.DEFAULT_METADATA_CLASS
        versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    
        # Allow dependency injection of other settings to make testing easier.
        settings = api_settings

     从源码介绍我们可以看到,相关的组件,这里做了全局配置

     请求到dispatch后,做了封装request和认证两件事,

    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        # 对原始request进行加工,封装request
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
    
        try:
            # 认证
            self.initial(request, *args, **kwargs)
    
            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
    
            response = handler(request, *args, **kwargs)
    
        except Exception as exc:
            response = self.handle_exception(exc)
    
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
        

    self.initialize_request(request, *args, **kwargs):--->封装request

    def initialize_request(self, request, *args, **kwargs):
    """
    Returns the initial request object.
    """
    parser_context = self.get_parser_context(request)
    
    return Request(
        request,
        parsers=self.get_parsers(),
        authenticators=self.get_authenticators(),    
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context
    )
    
        

    这里它返回了初始请求对象Request,它继承了Request类

    其中的几个方法:

    这里要说明的是最终的authentication_classes它到了全局去查找

    接下来执行self.initial(request, *args, **kwargs),我们点进去,这是运行之前需要执行的方法

    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        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
    
        # Ensure that the incoming request is permitted
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)

     调用方法处理程序之前,依次执行了四个restframework给我们的组件,版本管理,用户认证,权限,节流

    这里自上而下执行,版本就没啥说了,先来看用户认证:

    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.
    
        Note that if you override this and simply 'pass', then authentication
        will instead be performed lazily, the first time either
        `request.user` or `request.auth` is accessed.
        """
        request.user

     需要说明的是,这里的request其实是封装之后的request,读者一般第一次看到这里可能会比较纳闷,request.user是啥,其实这里的user调用了property属性 

    这里的user方法说明,返回与当前请求关联的用户,由提供给请求的身份验证类进行身份验证。

    对于这里的self._authenticate():

     def _authenticate(self):
            """
            Attempt to authenticate the request using each authentication instance
            in turn.
            """
            for authenticator in self.authenticators:
                try:
                    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.auth = user_auth_tuple
                    return
    
            self._not_authenticated()

    这里循环我们的authenticator,如果有返回值则执行authenticate(self)方法,最终返回的是个元祖,其中有user,token两个值

    如果上面方法执行抛出异常,则执行self._not_authenticated()方法,设置表示未经身份验证的请求的authenticator,user和authtoken。默认为None,AnonymousUser&None。

     整个restframework声明周期在这里在说明一下:

      发送请求-->Django的wsgi-->中间件-->路由系统_执行CBV的as_view(),就是执行内部的dispath方法-->在执行dispath之前,有版本分析 和 渲染器

      在dispath内,对request封装-->版本-->认证-->权限-->限流-->视图-->如果视图用到缓存( request.data or request.query_params )就用到了 解析器-->视图处理数据,用到了序列化(对数据进行序列化或验证) -->视图返回数据可以用到分页

    2.用户登录认证

    对于rest_framework给我们提供的这个内置认证组件

    在authentication.py文件下包含的认证类:

    包含了这么多,但我们其实也是基于上面的BaseAuthentication来重写我们的用户认证,先来看看源码:

    class BaseAuthentication(object):
        """
        All authentication classes should extend BaseAuthentication.
        """
    
        def authenticate(self, request):
            """
            Authenticate the request and return a two-tuple of (user, token).
            """
            raise NotImplementedError(".authenticate() must be overridden.")
    
        def authenticate_header(self, request):
            """
            Return a string to be used as the value of the `WWW-Authenticate`
            header in a `401 Unauthenticated` response, or `None` if the
            authentication scheme should return `403 Permission Denied` responses.
            """
            pass

    说了一大堆东西,最终也只是解释性说明,还是需要我们自己来写相关的用户认证,官方只给我们提供了这么一个框架而已

    这里相关具体使用方法可以参考我的这篇博客

     具体流程:

      创建认证类,继承BaseAuthentication,重写authenticate方法和authenticate_header方法

      对于authenticate()方法的返回值:

        可以返回 raise AuthenticationFailed({'code':1000,'error':'认证失败'})   需要导入:from rest_framework.exceptions import AuthenticationFailed

         可以返回一个元祖 return (token_obj.user, token_obj)

              token_obj.user赋值给了request.user

              token_obj赋值给了request.auth

      这里还要注意它的使用,可以在全局配制,亦可以在局部配制:

          对于局部,直接在上面加上authentication_classes = [BaseAuthentication,]

          对于全局,需要在我们方setting下进行restframework全局设置

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]
    }

    3. 权限认证 

    permissions.py:

    class BasePermission(object):
        """
        A base class from which all permission classes should inherit.
        """
    
        def has_permission(self, request, view):
            """
            Return `True` if permission is granted, `False` otherwise.
            """
            return True
    
        def has_object_permission(self, request, view, obj):
            """
            Return `True` if permission is granted, `False` otherwise.
            """
            return True

      这里BasePermission类给我们提供了has_permission方法,和has_has_object_permission两个方法,都是让我们自己重写,最终返回值都是都是布尔值,以此判定是否具有权限

    我们需要编写自己的类,来继承BasePermission

    使用样式伪代码:

    class TestView(APIView):
        # 认证的动作是由request.user触发
        authentication_classes = [TestAuthentication, ]
    
        # 权限
        # 循环执行所有的权限
        permission_classes = [TestPermission, ]
    
        def get(self, request, *args, **kwargs):
            # self.dispatch
            print(request.user)
            print(request.auth)
            return Response('GET请求,响应内容')
    
        def post(self, request, *args, **kwargs):
            return Response('POST请求,响应内容')

    以上是在局部配置,全局配置只需在配置文件中写入即可

    细节详情参考博客

    4.节流

    throttling.py:

    class BaseThrottle(object):
        """
        Rate throttling of requests.
        """
    
        def allow_request(self, request, view):
            """
            Return `True` if the request should be allowed, `False` otherwise.
            """
            raise NotImplementedError('.allow_request() must be overridden')
    
        def get_ident(self, request):
            """
            Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
            if present and number of proxies is > 0. If not use all of
            HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.
            """
            xff = request.META.get('HTTP_X_FORWARDED_FOR')
            remote_addr = request.META.get('REMOTE_ADDR')
            num_proxies = api_settings.NUM_PROXIES
    
            if num_proxies is not None:
                if num_proxies == 0 or xff is None:
                    return remote_addr
                addrs = xff.split(',')
                client_addr = addrs[-min(num_proxies, len(addrs))]
                return client_addr.strip()
    
            return ''.join(xff.split()) if xff else remote_addr
    
        def wait(self):
            """
            Optionally, return a recommended number of seconds to wait before
            the next request.
            """
            return None

    可以看出这是一个父类,它只提供了返回值,这里具体就是代码的编写了,怎么才能做到时间段内的固定访问次数呢,肯定需要用到时间模块以及该用户的ip了,通过

    RECORD = {
        '用户IP': [12312139, 12312135, 12312133, ]
    }
    的形式来做判断
    代码演示:
    from rest_framework.throttling import BaseThrottle
    import time
     
    D = {}  # {'127.0.0.1': [1533302442, 1533302439,...]}
     
    class MyThrottle(BaseThrottle):
        # 点进源码直接看当中携带的参数
        def allow_request(self, request, view):
            """
            Return `True` if the request should be allowed, `False` otherwise.
            """
            # 访问当前IP
            ip = request.META.get('REMOTE_ADDR')
            print(ip)
            now = time.time()
            if ip not in D:
                D[ip] = []  # 初始化一个空的访问历史列表
     
            history = D[ip]
            # 当历史列表中有元素,并且当前时间戳减去最后一个元素的时间戳大于10
            while history and (now - history[-1]) > 10:
                history.pop()
            # 判断最近10秒内的访问次数是否超过了阈值(3次)
            if len(history) >= 3:
                return False
            else:
                # 把这一次的访问时间加到访问历史列表的第一位
                D[ip].insert(0,now)
                return True

     具体操作链接

    这里也能使用它内部封装好的子类进行操作

    class SimpleRateThrottle(BaseThrottle):
        """
        A simple cache implementation, that only requires `.get_cache_key()`
        to be overridden.
    
        The rate (requests / seconds) is set by a `rate` attribute on the View
        class.  The attribute is a string of the form 'number_of_requests/period'.
    
        Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')
    
        Previous request information used for throttling is stored in the cache.
        """
        cache = default_cache
        timer = time.time
        cache_format = 'throttle_%(scope)s_%(ident)s'
        scope = None
        THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
    
        def __init__(self):
            if not getattr(self, 'rate', None):
                self.rate = self.get_rate()
            self.num_requests, self.duration = self.parse_rate(self.rate)
    
        def get_cache_key(self, request, view):
            """
            Should return a unique cache-key which can be used for throttling.
            Must be overridden.
    
            May return `None` if the request should not be throttled.
            """
            raise NotImplementedError('.get_cache_key() must be overridden')
    
        def get_rate(self):
            """
            Determine the string representation of the allowed request rate.
            """
            if not getattr(self, 'scope', None):
                msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                       self.__class__.__name__)
                raise ImproperlyConfigured(msg)
    
            try:
                return self.THROTTLE_RATES[self.scope]
            except KeyError:
                msg = "No default throttle rate set for '%s' scope" % self.scope
                raise ImproperlyConfigured(msg)
    
        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)
            num, period = rate.split('/')
            num_requests = int(num)
            duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
            return (num_requests, duration)
    
        def allow_request(self, request, view):
            """
            Implement the check to see if the request should be throttled.
    
            On success calls `throttle_success`.
            On failure calls `throttle_failure`.
            """
            if self.rate is None:
                return True
    
            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()
    
            # Drop any requests from the history which have now passed the
            # throttle duration
            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()
    
        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
    
        def throttle_failure(self):
            """
            Called when a request to the API has failed due to throttling.
            """
            return False
    
        def wait(self):
            """
            Returns the recommended next request time in seconds.
            """
            if self.history:
                remaining_duration = self.duration - (self.now - self.history[-1])
            else:
                remaining_duration = self.duration
    
            available_requests = self.num_requests - len(self.history) + 1
            if available_requests <= 0:
                return None
    
            return remaining_duration / float(available_requests)

     只用它的好处在于省去了大量代码,只要实现逻辑层相关业务即可

    自己写的throttle.py:

    from rest_framework.throttling import SimpleRateThrottle
    
    class VisitThrottle(SimpleRateThrottle):
        '''匿名用户60s只能访问三次(根据ip)'''
        scope = 'OP'   #这里面的值,自己随便定义,settings里面根据这个值配置Rate
    
        def get_cache_key(self, request, view):
            #通过ip限制节流
            return self.get_ident(request)    # 远程IP地址
    
    class UserThrottle(SimpleRateThrottle):
        '''登录用户60s可以访问10次'''
        scope = 'OA'    #这里面的值,自己随便定义,settings里面根据这个值配置Rate
    
        def get_cache_key(self, request, view):
            return request.user.username    # 返回用户名

    setting.py下的配制

    REST_FRAMEWORK = {
        #节流
        "DEFAULT_THROTTLE_CLASSES":['API.utils.throttle.UserThrottle'],   #全局配置,登录用户节流限制(10/m)
        "DEFAULT_THROTTLE_RATES":{
            'OP':'3/m',         #没登录用户3/m,
            'OA':'10/m',    #登录用户10/m,
        }
    }

    5.版本

     这里总共有五个类供我们使用

    versioning.py

    我们一把常用的就是全局使用,其他的用法详见wusir博客

     6.分页

    这里抛开基类,提供了三个类供我们去使用,每种都具有同的效果

    pagination.py

    技术参考博客链接

    技术参考博客链接

  • 相关阅读:
    【React Native】某个页面禁用物理返回键
    【React Native】DeviceEventEmitter监听通知及带参数传值
    转载【React Native代码】手写验证码倒计时组件
    【React Native】 中设置 APP 名称、应用图标、为安卓添加启动图
    【React Native错误集】* What went wrong: Execution failed for task ':app:installDebug'.
    【React Native错误集】Import fails with "Failed to execute 'ImportScripts' on 'WorkerGlobalScope'"
    【React Native错误集】Android error “Could not get BatchedBridge, make sure your bundle is packaged properly” on start of app
    「React Native笔记」在React的 setState 中操作数组和对象的多种方法(合集)
    【React Native】Error: Attribute application@allowBackup value=(false) from AndroidManifest.xml
    坚果云如何使用二次验证码/谷歌身份验证器/两步验证/虚拟MFA?
  • 原文地址:https://www.cnblogs.com/LearningOnline/p/9495931.html
Copyright © 2020-2023  润新知