說到api版本控制,就是咱們的前端人員請求的後臺接口可能有多個版本,後臺的接口地址通常是有兩種形式,博主現以這兩種形式逐一解釋api版本控制組件的源碼剖析。前端
第一種api版本控制的url格式通常是:http://localhost:8000/user/select/?version=v1。第二種是:http://localhost:8000/user/v1/select/。分別對應如下兩種python
一、咱們依然是使用流程來解析源碼,首先咱們確定是匹配user下select路由的視圖類進入as_view方法django
from django.conf.urls import url from . import views app_name = '[user]' urlpatterns = [ # 這是get請求參數的 url(r'select/', views.UserView.as_view(), name="select"), # 用戶信息查詢全部 # 這是urlpath路徑的參數 url(r'^?P<version>[v1|v2])/select/$', views.UserView.as_view(), name="select"), # 用戶信息查詢全部,與上者只存其一 ]
二、在看下UserView下有沒有as_view方法json
class UserView(APIView): def get(self, request): print(request.version) return Response(data={"code": 200, "result": "res"})
三、會發現UserView下沒有as_view方法,這個是能夠查看APIView父類下有沒有as_view方法,若沒有,就以此類推。顯而易見,APIView下有as_view方法,博主添上代碼已中文註釋api
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 # 執行父類的as_view方法,這裏的cls就是請求視圖類 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方法,super關鍵字已經在前幾篇博客中說起,朋友們可前往查看。博主就再一次添上View父類的as_view方法代碼及中文註釋session
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)) # 執行view方法 def view(request, *args, **kwargs): # 實例化請求視圖類對象,即self是請求視圖類對象 self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get # 注意:這裏的request對象仍是原生的request對象 self.request = request self.args = args self.kwargs = kwargs # dispatch方法相當重要 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方法,根據中文註釋,最後再一次從請求視圖類開始尋找dispatch方法,請求視圖類中沒有,就又回到了APIView類中,執行dispatch方法,添上dispatch方法代碼及中文註釋app
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仍是請求視圖類對象 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: # 經過python的反射機制反射到請求視圖類的方法名稱 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
六、api版本控制組件就在initial方法裏頭,咱們點進去看下,添上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. # rest framework的api的版本控制 # 相似於:api/?version=v1 或者是 api/v1/ version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted # 查看源碼,用戶驗證的方法,這個request 是加工以後的request self.perform_authentication(request) # 用戶權限驗證 self.check_permissions(request) # 用戶訪問頻率限制 self.check_throttles(request)
七、裏面就會有版本控制的代碼語句:version, schema = self.determine_version(request, *args, **kwargs),接下來就添上涉及determine_version方法的一些重要代碼及中文註釋ide
class APIView(View): # 若是請求視圖類中沒有versioning_class變量及值,就會去匹配全局配置文件的值 versioning_class = api_settings.DEFAULT_VERSIONING_CLASS 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) """ # 這裏判斷self.versioning_class是否有值 if self.versioning_class is None: return (None, None) # 實例化版本控制類對象 scheme = self.versioning_class() from rest_framework.versioning import BaseVersioning # 這裏就對應上一個方法的版本值和調用的版本控制類對象 return (scheme.determine_version(request, *args, **kwargs), scheme)
八、從上面就能夠看出咱們能夠在請求視圖類中編寫上面說起的變量,可是這個變量的值就不是列表了,還有代碼中的註釋,值能夠是一個咱們自定義或者框架自帶的版本控制類,隨後添上Django框架中versioning.py文件的部分版本控制類fetch
class BaseVersioning(object): # 若是請求視圖類中沒有如下變量和值,就會匹配全局配置文件中的值 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)) 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 def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): # 這個方法就是生成反向的url,這裏傳遞視圖的名稱以外,只須要傳遞新封裝的request對象便可 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 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. # 編寫url路由的規則就須要遵循下面的編寫規則,這個很重要 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): # 獲取版本值 version = kwargs.get(self.version_param, self.default_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,這裏傳遞視圖的名稱以外,只須要傳遞新封裝的request對象便可 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 )
九、最後,咱們若想要自定義,就須要繼承裏面的基類,就和上面的相似,並且其實咱們是不須要自定義,由於Django中已經夠開發使用。若是在全局中配置,方式以下:
REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "URLPathVersioning", "DEFAULT_VERSION": "v1", # 默認的版本 "ALLOWED_VERSIONS": ["v1", "v2"] # 容許的版本 }