zoukankan      html  css  js  c++  java
  • rest-framework框架——版本

    一、DRF版本控制介绍

      随着项目更新,版本会越来越多,不能新的版本出现,旧版本就不再使用维护了。因此不同的版本会有不同的处理,且接口会返回不同的信息。

      API版本控制允许我们在不同的客户端之间更改行为(同一个接口的不同版本会返回不同的数据)。

      DRF提供了许多不同的版本控制方案。可能会有一些客户端因为某些原因不再维护了,但是我们后端的接口还要不断的更新迭代,这个时候通过版本控制返回不同的内容就是一种不错的解决方案。

      rest_framework.versioning里提供了五种版本控制方案如下所示:

    from rest_framework import versioning           # view中引入版本控制
    # 查看 rest_framework/versioning.py文件:
    
    # 最基础的版本控制类,给其他版本控制类提供一些共用方法
    class BaseVersioning:...
    
    # 在accept请求头中配置版本信息
    # accept代表希望返回的数据类型,可以携带版本信息
    # Accept: application/json; version=1.0
    class AcceptHeaderVersioning(BaseVersioning):   # 将版本信息放到请求头中
        """
        GET /something/ HTTP/1.1
        Host: example.com
        Accept: application/json; version=1.0
        """
    
    # 在url上携带版本信息
    # url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
    class URLPathVersioning(BaseVersioning):        # 将版本信息放入URL中
        """
        To the client this is the same style as `NamespaceVersioning`.
        The difference is in the backend - this implementation uses
        Django's URL keyword arguments to determine the version.
    
        An example URL conf for two views that accept two different versions.
    
        urlpatterns = [
            url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
            url(r'^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail')
        ]
    
        GET /1.0/something/ HTTP/1.1
        Host: example.com
        Accept: application/json
        """
    
    # 把版本信息放在路由分发里,并把路由的namespace配置成版本
    # url(r'^v1/', include('users.urls', namespace='v1'))
    class NamespaceVersioning(BaseVersioning):      # 通过namespace来区分版本
        """
        To the client this is the same style as `URLPathVersioning`.
        The difference is in the backend - this implementation uses
        Django's URL namespaces to determine the version.
    
        An example URL conf that is namespaced into two separate versions
    
        # users/urls.py
        urlpatterns = [
            url(r'^/users/$', users_list, name='users-list'),
            url(r'^/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail')
        ]
    
        # urls.py
        urlpatterns = [
            url(r'^v1/', include('users.urls', namespace='v1')),
            url(r'^v2/', include('users.urls', namespace='v2'))
        ]
    
        GET /1.0/something/ HTTP/1.1
        Host: example.com
        Accept: application/json
        """
    
    # 在我们的host上配置版本信息
    # Host:v1.example.com
    class HostNameVersioning(BaseVersioning):        # 通过主机名来区分版本
        """
        GET /something/ HTTP/1.1
        Host: v1.example.com
        Accept: application/json
        """
    
    # 在url过滤条件上配置版本信息
    # GET /something/?version=0.1 HTTP/1.1
    class QueryParameterVersioning(BaseVersioning):  # 通过url查询参数区分版本
        """
        GET /something/?version=0.1 HTTP/1.1
        Host: example.com
        Accept: application/json
        """

    二、源码分析

    1、查看APIView类中的dispatch方法

      APIView中重新定义了dispatch方法,重新封装了request。同时try中的代码一定会执行,因此会执行self.initial()方法。

    class APIView(View):
    
        def dispatch(self, request, *args, **kwargs):
            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)

    2、查看initial方法

      iniital方法中做了各种初始化操作。

      其中versionscheme是版本控制组件的入口,也是determine_version方法的返回值。

      determine_version()的返回值,传值给 request.version 和 request.versioning_scheme。

    class APIView(View):
    
        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
            ... 

      可以看到在这里是有version的,也就是版本。

    3、查看determine_version方法

      可以看到首先判断 self.versioning_class 是否为None。如果为None,则返回None、None。

      scheme实际是自己配置的版本控制类的实例化对象。默认的版本控制类是null,我们必须要有一个自己配置的版本控制类。

      且配置的类里必须要有一个 determine_version 方法(返回值就是版本号)。

    class APIView(View):
    
        def determine_version(self, request, *args, **kwargs):
            """
            If versioning is being used, then determine any API version for the
            incoming request. Returns a two-tuple of (version, versioning_scheme)
            """
            if self.versioning_class is None:
                return (None, None)
            scheme = self.versioning_class()
            return (scheme.determine_version(request, *args, **kwargs), scheme)
    

    (1)继续查看versioning_class

    class APIView(View):
        ...
        versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    

      由此可知就是在api_sttings中配置了一个类。

      因此在前面determine_version()中self.versioning_class()是做了一个实例化的动作。

      继续查看api_settings:在site-packages/rest_framework/settings.py文件中可以看到api_settings其实是APISettings的实例化:

    api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

      DEFAULTS默认读取的是 site-packages/rest_framework/settings.py中DEFAULTS字段:

    DEFAULTS = {
        ...
        'DEFAULT_VERSIONING_CLASS': None,
        ...
    }

      因此,默认情况下self.versioning_class的返回值为None。

    (2)return (scheme.determine_version(request, *args, **kwargs), scheme)返回了一个元组

      如果在/views/course.py的视图类中定义versioning_class:

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import QueryParameterVersioning
    
    
    class CourseView(APIView):
        versioning_class = QueryParameterVersioning
    
        def get(self, request, *args, **kwargs):
            self.dispatch
            return Response('...')

      则可以实例化得到scheme实例,并在函数返回语句中返回scheme。

    (3)进一步查看QueryParameterVersioning中的determine_version

    class QueryParameterVersioning(BaseVersioning):
        """
        GET /something/?version=0.1 HTTP/1.1
        Host: example.com
        Accept: application/json
        """
        invalid_version_message = _('Invalid version in query parameter.')
    
        def determine_version(self, request, *args, **kwargs):
            version = request.query_params.get(self.version_param, self.default_version)
            if not self.is_allowed_version(version):
                raise exceptions.NotFound(self.invalid_version_message)
            return version
    

      这里返回的是version,预计就是版本号。

    (4)进一步查看request.query_params定义

    class Request(object):
    
        @property
        def query_params(self):
            """
            More semantically correct name for request.GET.
            """
            return self._request.GET
    

      因此version = request.query_params.get(self.version_param, self.default_version)其实是去URL中获取GET传来的version对应的参数。

    (5)查看version_param

    class BaseVersioning(object):
        default_version = api_settings.DEFAULT_VERSION
        allowed_versions = api_settings.ALLOWED_VERSIONS
        version_param = api_settings.VERSION_PARAM
    

      由此可知这是一个全局配置,默认值就等于version,由此可知前面返回的version就是版本号。

    4、version返回,分析inital方法

        def initial(self, request, *args, **kwargs):
    
            # Determine the API version, if versioning is in use.
            version, scheme = self.determine_version(request, *args, **kwargs)   
            request.version, request.versioning_scheme = version, scheme
    

      version是版本,scheme是对象(自己配置的版本控制类的实例化对象)并分别赋值给request.version和request.scheme。

    5、在视图类中拿到版本

    class CourseView(APIView):
        versioning_class = QueryParameterVersioning
    
        def get(self, request, *args, **kwargs):
            print(request.version)
            return Response('...')
    

    6、页面访问测试

    (1)直接访问

      

      此时python后台输出:none

    (2)用url参数传递版本

      

      此时python后台输出:v1

     二、版本控制类及源码解析

      在项目中要引入rest_framework框架提供的版本控制类:

    from rest_framework import versioning

    1、查看QueryParameterVersioning类

    class QueryParameterVersioning(BaseVersioning):
        """
        GET /something/?version=0.1 HTTP/1.1
        Host: example.com
        Accept: application/json
        """
        invalid_version_message = _('Invalid version in query parameter.')
    
        def determine_version(self, request, *args, **kwargs):
            version = request.query_params.get(self.version_param, self.default_version)
            if not self.is_allowed_version(version):
                raise exceptions.NotFound(self.invalid_version_message)
            return version
    

      可以看到version拿到后,用self.is_allowed_version方法做了一个判断。

    2、查看is_allowed_version方法

    class BaseVersioning(object):
        default_version = api_settings.DEFAULT_VERSION
        allowed_versions = api_settings.ALLOWED_VERSIONS
        version_param = api_settings.VERSION_PARAM
    
        def is_allowed_version(self, version):
            if not self.allowed_versions:
                return True
            return ((version is not None and version == self.default_version) or
                    (version in self.allowed_versions))
    

      可以看到ALLOWED_VERSIONS也是存放在配置文件中。

    3、在settings.py中添加允许的版本

      凡是关于restframework框架的配置都需要写 REST_FRAMEWORK,并让它等于一个字典

    # 渲染器配置到全局
    REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer',
                                     'rest_framework.renderers.BrowsableAPIRenderer'],
        'ALLOWED_VERSIONS': ['v1', 'v2']  # 允许的版本
    }
    

    4、访问验证

      

    三、默认版本与版本参数

      settings.py做如下配置

    # 渲染器配置到全局
    REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer',
                                     'rest_framework.renderers.BrowsableAPIRenderer'],
        'ALLOWED_VERSIONS': ['v1', 'v2'],  # 允许的版本
        'VERSION_PARAM': 'version',     # 把默认的version修改为其他参数:http://127.0.0.1:8000/api/course/?versionsss=v1
        'DEFAULT_VERSION': 'v1',        # 默认版本
    }
    

    1、修改参数为其他值访问效果

      

    2、配置默认版本后不写版本参数也可获取默认版本

      

      python后台输出:v1。

    四、配置版本控制

    1、局部版本控制

      前面都是在单个类中配置了版本控制,如下所示:

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import QueryParameterVersioning
    
    
    class CourseView(APIView):
        versioning_class = QueryParameterVersioning
    
        def get(self, request, *args, **kwargs):
            self.dispatch
            return Response('...')

    2、全局版本控制

      源码查看到全局版本控制配置信息:

    class APIView(View):
    
        versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    
        # Allow dependency injection of other settings to make testing easier.
        settings = api_settings

      因此也可以在settings.py中配置全局版本控制:

    # 渲染器配置到全局
    REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer',
                                     'rest_framework.renderers.BrowsableAPIRenderer'],
        'ALLOWED_VERSIONS': ['v1', 'v2'],  # 允许的版本
        'VERSION_PARAM': 'version',     # 把默认的version修改为其他参数:http://127.0.0.1:8000/api/course/?versionsss=v1
        'DEFAULT_VERSION': 'v1',        # 默认版本
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning',  # 全局版本控制
    }
    

      显示效果如下:

      

    五、写版本推荐方式——基于url的正则方式(如:/v1/users/)

      前面写的是基于url的get传参方式,如:/users?version=v1,但是这种方式显示版本不是最推荐的。一般需要把版本号写在前面。改写需要调整urls.py配置。

    1、项目urls.py修改

    from django.conf.urls import url, include
    
    urlpatterns = [
        # path('admin/', admin.site.urls),
        url(r'^api/', include('api.urls')),
    ]
    

    2、应用目录下创建urls.py文件及配置

    from django.conf.urls import url, include
    from api.views import course
    
    urlpatterns = [
        url(r'^(?P<version>[v1|v2]+)/course/$', course.CourseView.as_view()),
    ]
    

    3、修改/api/views/course.py类视图文件

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning
    
    
    class CourseView(APIView):
        versioning_class = URLPathVersioning
    
        def get(self, request, *args, **kwargs):
            print(request.version)
            return Response('...')
    

    4、访问显示效果

      

      以后都推荐用这种方式写版本,全局配置修改同上。

    六、其他版本使用方式

      详见:http://www.cnblogs.com/wupeiqi/articles/7805382.html

      基于 accept 请求头方式,如:Accept: application/json; version=1.0

      基于主机名方法,如:v1.example.com

      基于django路由系统的namespace,如:example.com/v1/users/

    七、项目示例

    1、DRFDemo项目中引入版本控制应用

      引入versionDemo应用。配置url.py和view.py文件如下所示:

    # versionDemo/urls.py
    from django.urls import path, include
    from .views import DemoView
    
    urlpatterns = [
        path(r"", DemoView.as_view()),
    ]
    
    
    # versionDemo/views.py
    from django.shortcuts import render
    from rest_framework.views import APIView
    from rest_framework.response import Response
    
    class DemoView(APIView):
        def get(self, request):
            print(request.version)
            print(request.versioning_scheme)
            # 得到版本号,根据版本号的不同返回不同的信息
    
            if request.version == "v1":
                return Response("v1版本的数据")
            elif request.version == "v2":
                return Response("v2版本的数据")
            return Response("不存在的版本")

    2、在settings.py文件中添加版本控制类

      创建定义自己的版本控制类,类中必须自定义 determine_version 方法:

    # 创建文件/DRFDemo/utils/version.py
    
    class MyVersion:
        def determine_version(self, request, *args, **kwargs):
            # 该方法返回值给了 request.version
            # 返回版本号
            # 版本号携带在过滤条件中  xxx?version=v1
            version = request.query_params.get("version", "v1")
            return version 

      再在settings.py中全局引入版本控制,覆盖默认值为None的 versioning_class:

    REST_FRAMEWORK = {
        "DEFAULT_VERSIONING_CLASS": "utils.version.MyVersion"
    }

    3、测试获取version信息

      默认访问“127.0.0.1:8000/version/”,返回得到信息:“v1版本的数据”

      

      访问“127.0.0.1:8000/version/?version=v2”,返回得到信息:“v2版本的数据”

      

      访问"127.0.0.1:8000/version/?version=v3",返回得到信息:“不存在的版本”

      

    4、使用内置的版本控制类

      引入内置的版本控制类:

    from rest_framework.versioning import QueryParameterVersioning,AcceptHeaderVersioning,NamespaceVersioning,URLPathVersioning
    
    #基于url的get传参方式:QueryParameterVersioning------>如:/users?version=v1
    #基于url的正则方式:URLPathVersioning------>/v1/users/
    #基于accept请求头方式:AcceptHeaderVersioning------>Accept: application/json; version=1.0
    #基于主机名方法:HostNameVersioning------>v1.example.com
    #基于django路由系统的namespace:NamespaceVersioning------>example.com/v1/users/

      上述五种版本控制类都默认继承 BaseVersioning:

    class BaseVersioning:
        default_version = api_settings.DEFAULT_VERSION     # 默认版本
        allowed_versions = api_settings.ALLOWED_VERSIONS   # 允许版本
        version_param = api_settings.VERSION_PARAM         # 关键字
    
        def determine_version(self, request, *args, **kwargs):
            msg = '{cls}.determine_version() must be implemented.'
            raise NotImplementedError(msg.format(
                cls=self.__class__.__name__
            ))
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            return _reverse(viewname, args, kwargs, request, format, **extra)
    
        def is_allowed_version(self, version):
            if not self.allowed_versions:
                return True
            return ((version is not None and version == self.default_version) or
                    (version in self.allowed_versions))

      依据BaseVersioning配置settings.py中版本控制:

    REST_FRAMEWORK = {
        # "DEFAULT_VERSIONING_CLASS": "utils.version.MyVersion",
        # 默认使用的版本控制类
        "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning",
        # 默认使用的版本
        "DEFAULT_VERSION": "v1",
        # 允许的版本
        "ALLOWED_VERSIONS": "v1, v2",
        # 版本使用的参数名称
        "VERSION_PARAM": "ver"
    }

      访问成功,显示如下所示:

      

      访问失败,会使用框架提示信息:

      

  • 相关阅读:
    Computer Networking: Computer networks and the Internet
    编译pqxx源码configure时遇到codecs.py LookupError的解决方法
    DBMS-存储和文件结构
    DBMS-关系数据库的设计:范式、函数依赖、分解算法、多值依赖
    WebPack填坑笔记
    VsCode常用快捷键
    用户 在地址栏输入网址 经历了那些
    正向代理和反向代理
    检测浏览器(BOM)以及地址栏网址的API
    js变量按照存储方式区分,有哪些类型,并表述其特点
  • 原文地址:https://www.cnblogs.com/xiugeng/p/11972119.html
Copyright © 2011-2022 走看看