Django Rest framework 之 節流
Django Rest framework 之 節流
- RESTful 規範
- django rest framework 之 認證(一)
- django rest framework 之 許可權(二)
- django rest framework 之 節流(三)
- django rest framework 之 版本(四)
- django rest framework 之 解析器(五)
- django rest framework 之 序列化(六)
- django rest framework 之 分頁(七)
- 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。
第三步:判斷當前列表中儲存的時間的個數,如果小於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', 表示一天訪問一次