RestFramework——API基本實現及dispatch基本源碼剖析

基於Django實現

  在使用RestFramework以前咱們先用Django本身實現如下API。前端

  API徹底能夠有咱們基於Django本身開發,原理是給出一個接口(URL),前端向URL發送請求以獲取數據。這樣能實現先後端分離的效果。但Django實現的API許多功能都須要咱們本身寫。django

URL編程

from django.contrib import admin from django.conf.urls import url, include from app01 import views from app02 import views urlpatterns = [ url('admin/', admin.site.urls), url('app02/', include('app02.urls'))#路由分發
]
全局路由
from app02 import views from django.conf.urls import url urlpatterns = [ url('^users/', views.users), url('^user/(\d+)', views.user), #######FBV與CBV的分界線########
    url('^users/', views.UsersView.as_view()), url('^user/', views.UserView.as_view()), ]
app02/urls.py

views.pyjson

  FBV後端

from django.shortcuts import render,HttpResponse import json def users(request): response = {'code':1000,'data':None}  #code用來表示狀態,好比1000表明成功,1001表明
    response['data'] = [ {'name':'Damon','age':22}, {'name':'Stefan','age':10}, {'name':'Elena','age':11}, ] return HttpResponse(json.dumps(response))  #返回就送字符串,前端解析

def user(request,pk): if request.method =='GET': return HttpResponse(json.dumps({'name':'Stefan','age':11}))  #返回一條數據
    elif request.method =='POST': return HttpResponse(json.dumps({'code':1111}))  #返回一條數據
    elif request.method =='PUT': pass
    elif request.method =='DELETE': pass
FBV

  CBVapi

from django.views import View class UsersView(View): def get(self,request): response = {'code':1000,'data':None} response['data'] = [ {'name': 'Damon', 'age': 22}, {'name': 'Stefan', 'age': 10}, {'name': 'Elena', 'age': 11}, ] return HttpResponse(json.dumps(response),stutas=200) class UserView(View): def get(self,request,pk): return HttpResponse(json.dumps({'name':'haiyan','age':11}))  #返回一條數據
    def post(self,request,pk): return HttpResponse(json.dumps({'code':1111}))  #返回一條數據
    def put(self,request,pk): pass
    def delete(self,request,pk): pass
CBV

注:一般咱們在先後端分離進行編程時會推崇使用CBV的形式,CBV的代碼可讀性較高。緩存

 

基於RestFramework實現

安裝:app

pip3 install djangorestframework -i http://pypi.douban.com/simple/ --trusted-host=pypi.douban.com

  RestFramework能夠直接在Django中使用,安裝完RestFramework後在Django中能夠當作模塊通常導入便可使用。(記得在settings.py中進行註冊,如app)前後端分離

URL與基於Django實現相同,這裏選用CBV的形式ide

from app02 import views from django.conf.urls import url urlpatterns = [ url('^users/', views.UsersView.as_view()),#CBV必需要有as_view()
    url('^user/', views.UserView.as_view()), ]

views.py

  CBV

#導入rest_framework,自定義視圖的類需繼承APIView
from rest_framework.views import APIView from rest_framework.response import Response class TestView(APIView): def dispatch(self, request, *args, **kwargs): """ 請求到來以後,在url中執行as_view()就會執行dispatch方法,dispatch方法是APIView類中內置的,根據請求方式不一樣觸發 get/post/put等方法。可自定製~ 注意:APIView中的dispatch方法有好多好多的功能 """
        return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')

注:重要的功都在APIView的dispatch中觸發。要掌握RestFramework,必須弄懂dispatch方法作了些什麼,這樣咱們才能夠根據本身的需求進行自定製。

 

dispatch基本源碼剖析

  咱們在繼承了APIView以後就能夠重寫裏面的方法進行自定製。此時咱們須要先弄懂APIView裏到底封裝了哪些方法。在APIView中,最重要的就是dispatch方法。

請求在url中執行as_view()時就會觸發dispatch,進入源碼咱們能夠看到dispatch主要作了四件事:

