Django rest framework 限制訪問頻率(原始碼分析三)
阿新 • • 發佈:2018-11-09
當用發出請求時 首先執行dispatch函式,當執行當第二部時:
#2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制 self.initial(request, *args, **kwargs)
進入到initial方法:
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler.""" self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use.#2.1處理版本資訊 version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted #2.2處理認證資訊 self.perform_authentication(request)#2.3處理許可權資訊 self.check_permissions(request) #2.4對使用者的訪問頻率進行限制 self.check_throttles(request)
#2.4對使用者的訪問頻率進行限制 self.check_throttles(request)
限流的具體分析:
一、執行check_throttles方法
def check_throttles(self, request): """ Check if request should be throttled. Raises an appropriate exception if the request is throttled. """ #遍歷throttle物件列表 for throttle in self.get_throttles(): #根據allow_request()的返回值進行下一步操作,返回True的話不執行下面程式碼,標識不限流,返回False的話執行下面程式碼,還可以丟擲異常 if not throttle.allow_request(request, self): #返回False的話執行 self.throttled(request, throttle.wait())
二、執行allow_request方法
首先找到BaseThrottle類,有好多類繼承了該類,並且都有allow_request方法,至於執行那個類中的allow_request方法,取決於我們自定義的Throttle這個類繼承誰
class BaseThrottle(object): """ Rate throttling of requests. """ def allow_request(self, request, view): """ Return `True` if the request should be allowed, `False` otherwise. """ raise NotImplementedError('.allow_request() must be overridden') #獲取唯一標識,匿名使用者用ip地址,認證使用者用自己的user資訊 def get_ident(self, request): """ Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR if present and number of proxies is > 0. If not use all of HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR. """ xff = request.META.get('HTTP_X_FORWARDED_FOR') remote_addr = request.META.get('REMOTE_ADDR') num_proxies = api_settings.NUM_PROXIES if num_proxies is not None: if num_proxies == 0 or xff is None: return remote_addr addrs = xff.split(',') client_addr = addrs[-min(num_proxies, len(addrs))] return client_addr.strip() return ''.join(xff.split()) if xff else remote_addr def wait(self): """ Optionally, return a recommended number of seconds to wait before the next request. """ return None
三、具體分析以具有代表性的SimpleRateThrottle類分析
class SimpleRateThrottle(BaseThrottle): """ A simple cache implementation, that only requires `.get_cache_key()` to be overridden. The rate (requests / seconds) is set by a `throttle` attribute on the View class. The attribute is a string of the form 'number_of_requests/period'. Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') Previous request information used for throttling is stored in the cache. """ 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): #判斷是否有rate,實際上rate就是我們定義的scope的值 if not getattr(self, 'rate', None): #沒有呼叫get_rate()獲取 self.rate = self.get_rate() #num_requests代表具體的次數 duration代表具體的時間單位 self.num_requests, self.duration = self.parse_rate(self.rate) def get_cache_key(self, request, view): """ Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """ raise NotImplementedError('.get_cache_key() must be overridden') def get_rate(self): """ Determine the string representation of the allowed request rate. """ #判斷當前類中我們定義的scope是否有值,沒有則丟擲異常,告訴我們必須設定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: #有的話直接返回scope對應的值 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): """ Given the request rate string, return a two tuple of: <allowed number of requests>, <period of time in seconds> """ if rate is None: return (None, None) #rate實際就是我們自定義的scope的值 如10/m(代表10次每分鐘) #按'/'切分num代表次數,period代表時間 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): """ Implement the check to see if the request should be throttled. On success calls `throttle_success`. On failure calls `throttle_failure`. """ #如果沒有rate也就是scope沒有值 #如果沒有則表示不限流, if self.rate is None: return True #get_cache_key必須自己重寫,如果沒有則表示不限流 #key一般是唯一標示如使用者名稱密碼 或者登陸者的ip地址 self.key = self.get_cache_key(request, view) if self.key is None: return True #獲取當前key所對應的時間列表,也就是使用者每一次訪問的時間都放到該列表中 self.history = self.cache.get(self.key, []) #當前時間 self.now = self.timer() # Drop any requests from the history which have now passed the # throttle duration #當時間列表存在,並且當前時間已經超出了規定的時間 while self.history and self.history[-1] <= self.now - self.duration: #把最開始加進去的時間刪除 self.history.pop() #如果列表中的時間數大於規定執行的次數 if len(self.history) >= self.num_requests: #則return False return self.throttle_failure() #return True return self.throttle_success() def throttle_success(self): """ Inserts the current request's timestamp along with the key into the cache. """ self.history.insert(0, self.now) self.cache.set(self.key, self.history, self.duration) return True def throttle_failure(self): """ Called when a request to the API has failed due to throttling. """ return False def wait(self): """ Returns the recommended next request time in seconds. """ 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)
四、回到第一步,執行allow_request方法
def allow_request(self, request, view): """ Implement the check to see if the request should be throttled. On success calls `throttle_success`. On failure calls `throttle_failure`. """ #如果沒有rate也就是scope沒有值 #如果沒有則表示不限流, if self.rate is None: return True #get_cache_key必須自己重寫,如果沒有則表示不限流 #key一般是唯一標示如使用者名稱密碼 或者登陸者的ip地址 self.key = self.get_cache_key(request, view) if self.key is None: return True #獲取當前key所對應的時間列表,也就是使用者每一次訪問的時間都放到該列表中 self.history = self.cache.get(self.key, []) #當前時間 self.now = self.timer() # Drop any requests from the history which have now passed the # throttle duration #當時間列表存在,並且當前時間已經超出了規定的時間 while self.history and self.history[-1] <= self.now - self.duration: #把最開始加進去的時間刪除 self.history.pop() #如果列表中的時間數大於規定執行的次數 if len(self.history) >= self.num_requests: #則return False return self.throttle_failure() #return True return self.throttle_success()
五、根據allow_request方法的返回值確定具體執行,返回False的話執行
#根據allow_request()的返回值進行下一步操作,返回True的話不執行下面程式碼,標識不限流,返回False的話執行下面程式碼,還可以丟擲異常 if not throttle.allow_request(request, self): #返回False的話執行 self.throttled(request, throttle.wait())
def throttled(self, request, wait): """ If request is throttled, determine what kind of exception to raise. """ raise exceptions.Throttled(wait)
class Throttled(APIException): status_code = status.HTTP_429_TOO_MANY_REQUESTS default_detail = _('Request was throttled.') extra_detail_singular = 'Expected available in {wait} second.' extra_detail_plural = 'Expected available in {wait} seconds.' default_code = 'throttled' def __init__(self, wait=None, detail=None, code=None): if detail is None: detail = force_text(self.default_detail) if wait is not None: wait = math.ceil(wait) detail = ' '.join(( detail, force_text(ungettext(self.extra_detail_singular.format(wait=wait), self.extra_detail_plural.format(wait=wait), wait)))) self.wait = wait super(Throttled, self).__init__(detail, code)
例子:繼承BaseThrottle
url(r'^limit/', app03_views.Limitview.as_view()),
import time from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.throttling import BaseThrottle from rest_framework import exceptions # Create your views here. AllOW={} class MyThrottle(BaseThrottle): ''' 要求每分鐘只能訪問十次 ''' ip = '1.1.1.1' def allow_request(self, request, view): ctime=time.time() ip=self.ip if ip not in AllOW: AllOW[ip]=[ctime,] else: time_list=AllOW[ip] while True: if ctime-60>time_list[-1]: time_list.pop() else : break if len(AllOW[ip])>10: return False AllOW[ip].insert(0,ctime) return True def wait(self): ip=self.ip ctime=time.time() first_in_time=AllOW[ip][-1] wt=60-(ctime-first_in_time) return wt class Limitview(APIView): throttle_classes=[MyThrottle,] def get(self,reques): self.dispatch() return Response('歡迎訪問') def throttled(self,request,wait): class InnerThrottled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = 'Expected available in {wait} second.' extra_detail_plural = '還需要再等待{wait}秒' raise InnerThrottled(wait)Views
REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, "DEFAULT_AUTHENTICATION_CLASSES": [ # "app01.utils.MyAuthentication", "app02.utils.MyAuthentication", ], "DEFAULT_PERMISSION_CLASSES":[ "app02.utils.MyPermission", "app02.utils.AdminPermission", ], }settings
seetings中沒有配置跟Throttle有的的資訊
繼承SimpleRateThrottle
url(r'^limit/', app03_views.Limitview.as_view()),urls
import time from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.throttling import BaseThrottle from rest_framework.throttling import SimpleRateThrottle from rest_framework import exceptions # Create your views here. AllOW={} class MyThrottle(BaseThrottle): ''' 要求每分鐘只能訪問十次 ''' ip = '1.1.1.1' def allow_request(self, request, view): ctime=time.time() ip=self.ip if ip not in AllOW: AllOW[ip]=[ctime,] else: time_list=AllOW[ip] while True: if ctime-60>time_list[-1]: time_list.pop() else : break if len(AllOW[ip])>10: return False AllOW[ip].insert(0,ctime) return True def wait(self): ip=self.ip ctime=time.time() first_in_time=AllOW[ip][-1] wt=60-(ctime-first_in_time) return wt class MySimpleRateThrottle(SimpleRateThrottle): #必須要寫因為SimpleRateThrottle中要求了必須寫 ''' def get_rate(self): """ Determine the string representation of the allowed request rate. """ #判斷當前類中我們定義的scope是否有值,沒有則丟擲異常,告訴我們必須設定scope if not getattr(self, 'scope', None): msg = ("You must set either `.scope` or `.rate` for '%s' throttle" % self.__class__.__name__) raise ImproperlyConfigured(msg) ''' scope = 'tiga' #也必須必須要寫 ''' def get_cache_key(self, request, view): """ Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """ raise NotImplementedError('.get_cache_key() must be overridden') ''' def get_cache_key(self, request, view): return self.get_ident(request) class Limitview(APIView): throttle_classes=[MySimpleRateThrottle,] def get(self,reques): return Response('歡迎訪問') def throttled(self,request,wait): class InnerThrottled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = 'Expected available in {wait} second.' extra_detail_plural = '還需要再等待{wait}秒' raise InnerThrottled(wait)Views
from django.db import models # Create your models here. class Userinfo(models.Model): name=models.CharField(max_length=32,verbose_name='使用者名稱') pwd=models.CharField(max_length=32,verbose_name='密碼') token=models.CharField(max_length=64,null=True) def __str__(self): return self.nameView Code
REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, "DEFAULT_AUTHENTICATION_CLASSES": [ # "app01.utils.MyAuthentication", "app02.utils.MyAuthentication", ], "DEFAULT_PERMISSION_CLASSES":[ "app02.utils.MyPermission", "app02.utils.AdminPermission", ], "DEFAULT_THROTTLE_RATES":{ 'tiga':'10/m', } }settings
認證,許可權,限流綜合聯絡
url(r'^index/', app04_views.IndexView.as_view()), url(r'^user/', app04_views.UserView.as_view()), url(r'^manage/', app04_views.ManageView.as_view()),
from django.shortcuts import render from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.authentication import BaseAuthentication from rest_framework.permissions import BasePermission from rest_framework.throttling import SimpleRateThrottle from rest_framework import exceptions from app01 import models # Create your views here. class MyAuthentication(BaseAuthentication): def authenticate(self, request): token=request.query_params.get('token') user=models.Userinfo.objects.filter(token=token).first() if user: return (user.name,user) return None class UserPermission(BasePermission): message='登入使用者才可以訪問' def has_permission(self, request, view): if request.user: return True return False class AdminPermission(BasePermission): message='管理員才能訪問' def has_permission(self, request, view): if request.user =='ctz': return True return False class AnnoThrottle(SimpleRateThrottle): scope = 'anno' def get_cache_key(self, request, view): #如果是匿名使用者則執行 if not request.user: return self.get_ident(request) #如果不是匿名使用者則讓他執行 return None class UserThrottle(SimpleRateThrottle): scope = 'user' def get_cache_key(self, request, view): #當前使用者登陸了,並且當前使用者不是管理員 if request.user and request.user!='ctz': return self.get_ident(request) #如果是匿名使用者和管理員 則讓他繼續執行 return None class AdminThrottle(SimpleRateThrottle): scope = 'admin' def get_cache_key(self, request, view): #如果是管理員 if request.user=='ctz': return self.get_ident(request) #不是管理員 return None class IndexView(APIView): ''' 要求,所有使用者都能訪問,匿名使用者5/m,普通使用者10/m,管理員不限 ''' authentication_classes = [MyAuthentication,] permission_classes = [] throttle_classes = [AnnoThrottle,UserThrottle,AdminThrottle] def get(self,request): return Response('首頁') def throttled(self, request, wait): class UserInnerThrottled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = 'Expected available in {wait} second.' extra_detail_plural = '還需要再等待{wait}秒' raise UserInnerThrottled(wait) class UserView(APIView): ''' 要求:登入使用者能訪問,普通使用者10/m,管理員20/m ''' authentication_classes = [MyAuthentication,] permission_classes = [UserPermission,] throttle_classes = [UserThrottle,AdminThrottle] def get(self,request): return Response('使用者介面') def permission_denied(self, request, message=None): """ If request is not permitted, determine what kind of exception to raise. """ if request.authenticators and not request.successful_authenticator: raise exceptions.NotAuthenticated('無權訪問') raise exceptions.PermissionDenied(detail=message) def throttled(self, request, wait): class UserInnerThrottled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = 'Expected available in {wait} second.' extra_detail_plural = '還需要再等待{wait}秒' raise UserInnerThrottled(wait) class ManageView(APIView): ''' 要求:只有管理園能訪問,5/m ''' authentication_classes = [MyAuthentication,] permission_classes = [AdminPermission,] throttle_classes = [AdminThrottle] def get(self,request): return Response('管理員介面') def permission_denied(self, request, message=None): """ If request is not permitted, determine what kind of exception to raise. """ if request.authenticators and not request.successful_authenticator: raise exceptions.NotAuthenticated('無權訪問') raise exceptions.PermissionDenied(detail=message) def throttled(self, request, wait): class UserInnerThrottled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = 'Expected available in {wait} second.' extra_detail_plural = '還需要再等待{wait}秒' raise UserInnerThrottled(wait)
from django.db import models # Create your models here. class Userinfo(models.Model): name=models.CharField(max_length=32,verbose_name='使用者名稱') pwd=models.CharField(max_length=32,verbose_name='密碼') token=models.CharField(max_length=64,null=True) def __str__(self): return self.name
REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, "DEFAULT_AUTHENTICATION_CLASSES": [ # "app01.utils.MyAuthentication", "app02.utils.MyAuthentication", ], "DEFAULT_PERMISSION_CLASSES":[ "app02.utils.MyPermission", "app02.utils.AdminPermission", ], "DEFAULT_THROTTLE_RATES":{ 'tiga':'10/m', 'anno':'5/m', 'user':'10/m', 'admin':'20/m', } }
當用發出請求時 首先執行dispatch函式,當執行當第二部時:
#2.處理版本資訊 處理認證資訊 處理許可權資訊 對使用者的訪問頻率進行限制 self.initial(request, *args, **kwargs)
進入到initial方法:
def initial(self, request, *args, **kwargs): """ Runs anything that needs to occur prior to calling the method handler. """ self.format_kwarg = self.get_format_suffix(**kwargs) # Perform content negotiation and store the accepted info on the request neg = self.perform_content_negotiation(request) request.accepted_renderer, request.accepted_media_type = neg # Determine the API version, if versioning is in use. #2.1處理版本資訊 version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted #2.2處理認證資訊 self.perform_authentication(request) #2.3處理許可權資訊 self.check_permissions(request) #2.4對使用者的訪問頻率進行限制 self.check_throttles(request)
#2.4對使用者的訪問頻率進行限制 self.check_throttles(request)
限流的具體分析:
一、執行check_throttles方法
def check_throttles(self, request): """ Check if request should be throttled. Raises an appropriate exception if the request is throttled. """ #遍歷throttle物件列表 for throttle in self.get_throttles(): #根據allow_request()的返回值進行下一步操作,返回True的話不執行下面程式碼,標識不限流,返回False的話執行下面程式碼,還可以丟擲異常 if not throttle.allow_request(request, self): #返回False的話執行 self.throttled(request, throttle.wait())
二、執行allow_request方法
首先找到BaseThrottle類,有好多類繼承了該類,並且都有allow_request方法,至於執行那個類中的allow_request方法,取決於我們自定義的Throttle這個類繼承誰
class BaseThrottle(object): """ Rate throttling of requests. """ def allow_request(self, request, view): """ Return `True` if the request should be allowed, `False` otherwise. """ raise NotImplementedError('.allow_request() must be overridden') #獲取唯一標識,匿名使用者用ip地址,認證使用者用自己的user資訊 def get_ident(self, request): """ Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR if present and number of proxies is > 0. If not use all of HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR. """ xff = request.META.get('HTTP_X_FORWARDED_FOR') remote_addr = request.META.get('REMOTE_ADDR') num_proxies = api_settings.NUM_PROXIES if num_proxies is not None: if num_proxies == 0 or xff is None: return remote_addr addrs = xff.split(',') client_addr = addrs[-min(num_proxies, len(addrs))] return client_addr.strip() return ''.join(xff.split()) if xff else remote_addr def wait(self): """ Optionally, return a recommended number of seconds to wait before the next request. """ return None
三、具體分析以具有代表性的SimpleRateThrottle類分析
class SimpleRateThrottle(BaseThrottle): """ A simple cache implementation, that only requires `.get_cache_key()` to be overridden. The rate (requests / seconds) is set by a `throttle` attribute on the View class. The attribute is a string of the form 'number_of_requests/period'. Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') Previous request information used for throttling is stored in the cache. """ 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): #判斷是否有rate,實際上rate就是我們定義的scope的值 if not getattr(self, 'rate', None): #沒有呼叫get_rate()獲取 self.rate = self.get_rate() #num_requests代表具體的次數 duration代表具體的時間單位 self.num_requests, self.duration = self.parse_rate(self.rate) def get_cache_key(self, request, view): """ Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """ raise NotImplementedError('.get_cache_key() must be overridden') def get_rate(self): """ Determine the string representation of the allowed request rate. """ #判斷當前類中我們定義的scope是否有值,沒有則丟擲異常,告訴我們必須設定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: #有的話直接返回scope對應的值 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): """ Given the request rate string, return a two tuple of: <allowed number of requests>, <period of time in seconds> """ if rate is None: return (None, None) #rate實際就是我們自定義的scope的值 如10/m(代表10次每分鐘) #按'/'切分num代表次數,period代表時間 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): """ Implement the check to see if the request should be throttled. On success calls `throttle_success`. On failure calls `throttle_failure`. """ #如果沒有rate也就是scope沒有值 #如果沒有則表示不限流, if self.rate is None: return True #get_cache_key必須自己重寫,如果沒有則表示不限流 #key一般是唯一標示如使用者名稱密碼 或者登陸者的ip地址 self.key = self.get_cache_key(request, view) if self.key is None: return True #獲取當前key所對應的時間列表,也就是使用者每一次訪問的時間都放到該列表中 self.history = self.cache.get(self.key, []) #當前時間 self.now = self.timer() # Drop any requests from the history which have now passed the # throttle duration #當時間列表存在,並且當前時間已經超出了規定的時間 while self.history and self.history[-1] <= self.now - self.duration: #把最開始加進去的時間刪除 self.history.pop() #如果列表中的時間數大於規定執行的次數 if len(self.history) >= self.num_requests: #則return False return self.throttle_failure() #return True return self.throttle_success() def throttle_success(self): """ Inserts the current request's timestamp along with the key into the cache. """ self.history.insert(0, self.now) self.cache.set(self.key, self.history, self.duration) return True def throttle_failure(self): """ Called when a request to the API has failed due to throttling. """ return False def wait(self): """ Returns the recommended next request time in seconds. """ 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