Django框架rest_framework中APIView的as_view()源碼解析、認證、權限、頻率控制

在先後端分離項目中前面咱們也提到了各類認證須要本身來作,那麼咱們用rest_framework的時候前端

rest_framework也爲咱們提供相應的接口,rest_framework中的APIView實現了和Django原生View  as_view()同樣的功能json

並在此基礎上實現了原生request的封裝、認證、權限、視圖等等功能後端

 

 

咱們來看APIView的as_view如何實現的:api

 

 

 經過上篇對View源碼的分析咱們能夠得知,在View的的閉包函數view中調用了dispatch方法,那麼咱們在找dispatch的時候仍是要從self開始找,緩存

此時的self是咱們在視圖層定義的視圖類的對象,視圖類並無定義dispatch,那麼就找父類APIView,在APIView中咱們找到了dispatch閉包

 

那麼意味着APIView對dispatch進行了重寫,咱們來看看APIView怎麼封裝的dispatch方法:app

 

 咱們繼續深刻initialize_request()去看看它是怎麼封裝原生request的:前後端分離

 

 到這裏,咱們能夠知道,它將原生的request和認證等組件給到Request類實例化返回,咱們仍然須要進一步去看Request的源碼:ide

 

 這裏咱們能夠得知,它將原生的request封裝到了新的request對象的_request屬性中,那麼你就會想了,那原生request 的數據和方法都到哪裏去了呢?函數

彆着急,它也給你作了封裝,繼續看:

GET請求的數據:

 

 它將原生GET請求的數據放到了query_parms裏面,咱們在視圖類中經過request.query_parms就能夠取到

 

POST請求數據:

 

 

這裏的data不只僅是POST的數據,全部請求方式的鍵值對數據的都會被放入data裏面,支持urlencoded、form-data、json(application/json)

 

 

 

FILES數據:

 

對USER的封裝:

 

 此外還封裝了auth等其餘功能,這些功能不只在新的request裏面有,它也一樣存在原生的request裏,在該方法的解釋上,它講明瞭它支持Django底層contrib,並將user設置在了Django原生的HttpRequest實例中,以保證在任何Django原生中間件均可以經過校驗,因此咱們在使用APIView時能夠幾乎不用考慮兼容性問題

 

 好了,咱們剛剛看完了initialize_request()源碼,瞭解了APIView對原生request進行如何進行封裝以後

接下來咱們來看initial是如何幫咱們作認證、權限等校驗的

 

 

認證校驗

 首先咱們來看認證校驗

按住Ctrl點進去以後發現它就一行代碼

request.user

那此時咱們繼續找Request類中的user,

 

 發現它執行了_authenticate(),而後因爲好奇心,咱繼續往下點:

 

 經過上圖的分析咱們得知,self.authenticators 這個裏面裝着一個個的認證類對象,那麼這些認證類對象是哪裏來的呢?咱們繼續探究:

 咱們回到Request封裝原生request實例化的地方看傳入的是什麼東西

 

咱們繼續找 get_authenticators()方法!

 

 發現是從self.authentication_classes中拿到的,果真返回的是一個裝有對象的列表

咱們繼續找authentication_classes

咱們在視圖類中沒有定義authentication_classes屬性,那麼繼續往上找發現:

 

 這時候看到它是從api_settings裏找的,咱們或多或少應該明白了,它會從用戶配置中去找這個屬性,可是咱們並無早配置文件中配置這個屬性,也就意味self.get_authenticators()拿到的是空列表,剛纔全部的流程都沒有走,意味着APIView沒有任何的認證措施,那麼這些認證措施就須要咱們本身來作了。。。。

 

如何自定義認證類?

