節流又叫限流,限制訪問。就是一般一個用戶在屢次請求一個頁面,或者點擊一個連接的時候,前幾回點擊是沒有問題的,可是一旦連續幾回以後,就會出現訪問受限,離下一次訪問還有50秒等的字樣,在django rest framework
中有一個專門的組件來作限制訪問。html
一旦一個用戶向資源發送請求,那麼根據用戶的身份就有兩種狀況,匿名用戶和認證用戶。那麼根據用戶的身份怎麼作限制訪問?就是要找到用戶的惟一標識。django
用戶標識的問題解決了,假設設置的是每分鐘只能訪問5次,也就是5次/min。當用戶發送請求,能夠拿到用戶的惟一標識,判斷用戶是第幾回訪問。有下面幾種狀況:api
根據上面的狀況能夠得出如下思路:
當一個用戶發送請求的時候,我能夠在緩存(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次,則不插人列表。緩存
這樣根據思路就能夠寫出下面限流類:ide
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])
一樣的咱們向認證,權限那樣再utils包中定義限流組件
源碼分析
對於匿名用戶和認證用戶作不一樣的限制訪問post
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
能夠再setting.py
文件中全局配置,也能夠再視圖中重寫,局部配置,可是訪問頻率,須要限流類的scope
屬性定義。
對於匿名用戶,每分鐘訪問5次,認證用戶,每分鐘5次測試
REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES":["api.utils.throttle.UserThrottle"], "DEFAULT_THROTTLE_RATES":{ "anonymous":'5/m', "user":'10/m', } }
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('訪問成功')
使用postman或者瀏覽器發送請求
一分鐘連續發送5次,正常
發送第6次時,訪問受限
spa
像django rest framework
之 認證同樣進入,request的請求流程,進入源碼查看具體權限的操做
獲取限流類以後並實例化成對象,使得能夠調用具體的方法
一樣的默認的是經過全局配置
在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()
VISIT_RECORD
中的鍵是惟一的。scope
定義了具體一個節流類怎麼節流,在setting.py
文件和節流類中都須要定義。SimpleRateThrottle
中的parse_rate()
方法對scope
進行了解析