#在APIView類中:
    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 # 1.封裝Django原始的request,使得用了framework之後的request都具備更多功能
        """ 增長的功能 parsers=self.get_parsers(), authenticators=self.get_authenticators(),#獲取認證相關的全部類並實例化,傳入request對象 negotiator=self.get_content_negotiator(), parser_context=parser_context """ request = self.initialize_request(request, *args, **kwargs) self.request = request#將封裝後的request賦值給原始request
        self.headers = self.default_response_headers  # deprecate?

        try: """ 2.版本處理、用戶認證、權限、訪問頻率限制 """ 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 #3.執行函數get/post/put/delete
            response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) #4.對返回結果進行再次加工
        self.response = self.finalize_response(request, response, *args, **kwargs) return self.response

 

接下來咱們對每一步進行具體的分析

第一步:封裝request

request = self.initialize_request(request, *args, **kwargs) #查看initialize_request作了什麼
def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(),#獲取認證相關的全部類並實例化,傳入request對象。優先找本身的,沒有就找父類的
        negotiator=self.get_content_negotiator(), parser_context=parser_context )

 

1.一、咱們看到request封裝了一個認證的功能——獲取認證相關的全部的類並實例化,看看get_authenticators()作了什麼

def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """
        #返回的是對象列表[SessionAuthentication,BaseAuthentication]
        return [auth() for auth in self.authentication_classes] #self.authentication_classes是封裝有認證功能的類的列表
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES  #默認的,若是本身有會優先執行直接的

 

1.二、去settings.py查看默認的配置是什麼

DEFAULTS = { # Base API policies
    'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication',   #這時候就找到了他默認認證的類了,能夠導入看看
        'rest_framework.authentication.BasicAuthentication' ),

 

1.三、導入SessionAutenticationBasicAuthentication查看這兩個雷兜風裝了什麼功能

from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import BaseAuthentication
class BaseAuthentication(object): """ All authentication classes should extend BaseAuthentication. """

    def authenticate(self, request): """ Authenticate the request and return a two-tuple of (user, token). """
        raise NotImplementedError(".authenticate() must be overridden.") def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """
        pass
BaseAuthentication
class BasicAuthentication(BaseAuthentication): """ HTTP Basic authentication against username/password. """ www_authenticate_realm = 'api'

    def authenticate(self, request): """ Returns a `User` if a correct username and password have been supplied using HTTP Basic authentication. Otherwise returns `None`. """ auth = get_authorization_header(request).split() if not auth or auth[0].lower() != b'basic': return None   #返回none不處理。讓下一個處理

        if len(auth) == 1: msg = _('Invalid basic header. No credentials provided.') raise exceptions.AuthenticationFailed(msg) elif len(auth) > 2: msg = _('Invalid basic header. Credentials string should not contain spaces.') raise exceptions.AuthenticationFailed(msg) try: auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')   #用partition切割冒號也包括
        except (TypeError, UnicodeDecodeError, binascii.Error): msg = _('Invalid basic header. Credentials not correctly base64 encoded.') raise exceptions.AuthenticationFailed(msg) userid, password = auth_parts[0], auth_parts[2]  # 返回用戶和密碼
        return self.authenticate_credentials(userid, password, request) def authenticate_credentials(self, userid, password, request=None): """ Authenticate the userid and password against username and password with optional request for context. """ credentials = { get_user_model().USERNAME_FIELD: userid, 'password': password } user = authenticate(request=request, **credentials) if user is None: raise exceptions.AuthenticationFailed(_('Invalid username/password.')) if not user.is_active: raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) return (user, None) def authenticate_header(self, request): return 'Basic realm="%s"' % self.www_authenticate_realm
BasicAuthentication

 

1.四、簡單自定製認證的類

  咱們能夠看到源碼中最重要的就是BasicAuthenticationauthenticate方法,因此要自定製認證的類只需重寫該方法便可

class MyAuthentication(BaseAuthentication): def authenticate(self, request): token = request.query_params.get('token')#登陸用戶有tocken字段
        obj = models.UserInfo.objects.filter(token=token).first() if obj: return (obj.username,obj) return None def authenticate_header(self, request): pass

 

