• 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

    技术参考博客链接

    技术参考博客链接

  • 相关阅读:
    将 expression 转换为数据类型 int 时发生算术溢出
    将博客搬至CSDN
    山东省滕州市木石镇化石沟村QQ群116528924
    未能加载文件或程序集 Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad
    sql server 怎么实现mysql中group_concat,列转行,列用分隔符拼接字符串
    sql server 条件 not in (null)总是false
    SCRIPT7002: XMLHttpRequest: 网络错误 0x2ef3, 由于出现错误 00002ef3 而导致此项操作无法完成,浏览器中的Keep-Alive
    2015年总结之什么叫软件开发?
    XSD(XML Schema Definition)用法实例介绍以及C#使用xsd文件验证XML格式
    XML组成结构以及C#通过DTD验证规范性
  • 原文地址:https://www.cnblogs.com/LearningOnline/p/9495931.html
Copyright © 2020-2023  润新知