註意:一定要跟著博主的解說再看代碼的中文註釋及其下麵的一行代碼!!! 說到api版本控制,就是我們的前端人員請求的後臺介面可能有多個版本,後臺的介面地址一般是有兩種形式,博主現以這兩種形式逐一解釋api版本控制組件的源碼剖析。 第一種api版本控制的url格式一般是:http://localhost ...
註意:一定要跟著博主的解說再看代碼的中文註釋及其下麵的一行代碼!!!
說到api版本控制,就是我們的前端人員請求的後臺介面可能有多個版本,後臺的介面地址一般是有兩種形式,博主現以這兩種形式逐一解釋api版本控制組件的源碼剖析。
第一種api版本控制的url格式一般是:http://localhost:8000/user/select/?version=v1。第二種是:http://localhost:8000/user/v1/select/。分別對應以下兩種
1、我們依然是使用流程來解析源碼,首先我們肯定是匹配user下select路由的視圖類進入as_view方法
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"), # 用戶信息查詢所有,與上者只存其一 ]
2、在看下UserView下有沒有as_view方法
class UserView(APIView): def get(self, request): print(request.version) return Response(data={"code": 200, "result": "res"})
3、會發現UserView下沒有as_view方法,這個是可以查看APIView父類下有沒有as_view方法,若沒有,就以此類推。顯而易見,APIView下有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 # 執行父類的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()
4、但是APIView類的as_view方法中又去執行了父類的as_view方法,super關鍵字已經在前幾篇博客中提及,朋友們可前往查看。博主就再一次添上View父類的as_view方法代碼及中文註釋
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 viewView.as_view()
5、在上面的方法中他就會執行到裡面的view方法,根據中文註釋,最後再一次從請求視圖類開始尋找dispatch方法,請求視圖類中沒有,就又回到了APIView類中,執行dispatch方法,添上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還是請求視圖類對象 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.responseAPIView.dispatch()
6、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)APIView.initial()
7、裡面就會有版本控制的代碼語句:version, schema = self.determine_version(request, *args, **kwargs),接下來就添上涉及determine_version方法的一些重要代碼及中文註釋
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)APIView.determine_version()
8、從上面就可以看出我們可以在請求視圖類中編寫上面提及的變數,但是這個變數的值就不是列表了,還有代碼中的註釋,值可以是一個我們自定義或者框架自帶的版本控制類,隨後添上Django框架中versioning.py文件的部分版本控制類
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 )versioning.py
9、最後,我們若想要自定義,就需要繼承裡面的基類,就和上面的類似,而且其實我們是不需要自定義,因為Django中已經夠開發使用。如果在全局中配置,方式如下:
REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "URLPathVersioning", "DEFAULT_VERSION": "v1", # 預設的版本 "ALLOWED_VERSIONS": ["v1", "v2"] # 允許的版本 }