第二步、版本處理、認證、權限、訪問頻率限制

self.initial(request, *args, **kwargs) #查看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.
    #2.1 處理版本信息,獲取版本必須用version
    version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted
    #2.2認證,將user封裝到request對象中
 self.perform_authentication(request) #2.3 權限
 self.check_permissions(request) #2.4 對請求用戶進行訪問頻率的限制
    self.check_throttles(request)

 

2.2.一、認證:查看perform_authentication方法,發現只是將user封裝到了request中

def perform_authentication(self, request): request.user

 

2.2.二、查看request.user中都封裝了什麼

class Request(object): @property def user(self): """ Returns the user associated with the current request, as authenticated by the authentication classes provided to the request. """
        if not hasattr(self, '_user'): self._authenticate()#執行用戶認證
        return self._user

 

2.2.三、執行self._authenticate() 開始用戶認證,若是驗證成功後返回元組: (用戶,用戶Token)

def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """
        #循環對象列表
        for authenticator in self.authenticators: try: # 執行每個對象的authenticate 方法
                user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise

            if user_auth_tuple is not None: self._authenticator = authenticator self.user, self.auth = user_auth_tuple#返回一個元組,賦給了self,就能夠request.user,request.auth了
                return self._not_authenticated()

 

2.2.四、在user_auth_tuple = authenticator.authenticate(self) 進行驗證,若是驗證成功,執行類裏的authenticatie方法。若是用戶沒有認證成功:self._not_authenticated()

def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """
        # 若是跳過了全部認證,默認用戶和Token和使用配置文件進行設置
        self._authenticator = None if api_settings.UNAUTHENTICATED_USER: self.user = api_settings.UNAUTHENTICATED_USER()# 默認值爲:匿名用戶AnonymousUser
        else: self.user = None# None 表示跳過該認證

        if api_settings.UNAUTHENTICATED_TOKEN: self.auth = api_settings.UNAUTHENTICATED_TOKEN()# 默認值爲:None
        else: self.auth = None #默認值均可以在settings.py中進行自定製配置
REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, }

 

2.3.、權限控制

######check_permissions方法#######
    def check_permissions(self, request): """ Check if the request should be permitted. Raises an appropriate exception if the request is not permitted. """
        for permission in self.get_permissions():#尋找類中的get_permissions()方法
            if not permission.has_permission(request, self):#無權限則拋出異常
 self.permission_denied( request, message=getattr(permission, 'message', None) ) ######get_permissions方法#######
    def get_permissions(self): """ Instantiates and returns the list of permissions that this view requires. """
        return [permission() for permission in self.permission_classes]#去settings.py中查是否有權限配置

 

2.四、對訪問頻率進行限制(如下簡稱限流)

  限流的主要目的仍是爲了防爬。一個網站的最終目的是爲了讓人去訪問的,可是有時候會有一些人工智能作一些對網站有傷害的事,這時候咱們就須要進行相應的限制了。權限的分配是一種對網站的保護的限制,但有些功能(好比看新聞、看動態等)是不須要任何權限只須要進入網站就能夠查看的,這時咱們就須要進行相應的限流操做,區分出非人類的用戶訪問予以限制。

def check_throttles(self, request): """ Check if request should be throttled. Raises an appropriate exception if the request is throttled. """
        """ 循環每個throttle對象,執行allow_request方法 allow_request: 返回False,說明限制訪問頻率 返回True,說明不限制,通行 可自定製 """
        for throttle in self.get_throttles(): if not throttle.allow_request(request, self): self.throttled(request, throttle.wait())#throttle.wait()表示多少秒後可再次訪問