怎麼作呢?缺什麼補什麼,在找authentication_classes的時候它會先去咱們的視圖類中找,那麼咱們就在視圖類中配置這麼個屬性,剛纔也推導過了,它是個列表或者元祖,那麼咱們就用列表好了,進一步反推,它會for循環這個列表並拿出一個個類.authenticate(),那麼咱們就先寫一個類並實現authenticate方法,將類名放到該列表中,剛纔咱們推理得出,authenticate方法能夠沒有返回值,也能夠返回兩個值,一個是user對象,一個是auth對象,若是有返回值,它將user對象賦值給了request.user,這時候咱們明白了,它的內部是在認證完了後將用戶對象塞給了request,此時的request就擁有了user這個全局屬性

我順便去摟了一眼官方文檔,咱們須要本身定義認證類而且繼承BaseAuthentication,那麼接下來咱們認證的寫代碼:

 

 那麼至此,咱們在用APIView的時候自定義認證就作完了,authenticate中的認證邏輯是能夠根據自身公司和項目須要去作的,這裏是給出最簡單的示範。

固然,若是剛纔你的思路一直走過來你會發現,咱們除了在視圖類中配置authentication_classes之外還能夠在配置文件中配置,配置方法以下:

REST_FRAMEWORK={
                "DEFAULT_AUTHENTICATION_CLASSES":["app01.views.MyAuth",]
            }

若是這樣配置了的話,在全局的視圖類都會生效,顯然對於網站註冊登錄主頁進行校驗用戶認證是不合理的,那咱們須要怎麼作呢

因爲源碼中查找authentication_classes的順序是先從視圖類找,視圖類沒有而後找配置文件,那麼咱們能夠在不須要校驗的視圖類中定義

authentication_classes = []

在相應視圖類中將它配置爲空列表便可。

下面咱們來總結下用法:

1、定義認證類(項目中通常在應用裏單開py文件存放)
2、在認證類中實現authenticate方法,實現具體的認證邏輯
3、認證經過返回user_obj和認證對象,這樣request就擁有了全局的user,認證不給前端拋異常

4、配置
  -在視圖類中配置
  -在全局配置,局部禁用

剛纔說了APIView除了有認證,還有權限和頻率控制

 

權限校驗

權限校驗的源碼和認證幾乎如出一轍的思路,用法如出一轍

也是須要自定義權限類,實現has_perission()方法,代碼以下:

from rest_framework.permissions import BasePermission   # 導入BasePermission

class MyPermision(BasePermission):      # 繼承BasePermission
    message = '不是超級用戶,查看不了'     # 自定義返回給前端的訪問錯誤信息
    def has_permission(self,request,view):
        if request.user.user_type==1:
            return True
        else:
            return False


class Book(APIView):                     
    permission_classes = [MyPermision,]    # 視圖類配置
    def get(self,request):
        pass

也能夠全局配置,局部禁用:

在配置文件REST_FRAMEWORK中加上配置

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['app01.views.MyAuth',],
    "DEFAULT_PERMISSION_CLASSES" : ['app01.views.MyPermission',]  ## ---加上這一行便可
}

局部禁用方式同樣是在視圖類中配置 

permission_classes = []

 

頻率控制

-使用:
        -第一步,寫一個頻率類,繼承SimpleRateThrottle
            from rest_framework.throttling import SimpleRateThrottle
            #重寫get_cache_key,返回self.get_ident(request)
            #必定要記住配置一個scop=字符串
            class MyThrottle(SimpleRateThrottle):
                scope = 'lxx'      
                def get_cache_key(self, request, view):
                    return self.get_ident(request)    # 這裏是頻率控制的條件,是以訪問IP來控制仍是以用戶ID控制均可以,暴露的配置接口,
                                 #這裏調用的父類get_ident
f方法
        -第二步:在setting中配置
                REST_FRAMEWORK = {
                   'DEFAULT_THROTTLE_RATES':{
                        'lxx':'3/m'            # 這裏的lxx 即在自定義頻率類中定義的scope
                   }
                }
        -局部使用
            -在視圖類中配置:
                throttle_classes=[MyThrottle,]
        -全局使用
            -在setting中配置    
                'DEFAULT_THROTTLE_CLASSES':['本身定義的頻率類'],
        -局部禁用
            throttle_classes=[]        

