在我們給外部提供的API中,可會存在多個版本,不同的版本可能對應的功能不同,所以這時候版本使用就顯得尤為重要,django rest framework也為我們提供了多種版本使用方法。 版本使用方式: 1.在url中傳遞版本:如http://www.example.com/api?version=v ...
一、簡介 |
在我們給外部提供的API中,可會存在多個版本,不同的版本可能對應的功能不同,所以這時候版本使用就顯得尤為重要,django rest framework也為我們提供了多種版本使用方法。
二、基本使用 |
版本使用方式:
1.在url中傳遞版本:如http://www.example.com/api?version=v1
和其他組建一樣,我們在utils裡面建立version.py,添加版本類
#!/usr/bin/env python3 #_*_ coding:utf-8 _*_ #Author:wd from rest_framework.versioning import BaseVersioning class Myversion(BaseVersioning): def determine_version(self, request, *args, **kwargs): myversion=request.query_params.get('version') return myversion
在訂單視圖中應用版本,(當然直接可以使用request.get獲取)
class OrderView(APIView): '''查看訂單''' from utils.permissions import MyPremission from utils.version import Myversion authentication_classes = [Authentication,] #添加認證 permission_classes = [MyPremission,] #添加許可權控制 versioning_class = Myversion #添加版本 def get(self,request,*args,**kwargs): print(request.version)#獲取版本 #當然使用request._request.get('version')也可以 ret = {'code':1000,'msg':"你的訂單已經完成",'data':"買了一個mac"} return JsonResponse(ret,safe=True)
models.py
from django.db import models class UserInfo(models.Model): user_type_choice = ( (1,"普通用戶"), (2,"會員"), ) user_type = models.IntegerField(choices=user_type_choice) username = models.CharField(max_length=32,unique=True) password = models.CharField(max_length=64) class UserToken(models.Model): user = models.OneToOneField(to=UserInfo) token = models.CharField(max_length=64)
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^api/v1/auth', views.AuthView.as_view()), url(r'^api/v1/order', views.OrderView.as_view()), ]
views.py
from django.shortcuts import HttpResponse from django.http import JsonResponse from rest_framework.views import APIView from rest_framework.authentication import BaseAuthentication from . import models from rest_framework import exceptions import hashlib import time class Authentication(BaseAuthentication): """ 認證類 """ def authenticate(self, request): token = request._request.GET.get("token") toke_obj = models.UserToken.objects.filter(token=token).first() if not toke_obj: raise exceptions.AuthenticationFailed("用戶認證失敗") return (toke_obj.user, toke_obj) # 這裡返回值一次給request.user,request.auth def authenticate_header(self, val): pass def md5(user): ctime = str(time.time()) m = hashlib.md5(bytes(user,encoding="utf-8")) m.update(bytes(ctime,encoding="utf-8")) return m.hexdigest() class AuthView(APIView): """登陸認證""" def dispatch(self, request, *args, **kwargs): return super(AuthView, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return HttpResponse('get') def post(self, request, *args, **kwargs): ret = {'code': 1000, 'msg': "登錄成功"} try: user = request._request.POST.get("username") pwd = request._request.POST.get("password") obj = models.UserInfo.objects.filter(username=user, password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = "用戶名或密碼錯誤" else: token = md5(user) models.UserToken.objects.update_or_create(user=obj, defaults={"token": token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = "請求異常" return JsonResponse(ret) class OrderView(APIView): '''查看訂單''' from utils.permissions import MyPremission from utils.version import Myversion authentication_classes = [Authentication,] #添加認證 permission_classes = [MyPremission,] #添加許可權控制 versioning_class = Myversion def get(self,request,*args,**kwargs): print(request.version) ret = {'code':1000,'msg':"你的訂單已經完成",'data':"買了一個mac"} return JsonResponse(ret,safe=True)
使用postman發送請求:http://127.0.0.1:8000/api/v1/order?token=7c191332ba452abefe516ff95ea9994a&version=v1,後臺可獲取版本。
當然上面獲取版本方式還有更為簡單的獲取版本方法,使用QueryParameterVersioning,其就是封裝的以上過程。
class OrderView(APIView): '''查看訂單''' from utils.permissions import MyPremission from utils.version import Myversion from rest_framework.versioning import QueryParameterVersioning authentication_classes = [Authentication,] #添加認證 permission_classes = [MyPremission,] #添加許可權控制 versioning_class = QueryParameterVersioning #該方法獲取參數的key為version def get(self,request,*args,**kwargs): print(request.version) ret = {'code':1000,'msg':"你的訂單已經完成",'data':"買了一個mac"} return JsonResponse(ret,safe=True)
當然,DRF也提供了可配置的版本,並且還能控製版本使用
settings.py
REST_FRAMEWORK = {#版本配置 "DEFAULT_VERSION":'v1', #預設的版本 "ALLOWED_VERSIONS":['v1','v2'], #允許的版本,這裡只允許V1和v2 "VERSION_PARAM":'version' , #get方式url中參數的名字 如?version=v1 }
使用postman驗證,發送帶token和版本http://127.0.0.1:8000/api/v1/order?token=7c191332ba452abefe516ff95ea9994a&version=v3
結果:
可見版本配置生效。
2.使用url路徑傳遞版本,如http://www.example.com/api/v1,django rest framework 當然也為我們提供了類:URLPathVersioning
為了區分,這裡新建url和view,如下:
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^api/v1/auth', views.AuthView.as_view()), url(r'^api/v1/order', views.OrderView.as_view()), url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view()), # 新建的url ]
UserView
class UserView(APIView): '''查看用戶信息''' from rest_framework.versioning import URLPathVersioning versioning_class =URLPathVersioning def get(self,request,*args,**kwargs): print(request.version) #獲取版本 res={"name":"wd","age":22} return JsonResponse(res,safe=True)
使用postman請求:http://127.0.0.1:8000/api/v1/user,同樣後臺能拿到版本結果。
三、源碼剖析 |
和認證流程一樣,請求進來,同樣走APIview的dispatch的方法,請閱讀註解部分:
1.APIView類的dispatch源碼:
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, # parsers=self.get_parsers(), # authenticators=self.get_authenticators(), # negotiator=self.get_content_negotiator(), # parser_context=parser_context # ) #request(原始request,[BasicAuthentications對象,]) #獲取原生request,request._request #獲取認證類的對象,request.authticators #1.封裝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
2.接著執行self.inital方法:
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 #2.實現認證 self.perform_authentication(request) #3.許可權判斷 self.check_permissions(request) #4.頻率限制 self.check_throttles(request)
3.可以看到版本控制是在認證之前,首先下執行version, scheme = self.determine_version(request, *args, **kwargs),以下是self.determine_version源碼:
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: #先判斷版本類是否存在(self.versioning_class 是否為存在),不存在返回tuple,(none,none) return (None, None) scheme = self.versioning_class() #存在返回版本類對象 return (scheme.determine_version(request, *args, **kwargs), scheme) #版本類存在,最後返回版本類對象的determine_version方法結果(也就是返回的版本號),和類對象,
這也就是每個版本類必須要有的方法,用來獲取版本。
4.承接 self.determine_version方法執行完成以後,接著執行request.version, request.versioning_scheme = version, scheme,這個不用多說,無非將版本號賦值給request.version屬性,版本類對象賦值給request.versioning_scheme,這也就是我們為什麼能通過request.version獲取版本號的原因。
5.同認證源碼一樣,self.determine_version方法中使用的版本類self.versioning_class(),在全局中也有配置
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 #版本處理類配置
6.基於以上源碼分析完成以後,下麵我們來剖析下,我們示例中所使用的兩個版本處理類,具體分析請看註解:
QueryParameterVersioning(BaseVersioning)
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.') ## 當setting.py配置了允許的版本時候,不匹配版本返回的錯誤信息,可以自己定義 def determine_version(self, request, *args, **kwargs): ## 獲取版本方法 version = request.query_params.get(self.version_param, self.default_version) # 通過request.query_paras方法獲取(本質request.MATE.get),
default_version預設是version,是在settings中配置的 if not self.is_allowed_version(version): #不允許的版本拋出異常 raise exceptions.NotFound(self.invalid_version_message) return version #無異常則返回版本號 def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): #url 反解析,可以通過該方法生成請求的url,後面會有示例 url = super(QueryParameterVersioning, self).reverse( viewname, args, kwargs, request, format, **extra ) if request.version is not None: return replace_query_param(url, self.version_param, request.version) return url
URLPathVersioning
class URLPathVersioning(BaseVersioning): """ 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 """ invalid_version_message = _('Invalid version in URL path.') # 不允許的版本信息,可定製 def determine_version(self, request, *args, **kwargs): ## 同樣實現determine_version方法獲取版本 version = kwargs.get(self.version_param, self.default_version) # 由於傳遞的版本在url的正則中,所以從kwargs中獲取,self.version_param預設是version if not self.is_allowed_version(version): raise exceptions.NotFound(self.invalid_version_message) # 沒獲取到,拋出異常 return version # 正常獲取,返回版本號 def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): # url反解析,後面會有示例 if request.version is not None: kwargs = {} if (kwargs is None) else kwargs kwargs[self.version_param] = request.version return super(URLPathVersioning, self).reverse( viewname, args, kwargs, request, format, **extra
這個版本類都繼承了BaseVersioning:
class BaseVersioning(object): default_version = api_settings.DEFAULT_VERSION #默默人版本配置 allowed_versions = api_settings.ALLOWED_VERSIONS #允許版本配置 version_param = api_settings.VERSION_PARAM #版本key配置 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))
四、利用版本反向生成URL |
以URLPathVersioning為例,其本質也是用的django的url反向解析方法,實現過程這裡就不用過多說明,有興趣可以自己看源碼。
1.配置url,為view取別名
urlpatterns = [ url(r'^api/v1/auth', views.AuthView.as_view()), url(r'^api/v1/order', views.OrderView.as_view()), url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view(),name="user_view"), ]
2.利用reverse方法反向生成請求的url,UserView視圖。
class UserView(APIView): '''查看用戶信息''' from rest_framework.versioning import URLPathVersioning versioning_class =URLPathVersioning def get(self,request,*args,**kwargs): print(request.version) url = request.versioning_scheme.reverse(viewname='user_view', request=request) #versioning_scheme已經在源碼中分析過了,就是版本類實例化的對象 print(url) res={"name":"wd","age":22} return JsonResponse(res,safe=True)
使用postman發請求:http://127.0.0.1:8000/api/v1/user查看結果如下:
五、總結 |
對於版本控制來說,其實沒必要自己去定義或自己寫版本處理的類,推薦使用全局配置,以及URLPathVersioning類。
具體配置:
# 全局配置 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", #類的路徑 "DEFAULT_VERSION":'v1', #預設的版本 "ALLOWED_VERSIONS":['v1','v2'], #允許的版本 # "VERSION_PARAM":'version' #使用QueryParameterVersioning時候進行的配置,get請求時候傳遞的參數的key } #單一視圖 versioning_class =URLPathVersioning