Django Rest framework 之 節流


一、節流

1、簡介

節流又叫限流,限制訪問。就是通常一個用戶在多次請求一個頁面,或者點擊一個鏈接的時候,前幾次點擊是沒有問題的,但是一旦連續幾次之后,就會出現訪問受限,離下一次訪問還有50秒等的字樣,在django rest framework 中有一個專門的組件來做限制訪問。

2、思路

一旦一個用戶向資源發送請求,那么根據用戶的身份就有兩種情況,匿名用戶和認證用戶。那么根據用戶的身份怎么做限制訪問?就是要找到用戶的唯一標識。

  • 匿名用戶:對於匿名用戶,唯一能用來標識的只有請求的IP
  • 認證用戶:認證用戶的用戶名,或者用戶ID等。

用戶標識的問題解決了,假設設置的是每分鍾只能訪問5次,也就是5次/min。當用戶發送請求,可以拿到用戶的唯一標識,判斷用戶是第幾次訪問。有下面幾種情況:

  • 第一到五次:這是可以通過的,返回資源。
    一分鍾之內
    • 第六次:請求被禁止,並返回提示信息。
      一分鍾之后
    • 第六次:請求別允許,返回資源。

根據上面的情況可以得出以下思路:
當一個用戶發送請求的時候,我可以在緩存(django rest framework就是這么做的)中生成一個字典,字典的鍵值對分別是用戶的唯一標識和用戶的訪問時間,例如下面:

VISIT_RECORD = {
            'weilan': [127,125, 121,110,89,68]  # 標識第一次訪問時間是68秒,第二次訪問時間是89秒,第三次訪問時間是110秒
}

第一步:當一個用戶第一次發送請求的時候,緩存VISIT_RECORD中沒有他的鍵,就會添加一個鍵是他的表示,值是一個列表,列表中存放他的第一次訪問時間為t1。
第二步:當再次發送請求的時候,會先在緩存VISIT_RECORD中找有沒有他的鍵,如果沒有,會返回第一步。如果有,取出列表,查看列表中的最后一次訪問值T1,並與本次訪問時間Tn比較,如果Tn-T1>60s,則將T1刪除,如果Tn-T1<60s,則保留T1,因為要保證一分鍾之內的訪問次數。
第三步:判斷當前列表中保存的時間的個數,如果小於5個,說明一分鍾之內還沒有訪問5次,將但訪問時間Tn插入到列表頭。如果個數超過5個,則說明一分鍾已經訪問過5次,本次訪問已經是第6次,則不插人列表。

這樣根據思路就可以寫出下面限流類:

import time
VISIT_RECORD = {}  # 這里再內存中生成,可以寫入緩存
class VisitThrottle(BaseThrottle):

    def __init__(self):
        self.history = None

    def allow_request(self,request,view):
        # 1. 獲取用戶IP 或者認證用戶的用戶名
        remote_addr = self.get_ident(request)

        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) < 5:
            history.insert(0,ctime)
            return True

        # return True    # 表示可以繼續訪問
        # return False # 表示訪問頻率太高,被限制

    def wait(self):
        # 還需要等多少秒才能訪問
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

二、示例

1、目錄結構

同樣的我們向認證,權限那樣再utils包中定義限流組件

2、具體限制訪問

對於匿名用戶和認證用戶做不同的限制訪問

from rest_framework.throttling import BaseThrottle,SimpleRateThrottle


class VisitThrottle(SimpleRateThrottle):
    scope = "anonymous"

    def get_cache_key(self, request, view):
        return self.get_ident(request)


class UserThrottle(SimpleRateThrottle):
    scope = "user"

    def get_cache_key(self, request, view):
        return request.user.username

3、配置限流類

可以再setting.py文件中全局配置,也可以再視圖中重寫,局部配置,但是訪問頻率,需要限流類的scope屬性定義。
對於匿名用戶,每分鍾訪問5次,認證用戶,每分鍾5次

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES":["api.utils.throttle.UserThrottle"],
    "DEFAULT_THROTTLE_RATES":{
        "anonymous":'5/m',
        "user":'10/m',
    }
}

4、視圖

from rest_framework.views import APIView

class UserInfoView(APIView):
    authentication_classes = []
    permission_classes = []
    throttle_classes = [throttle.VisitThrottle]  # 標識匿名用戶訪問

    def get(self, request, *args, **kwargs):
        print(request.META.get('REMOTE_ADDR'))  # 這里可以獲取到訪問的IP
        return HttpResponse('訪問成功')

5、請求測試

使用postman或者瀏覽器發送請求
一分鍾連續發送5次,正常

發送第6次時,訪問受限

三、源碼分析

django rest framework 之 認證一樣進入,request的請求流程,進入源碼查看具體權限的操作

1、進入dispath()方法

2、進入initial()方法

3、進入check_throttles()方法

4、獲取限流類

獲取限流類之后並實例化成對象,使得可以調用具體的方法

同樣的默認的是通過全局配置

5、原生的限流類

rest framework中也有相應的限流類,主要使用SimpleRateThrottle,因為在SimpleRateThrottle中的一些方法已經是實現了我們需要的邏輯

來看一下SimpleRateThrottle具體做了什么

class SimpleRateThrottle(BaseThrottle):
    cache = default_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()
        self.num_requests, self.duration = self.parse_rate(self.rate)

    def get_cache_key(self, request, view):  # 需要返回請求發情用戶的唯一標識

        raise NotImplementedError('.get_cache_key() must be overridden')

    def get_rate(self):  # scope屬性需要在節流類和配置文件中定義,才能達到節流的效果

        if not getattr(self, 'scope', None):
            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):  # 解析配置文件中的時間等
        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):  # 這與上面節流的操作相似,是具體的邏輯
        if self.rate is None:
            return True

        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True

        self.history = self.cache.get(self.key, [])
        self.now = self.timer()

        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):
        self.history.insert(0, self.now)
        self.cache.set(self.key, self.history, self.duration)
        return True

    def throttle_failure(self):   # 請求失敗的時候
        return False

    def wait(self):  # 返回等待時間
        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)

以上就是節流的流程和源碼分析

四、總結

節流同樣可以通過全局配置和局部配置的方法,影響視圖。

值得注意的是,有一個必須要重寫的接口get_cache_key()

  • 當匿名用戶的時候,返回值是匿名用戶的IP
  • 當為認證用戶的時候,可以是用戶的任何唯一標識。
    因為在VISIT_RECORD中的鍵是唯一的。

scope定義了具體一個節流類怎么節流,在setting.py文件和節流類中都需要定義。SimpleRateThrottle中的parse_rate()方法對scope進行了解析

  • "user":'1/s', 表示一秒訪問一次
  • "user":'1/m', 表示一分鍾訪問一次
  • "user":'1/h', 表示一小時訪問一次
  • "user":'1/d', 表示一天訪問一次


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM