一、版本传递的五种方式 1. URL的GET参数传递版本 1.1 基本用法
在视图类中引入 rest_framework.versioning
下的 QueryParameterVersioning
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.versioning import QueryParameterVersioningclass UserView (APIView ): versioning_class = QueryParameterVersioning def get (self, request, *args, **kwargs ): print (request.version) return Response("OK" )
1 2 3 4 5 6 7 8 from django.urls import pathfrom api import views urlpatterns = [ path('api/users/' , views.UserView.as_view()), ]
启动服务,浏览器访问路由时,要携带一个版本的参数,且参数名只能为 version
,后端会根据该参数名提取出版本的值
1 http: //127.0.0.1:8000/api /users/ ?v ersion=123
1.2 用法配置 版本管理组件支持一些配置,可以修改传参名,设置默认值等等,这些配置要写在 settings.py
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 REST_FRAMEWORK = { "VERSION_PARAM" : "v" , "DEFAULT_VERSION" : "v1" , "ALLOWED_VERSIONS" : ["v1" , "v2" , "v3" ], }
1.3 全局使用 如果有多个视图类,一个一个去添加版本解析类显然是不现实的。drf
提供了全局配置方式,可以将功能作用于所有的视图
1 2 3 4 5 6 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS" : "rest_framework.versioning.QueryParameterVersioning" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.versioning import QueryParameterVersioningclass UserView (APIView ): def get (self, request, *args, **kwargs ): print (request.version) return Response("OK" )
1.4 源码分析 通过解析 URL
携带的版本参数来获取版本值是怎么实现的?我们可以根据源码来简单分析下:
先来看红色框和箭头部分:
第一步,通过视图类走到了 rest_framework/views.py
中 APIView
的 dispatch
方法,该方法中调用了一个 initial
方法;
第二步,initial
方法中调用了对版本号处理的方法 determine_version
,该方法会返回两个值,最后把两个值分别赋值给 request.version
和 request.versioning_scheme
两个 request
对象的变量,这就是为什么我们可以在自己写的视图类中,通过 request.version
拿到版本号的原因了;
第三步, determine_version
方法中,尝试去找视图类中的 versioning_class
变量,即我们在 UserView
视图类中的 versioning_class = QueryParameterVersioning
这一行。变量如果有值,则执行 scheme = self.versioning_class()
,相当于执行 scheme = QueryParameterVersioning()
,即生成了一个 QueryParameterVersioning
类的对象,最后去执行对象中的 determine_version
方法;
第四步,QueryParameterVersioning
类中的 determine_version
方法里,实际就是通过 request.query_params.get
,即 request.GET.get
方法去获取 GET
请求中的参数值,而获取的变量名和默认值是由其父类 BaseVersioning
中定义的,获取到版本值以后,再判断值是否在指定的范围内,判断的范围也是从父类中获取;
第五步,BaseVersioning
类中定义的版本号参数的变量名、默认值、取值范围均是从 rest_framework/settings.py
中获取;
第六步,rest_framework/settings.py
中会去我们工程下的 settings.py
中寻找 REST_FRAMEWORK
变量,读取里面的值作为版本相关配置的值,如果读取不到相关的变量,则使用自己定义的默认变量,这就是为什么我们不进行版本配置时,传参的变量名只能为 “version” 的原因。
补充 :如果我们在自己编写的视图类中没有定义 versioning_class
,在执行到 determine_version
方法时,就会去寻找视图类父类中的 versioning_class
,即 APIView
中的 versioning_class
(绿色箭头所展示的流程)。 APIView
中的 versioning_class
的值为 DEFAULT_VERSIONING_CLASS
,所以给 DEFAULT_VERSIONING_CLASS
赋值以后就可以作用于全局的视图函数了,而且也解释了为什么同时配置了全局和视图类中的 versioning_class
,视图类中的配置会被优先使用。
2. URL路径传递版本 2.1 基本用法
在视图类中引入 rest_framework.versioning
下的 URLPathVersioning
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.versioning import URLPathVersioningclass UserView (APIView ): versioning_class = URLPathVersioning def get (self, request, *args, **kwargs ): print (request.version) return Response("OK" )
1 2 3 4 5 6 7 8 from django.urls import pathfrom api import views urlpatterns = [ path('api/<str:version>/users/' , views.UserView.as_view()), ]
1 http:// 127.0 .0.1 :8000 /api/ v1/users/
2.2 用法配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 REST_FRAMEWORK = { "VERSION_PARAM" : "v" , "DEFAULT_VERSION" : "v1" , "ALLOWED_VERSIONS" : ["v1" , "v2" , "v3" ], }
2.3 全局使用 1 2 3 4 5 6 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS" : "rest_framework.versioning.URLPathVersioning" }
2.4 源码分析 所有的流程都是差不多的,只是在第四步获取版本值的方式上有所不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class URLPathVersioning (BaseVersioning ): invalid_version_message = _('Invalid version in URL path.' ) def determine_version (self, request, *args, **kwargs ): version = kwargs.get(self .version_param, self .default_version) if version is None : version = self .default_version if not self .is_allowed_version(version): raise exceptions.NotFound(self .invalid_version_message) return version
当路由使用分组方式来接收变量时,解析出的数据会存放在 **kwargs
形参中一层一层传递,直到传给了 URLPathVersioning.determine_version
方法中。
3. 请求头传递版本 3.1 基本用法
在视图类中引入 rest_framework.versioning
下的 AcceptHeaderVersioning
类
1 2 3 4 5 6 7 8 9 10 11 12 from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.versioning import AcceptHeaderVersioningclass UserView (APIView ): versioning_class = AcceptHeaderVersioning def get (self, request, *args, **kwargs ): print (request.version) return Response("OK" )
1 2 3 4 5 6 7 8 from django.urls import pathfrom api import views urlpatterns = [ path('api/users/' , views.UserView.as_view()), ]
启动服务,此时无法使用浏览器设置请求头来访问,可以借助 Post man
发送请求,请求头必须添加:
1 Accept: application/json ;version = v1
3.2 用法配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 REST_FRAMEWORK = { "VERSION_PARAM" : "v" , "DEFAULT_VERSION" : "v1" , "ALLOWED_VERSIONS" : ["v1" , "v2" , "v3" ], }
3.3 全局使用 1 2 3 4 5 6 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS" : "rest_framework.versioning.AcceptHeaderVersioning" }
3.4 源码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class AcceptHeaderVersioning (BaseVersioning ): """ GET /something/ HTTP/1.1 Host: example.com Accept: application/json; version=1.0 """ invalid_version_message = _('Invalid version in "Accept" header.' ) def determine_version (self, request, *args, **kwargs ): media_type = _MediaType(request.accepted_media_type) version = media_type.params.get(self .version_param, self .default_version) version = unicode_http_header(version) if not self .is_allowed_version(version): raise exceptions.NotAcceptable(self .invalid_version_message) return version
AcceptHeaderVersioning
类中的 determine_version
方法是通过将请求头中内容进行解析,根据配置的版本变量名去获取版本值。
4. 二级域名传递版本 4.1 基本用法
在视图类中引入 rest_framework.versioning
下的 HostNameVersioning
类
1 2 3 4 5 6 7 8 9 10 11 12 from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.versioning import HostNameVersioningclass UserView (APIView ): versioning_class = HostNameVersioning def get (self, request, *args, **kwargs ): print (request.version) return Response("OK" )
1 2 3 4 5 6 7 8 from django.urls import pathfrom api import views urlpatterns = [ path('api/users/' , views.UserView.as_view()), ]
因为涉及到域名解析,需要在本地 hosts 文件中添加以下内容
1 2 127.0.0.1 v1.cdc.com127.0.0.1 v2.cdc.com
1 2 http:// v1.cdc.com:8000 /api/u sers/ http:// v2.cdc.com:8000 /api/u sers/
4.2 用法配置 1 2 3 4 5 6 7 8 9 10 11 12 13 REST_FRAMEWORK = { "VERSION_PARAM" : "v" , "DEFAULT_VERSION" : "v1" , "ALLOWED_VERSIONS" : ["v1" , "v2" , "v3" ], }
4.3 全局使用 1 2 3 4 5 6 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS" : "rest_framework.versioning.HostNameVersioning" }
4.4 源码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 class HostNameVersioning (BaseVersioning ): hostname_regex = re.compile (r'^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$' ) invalid_version_message = _('Invalid version in hostname.' ) def determine_version (self, request, *args, **kwargs ): hostname, separator, port = request.get_host().partition(':' ) match = self .hostname_regex.match (hostname) if not match : return self .default_version version = match .group(1 ) if not self .is_allowed_version(version): raise exceptions.NotFound(self .invalid_version_message) return version
HostNameVersioning
类中的 determine_version
方法是将访问后台的域名进行拆分解析,获取到版本号的值。
5. 路由名称空间传递版本 5.1 基本用法
在视图类中引入 rest_framework.versioning
下的 NamespaceVersioning
类
1 2 3 4 5 6 7 8 9 10 11 12 from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.versioning import NamespaceVersioningclass UserView (APIView ): versioning_class = NamespaceVersioning def get (self, request, *args, **kwargs ): print (request.version) return Response("OK" )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from django.urls import path, include urlpatterns = [ path('api/v1/' , include("api.urls" , namespace="v1" )), path('api/v2/' , include("api.urls" , namespace="v2" )), ]from django.urls import pathfrom api import views urlpatterns = [ path('users/' , views.UserView.as_view()), ] app_name = "api"
1 2 http:// 127.0 .0.1 :8000 /api/ v1/users/ http:// 127.0 .0.1 :8000 /api/ v2/users/
5.2 用法配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 REST_FRAMEWORK = { "VERSION_PARAM" : "v" , "DEFAULT_VERSION" : "v1" , "ALLOWED_VERSIONS" : ["v1" , "v2" , "v3" ], }
5.3 全局使用 1 2 3 4 5 6 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS" : "rest_framework.versioning.NamespaceVersioning" }
5.4 源码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class NamespaceVersioning (BaseVersioning ): invalid_version_message = _('Invalid version in URL path. Does not match any version namespace.' ) def determine_version (self, request, *args, **kwargs ): resolver_match = getattr (request, 'resolver_match' , None ) if resolver_match is None or not resolver_match.namespace: return self .default_version possible_versions = resolver_match.namespace.split(':' ) for version in possible_versions: if self .is_allowed_version(version): return version raise exceptions.NotFound(self .invalid_version_message)
NamespaceVersioning
类中的 determine_version
方法是借助路由分组反向解析来获取 Namespace
的值,即版本号的值。
二、反向生成URL 在 QueryParameterVersioning
、URLPathVersioning
、NamespaceVersioning
这三个版本处理的类中还定义了reverse
方法,用来反向生成 URL 并携带相关的的版本信息。
1 2 3 4 5 6 7 8 from django.urls import pathfrom api import views urlpatterns = [ path('api/users/' , views.UserView.as_view(), name="u1" ), path('api/users/<int:pk>' , views.UserView.as_view(), name="u2" ), ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.versioning import QueryParameterVersioningclass UserView (APIView ): versioning_class = QueryParameterVersioning def get (self, request, *args, **kwargs ): print (request.version) url1 = request.versioning_scheme.reverse("u1" , request=request) print (url1) url2 = request.versioning_scheme.reverse("u2" , args=(11 ,), request=request) print (url2) return Response("OK" )