在上篇我們對Django原生View源碼進行了局部解析:https://www.cnblogs.com/dongxixi/p/11130976.html
在前后端分離項目中前面我們也提到了各種認證需要自己來做,那么我們用rest_framework的時候
rest_framework也為我們提供相應的接口,rest_framework中的APIView實現了和Django原生View as_view()一樣的功能
並在此基礎上實現了原生request的封裝、認證、權限、視圖等等功能
我們來看APIView的as_view如何實現的:

通過上篇對View源碼的分析我們可以得知,在View的的閉包函數view中調用了dispatch方法,那么我們在找dispatch的時候還是要從self開始找,
此時的self是我們在視圖層定義的視圖類的對象,視圖類並沒有定義dispatch,那么就找父類APIView,在APIView中我們找到了dispatch

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

我們繼續深入initialize_request()去看看它是怎么封裝原生request的:

到這里,我們可以知道,它將原生的request和認證等組件給到Request類實例化返回,我們仍然需要進一步去看Request的源碼:

這里我們可以得知,它將原生的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 供判斷
至此,頻率控制部分的源碼我們也學習完了,當我們回過頭去看用法的時候會有種豁然開朗的感覺!
看源碼小技巧:
1、只看自己看得懂的
2、屬性查找一定要縷清繼承關系,明確當前的self是誰的對象,然后從它開始查找
3、**kwargs,*args相關一般不看
