rest_framework的APIView源码解析

APIView概述

  这个类继承了View类可以继承他来实现CBV模式的路由映射。他自身有增加了很多方法。这篇博客主要讲解APIView中的认证功能,权限组件,频率组件,as_view,dispatch方法

 

 

APIView的as_view方法

 当我们在url(r'/', xxx.as_view())时 as_view方法做了什么?

在url.py中我们只要对一个我们继承了APIView的类使用as_view方法就可以实现和FBV一样的功能,那么as_view方法究竟做了什么呢?

    def as_view(cls, **initkwargs):
        """
        Store the original class on the view function.

        This allows us to discover information about the view when we do URL
        reverse lookups.  Used for breadcrumb generation.
        """
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            def force_evaluation():
                raise RuntimeError(
                    'Do not evaluate the `.queryset` attribute directly, '
                    'as the result will be cached and reused between requests. '
                    'Use `.all()` or call `.get_queryset()` instead.'
                )
            cls.queryset._fetch_all = force_evaluation

        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)
APIView.as_view源码

在as_view函数中返回的是一个不同通过csrf_token验证的view,而这个view是执行View类中的as_view方法得到的,我们再去看看View类中的as_view方法做了什么事。

@classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        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. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # 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=())
        return view
View.as_view源码

在View中as_view中定义了一个内部函数view,转而直接把view这个函数名返回了,这不是和我们在views.py中定义一个函数然后把函数名写在url()中本质上不就是一样的吗。所以现在我们知道了每次访问进来,就执行内部函数view方法。

 

 当请求进来时,view方法执行又做了什么?

当请求进来时执行view方法

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs
view方法

view中执行了dispatch方法,因为在View中和APIView中都定义了dispatch方法,而APIView是继承的View所以APIView的dispatch方法覆盖了View的dispatch方法。执行的是APIView的方法

    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 = 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
APIView中的dispatch

 

http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

 

每次请求进来用请求的方式去我自定义的类表中的方法匹配是否在我的列表中,如果在就用反射在找出和请求方法相同名称的函数然后执行并返回他。

在这个方法中我们还需要关注这么几个点,可以看到request被再一次封装了,然后执行了对request执行了initial方法。后面讲组件的时候会再次提到他。

 

写着写着突然发现认证组件, 

认证组件

在对request进行initial方法时,执行了如下三个方法

  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)

 

perform_authentication中 request.user我们可以猜想request.user是一个类方法,request被initialize_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
        )
initial_request

其中是由Request分装了,在Resquest中我们看看是否有这个函数

@property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
request.user
    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()
_authenticate

这里有一个重点我们必须知道代码中self.authenticators是什么为什么可以循环他。我们在看一下initial_request中的代码authenticators 是由self.get_authenticators()得到的。

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]

 接着我们可以找到authentication_classes是在APIView中api_settings中赋值的是一个默认的设置值,APISettings重写了__getattr__方法,取属性是先去找我们的settings中有没有如果没有就是用默认的认证系统。因为是在APIView中定义的那么我们在最外层的子类可以直接定义我们所需要的权限类。

好了现在我们知道self.authenticators是什么了,是一个个认证类的示例,而且看request.user我们可以发现这个认证类中一定要定义个authenticate方法。可以看到authenticate应该返回的是一个user和auth信息返回的是一个元祖。

 

权限组件和频率组件的源码和认证组件的源码如出一辙就不细细分析了。

如果三步验证都成功那么会正常返回值,不然就会终端请求抛错。

posted @ 2018-04-11 18:55  沈俊杰  阅读(123)  评论(0)    收藏  举报