1. 程式人生 > >四、django rest_framework原始碼之頻率控制剖析

四、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檔案中進行,區域性配置在檢視配種進行,配製方法與認證類、許可權類的方法一直,這裡不再介紹。