咱們來看源碼:

仍是從initial()這裏進

 

點進去看

 

 這段是頻率校驗的核心代碼,self.get_throttles()走的是和上面認證、權限同樣的路子

也是去自定義頻率類、而後配置文件裏找相應頻率控制類而且直接加()實例化了

 

 如今每次for循環出來的 throttle 都是一個實例化的對象,

if not throttle.allow_request(request, self):

allow_request()從字面意思上咱們也能判斷出它應該是校驗是否對本次訪問放行的,那麼它的返回值應該就是True或者False

那麼這句判斷語句的意思就是:若是頻率校驗不經過,那麼就走if塊內部代碼

self.throttled(request, throttle.wait())

這句代碼的意思就是針對  throttle.wait()獲得的不一樣限制結果拋出不一樣異常給前端用戶

點進去看源代碼人家也是這麼幹的

 def throttled(self, request, wait):
        """
        If request is throttled, determine what kind of exception to raise.
        """
        raise exceptions.Throttled(wait)

那麼頻率校驗不經過的狀況咱們知道了,咱們就要關注它內部是如何實現對頻率的校驗的

進到allow_request裏面去看,因爲咱們自定義的頻率校驗類沒有定義該方法,那麼就向它的父類找

 

 

咱們來看源碼人家是怎麼作的

# settings配置
"""
 REST_FRAMEWORK = {
                
                     'DEFAULT_THROTTLE_RATES':{
                        'lxx':'3/m'     # 頻率配置   每分鐘3次
                     }
                 }   
""" 

#如下源碼+我的註釋

def allow_request(self, request, view):
        if self.rate is None:   # self.rate 是 get_rate() 經過咱們聲明的scope = 'lxx' 去拿到的配置文件中頻率配置中頻率值 '3/m'  ,
                   # 須要咱們在頻率控制類中聲明scope = 'lxx',也就是配置中字典的鍵,
                   # 鍵是經過反射scope拿到的,詳見 get_rate()源碼
            return True

        self.key = self.get_cache_key(request, view)   # self是自定義頻率類的對象,那麼get_cache_key將優先從自定義頻率類找
                                                       # 獲得的是頻率控制的依據,是用戶IP\ID或者其餘信息,由重寫get_cache_key肯定
        if self.key is None:
            return True

        self.history = self.cache.get(self.key, [])  # 從緩存中拿到當前用戶的訪問記錄
        self.now = self.timer()   # 獲取當前時間戳
        
     #self.now 是當前執行時間,self.duration是訪問頻率分母,以上述例子爲例 這裏就是60
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()  # 將訪問列表超時的去掉   history =[第三次訪問時間,第二次訪問時間,第一次訪問時間]

        # self.num_requests  訪問頻率分子,設置 '3/m' 表示每分鐘3次,以上例子爲例,這裏就是 3
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()   # 返回訪問失敗信息
        return self.throttle_success()   # 訪問成功

以上的self.duration 和self.num_requests在訪問時自定義頻率控制類實例化的時候,已經經過父類的__init__產生,源碼以下:

這裏又進一步調用了self.parse_rate()方法,傳入了自定義頻率類中的scope屬性值 :'3/m', 那麼這裏咱們大概就能猜到parse_rate()

幹了件什麼事兒,對!那就是拆分這個字符串,並返回  限制次數,限制時間長度,好比  3次,60秒

 一塊兒來看parse_rate()源碼:

 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)

        # 對 '3/m' 進行切分  num_requests=3,duration = 60
        num, period = rate.split('/')
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]  # period[0]取到切分後字符串 'm' 的第一個元素
        return (num_requests, duration)   # (3, 60)

以上allow_request()若是校驗不經過,那麼直接調用 throttle.failure()直接返回false

若是校驗經過,它應該還須要將本次訪問添加到訪問記錄中並保存了,那麼咱們猜 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   # 返回訪問成功 True 供判斷
相關文章
相關標籤/搜索