四、django rest_framework原始碼之頻率控制剖析
1. 緒言
許可權判定之後的下一個環節是訪問頻率控制,本篇我們分析訪問頻率控制部分原始碼。
2. 原始碼分析
訪問頻率控制在dispatch方法中的initial方法呼叫check_throttles方法開始。入口如下:
def check_throttles(self, request): for throttle in self.get_throttles():#遍歷每一個頻率控制物件 if not throttle.allow_request(request, self): self.throttled(request, throttle.wait())#wait方法返回還需要等待多少秒才可以訪問
get_throttles是獲取所有的頻率控制類的例項物件,原始碼如下:
def get_throttles(self): return [throttle() for throttle in self.throttle_classes]
獲取和例項化的方法都是通過列表生成式和讀取配置的頻率控制類,與認證、許可權如出一轍,這裡不再贅述。關鍵過程在執行例項化物件裡的方法,這裡以rest_framework自帶的SimpleRateThrottle類為例進行分析。check_throttles方法內的for迴圈開始後,首先獲取一個頻率控制例項,然後執行allow_request方法:
def allow_request(self, request, view): if self.rate is None:#如果配置中設定的頻率是None,就是不限制訪問頻率,直接返回True return True #get_cache_key的作用是從request中獲取訪問端標識(例如使用者名稱、IP) #這個方法必須被之類覆寫 self.key = self.get_cache_key(request, view) if self.key is None: return True#下面的cache是django自帶的快取 #從快取中取出訪問記錄(一個列表),如果找不到(沒有訪問過)就賦值為一個空列表 self.history = self.cache.get(self.key, []) self.now = self.timer()#獲取當前時間 #如果有訪問記錄,先刪除在訪問時間段之外的記錄 # 以3/m為例,時間段為1分鐘,那麼就是刪除一分鐘以前的記錄 while self.history and self.history[-1] <= self.now - self.duration: self.history.pop() #如果剩餘的訪問記錄數量多於訪問最大頻次(前一分鐘訪問次數超過3次) if len(self.history) >= self.num_requests: return self.throttle_failure()#不能再訪問了 return self.throttle_success()#繼續訪問吧
allow_request方法內的self.rate屬性是在構造方法中設定的,構造方法如下:
def __init__(self): if not getattr(self, 'rate', None):#如果沒有rate屬性 self.rate = self.get_rate()#獲得配置好的頻率,形如:'3/m' #對頻率(例如’3/m')進行解析,分割成頻次3,和時間間隔m self.num_requests, self.duration = self.parse_rate(self.rate)
get_rate方法:
def get_rate(self): 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:#如果設定了scope,就去配置中通過scope取出這個配置 #THROTTLE_RATES是在settings.py中的頻率控制配置項,是一個字典 return self.THROTTLE_RATES[self.scope]#返回配置好的頻率,形如:'3/m' except KeyError: msg = "No default throttle rate set for '%s' scope" % self.scope raise ImproperlyConfigured(msg)
parse_rate方法:
def parse_rate(self, rate): if rate is None: return (None, None) # 配置中設定的頻率格式為:’3/m' num, period = rate.split('/') num_requests = int(num) #獲取時間間隔,s為秒,m為分 duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] return (num_requests, duration)#返回一個tuple
那麼,此時allow_request的第一行程式碼就成功獲得了配置好的頻率。之所以要把get_rate和parse_rate方法原始碼貼出來,是因為方法裡面出現了scope屬性,這個屬性配置使用者配置我們的頻率,例如我們要配置一分鐘訪問三次,則在我們自定義的類中首先需要給scope一個字串值,例如scope=“xxx” , 然後在settings.py中進行如下配置:
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': { 'xxx': '3/m', }, }
另外,allow_request中呼叫了一個get_cache_key方法,該方法的作用是獲取訪問端的標識,這個方法必須被覆寫,否則會丟擲異常。
繼續貼出接受訪問和拒絕訪問時執行的方法原始碼:
def throttle_success(self): # 如果可以訪問,就將當前時間加入到快取中 self.history.insert(0, self.now) self.cache.set(self.key, self.history, self.duration) return True#返回True標識可以訪問
def throttle_failure(self): return False#返回False表示拒絕訪問
至於在allow_request方法中如何進行訪問判斷,在程式碼中有詳細註釋。
在退出allow_request方法後,如果被拒絕,最初被執行的check_throttles方法會呼叫一個wait方法,這個方法返回的是還有多少秒可以訪問。
3. 自定義頻率控制類
方法一:完全自己重新寫一個頻率控制類
import time VISIT_RECORD = {} #存放IP的資料庫 可以放在快取! class VisitThrattle(object): def __init__(self): self.history = None def allow_request(self, request, view): remote_addr = request._request.META.get('REMOTE_ADDR')#獲取IP ctime = time.time()#當前時間 if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr] = [ctime,] #表示第一次訪問 return True history = VISIT_RECORD.get(remote_addr) self.history = history while history and history[[-1] < ctime -60: history.pop() if len(history) < 3: history.insert(0, ctime) return True return False
方法二:繼承django rest_framework中的類
根據IP進行頻率控制:
class VisitThrottle(SimpleRateThrottle): #對匿名使用者的ip號通過時間做訪問頻率控制 scope = 'IPScope' def get_cache_key(self, request, view): #去快取裡取資料 return self.get_ident(request)#這是BaseThrottle中的方法
根據使用者名稱進行頻率控制:
class UserThrottle(SimpleRateThrottle): #對使用者的名字 通過時間做訪問頻率控制 scope = "userScope" def get_cache_key(self, request, view): return request.user.username
4. 頻率控制配置
在settings.py檔案中配置DEFAULT_THROTTLE_RATES,DEFAULT_THROTTLE_RATES裡面的鍵必須與頻率控制類裡面的scope的值一一對應。:
REST_FRAMEWORK = { …… 'DEFAULT_THROTTLE_RATES': { 'IPScope': '3/minute', 'userScope': '3/minute' } }
然後配置DEFAULT_THROTTLE_CLASSES,有全域性配置和區域性配置之分,全域性配置在settings.py檔案中進行,區域性配置在檢視配種進行,配製方法與認證類、許可權類的方法一直,這裡不再介紹。