from __future__ import unicode_literals import time from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured from rest_framework.settings import api_settings class BaseThrottle(object): """ Rate throttling of requests. """

    def allow_request(self, request, view): """ Return `True` if the request should be allowed, `False` otherwise. """
        raise NotImplementedError('.allow_request() must be overridden') def get_ident(self, request):#惟一標識
        """ Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR if present and number of proxies is > 0. If not use all of HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR. """ xff = request.META.get('HTTP_X_FORWARDED_FOR') remote_addr = request.META.get('REMOTE_ADDR')#獲取IP等
        num_proxies = api_settings.NUM_PROXIES#獲取代理信息

        if num_proxies is not None: if num_proxies == 0 or xff is None: return remote_addr addrs = xff.split(',') client_addr = addrs[-min(num_proxies, len(addrs))] return client_addr.strip() return ''.join(xff.split()) if xff else remote_addr def wait(self): """ Optionally, return a recommended number of seconds to wait before the next request. """
        return None#等待時長,可重寫
BaseThrottle
class SimpleRateThrottle(BaseThrottle): """ 一個簡單的緩存實現,只須要` get_cache_key() `。被覆蓋。 速率(請求/秒)是由視圖上的「速率」屬性設置的。類。該屬性是一個字符串的形式number_of_requests /期。 週期應該是:(的),「秒」,「M」,「min」,「h」,「小時」,「D」,「一天」。 之前用於節流的請求信息存儲在高速緩存中。 A simple cache implementation, that only requires `.get_cache_key()` to be overridden. The rate (requests / seconds) is set by a `throttle` attribute on the View class. The attribute is a string of the form 'number_of_requests/period'. Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') Previous request information used for throttling is stored in the cache. """ cache = default_cache timer = time.time cache_format = 'throttle_%(scope)s_%(ident)s' scope = None THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES def __init__(self): if not getattr(self, 'rate', None): self.rate = self.get_rate()#點進去看到須要些一個scope ,2/m
        self.num_requests, self.duration = self.parse_rate(self.rate) def get_cache_key(self, request, view):#這個至關因而一個半成品,咱們能夠來補充它
        """ Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """
        raise NotImplementedError('.get_cache_key() must be overridden') def get_rate(self): """ Determine the string representation of the allowed request rate. """
        if not getattr(self, 'scope', None):#檢測必須有scope,沒有就報錯了
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % self.__class__.__name__) raise ImproperlyConfigured(msg) try: return self.THROTTLE_RATES[self.scope] except KeyError: msg = "No default throttle rate set for '%s' scope" % self.scope raise ImproperlyConfigured(msg) def parse_rate(self, rate): """ Given the request rate string, return a two tuple of: <allowed number of requests>, <period of time in seconds> """
        if rate is None: return (None, None) num, period = rate.split('/') num_requests = int(num) duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] return (num_requests, duration) # 一、一進來首先執行,
    def allow_request(self, request, view): """ Implement the check to see if the request should be throttled. On success calls `throttle_success`. On failure calls `throttle_failure`. """
        if self.rate is None: return True self.key = self.get_cache_key(request, view)#二、執行get_cache_key
        if self.key is None: return True#不限制
 self.history = self.cache.get(self.key, [])#三、獲得的key,默認是一個列表,賦值給了self.history,
                                                       # self.history能夠理解爲每個ip對應的訪問記錄
        self.now = self.timer() # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration: self.history.pop() if len(self.history) >= self.num_requests: return self.throttle_failure() return self.throttle_success() def throttle_success(self): """ Inserts the current request's timestamp along with the key into the cache. """ self.history.insert(0, self.now) self.cache.set(self.key, self.history, self.duration) return True def throttle_failure(self): """ Called when a request to the API has failed due to throttling. """
        return False def wait(self): """ Returns the recommended next request time in seconds. """
        if self.history: remaining_duration = self.duration - (self.now - self.history[-1]) else: remaining_duration = self.duration available_requests = self.num_requests - len(self.history) + 1
        if available_requests <= 0: return None return remaining_duration / float(available_requests)
SimpleRateThrottle

 

第三步、執行函數get/post/put/delete

if request.method.lower() in self.http_method_names:#http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
  handler = getattr(self, request.method.lower(),self.http_method_not_allowed)#反射
else:   handler = self.http_method_not_allowed#拋出異常

 

第四步、對返回結果進行再次加工

相關文章
相關標籤/搜索