微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Django Rest Framework源码剖析(三)-----频率控制

<table style="height: 30px; background-color: #afeeee; width: 1266px; ; width: 1266px;" border="0"><tr>
<td><span style="font-size: 16px;">一、简介</td>
</tr></table>

承接上篇文章,当服务的接口被频繁调用,导致资源紧张怎么办呢?当然或许有很多解决办法,比如:负载均衡、提高服务器配置、通过代理限制访问频率等,但是django rest framework自身就提供了访问频率的控制,可以从代码本身做控制。

django rest framework 中频率控制基本原理基于访问次数和时间,通过计算实现,当然我们也可以自己定义频率控制方法。基本原理如下:

启用频率,DRF内部会有一个字典记录来访者的IP,以及访问时间最近几(通过配置)次的访问时间,这样确保每次列表中最后一个元素都是该用户请求的最早时间,形式如下:

举例说明,比如我现在配置了5秒内只能访问2次,每次请求到达频率控制时候先判断请求者IP是否已经在这个请求字典中,若存在,在判断用户请求5秒内的请求次数,若次数小于等于2,则允许请求,若大于2,则超过频率,不允许请求。

关于请求频率的的算法(以5秒内最多访问两次为例):

1.首先删除掉列表里5秒之前的请求,循环判断当前请求时间和最早请求时间之差记作t1,若t1大于5则代表列表中最早的请求已经在5秒外了,删除掉,继续判断倒数第二个请求,直到t1小于5.

2.当确保请求列表中只有5秒内请求时候,接着判断其请求次数(列表长度),若长度大于2,则证明超过5秒内访问超过2次了,则不允许,否则,通过并将此次访问时间插入到列表最前面,作为最新访问时间。

同样,先来了解下频率控制的使用方法,后面在分析源码

1.在utils目录下新建立文件,throttle.py,添加频率控制为每分钟只能访问5次

python3 rest_framework.throttling ottle

<span style="color: #0000ff;">class<span style="color: #000000;"> VisitThrottle(SimpleRateThrottle):
<span style="color: #800000;">"""<span style="color: #800000;">5秒内最多访问三次<span style="color: #800000;">"""<span style="color: #000000;">
scope = <span style="color: #800000;">"<span style="color: #800000;">WD<span style="color: #800000;">" <span style="color: #008000;">#<span style="color: #008000;">settings配置文件中的key,用于获取配置的频率

<span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; get_cache_key(self,request,view):
    </span><span style="color: #0000ff;"&gt;return</span> self.get_ident(request)</pre>

2.settings.py中配置全局频率控制

REST_FRAMEWORK = ottLE_CLASSES:[ottle.VisitThrottle], ottLE_RATES:,cope定义的值,

urls.py

django.conf.urls django.contrib app01 urlpatterns =<span style="color: #000000;"> [

url(r</span><span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;^api/v1/auth</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;,views.AuthView.as_view()),url(r</span><span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;^api/v1/order</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;,views.OrderView.as_view()),]</span></pre>

models.py

django.db <span style="color: #0000ff;">class<span style="color: #000000;"> UserInfo(models.Model):
user_type_choice
=<span style="color: #000000;"> (
(1,<span style="color: #800000;">"<span style="color: #800000;">普通用户<span style="color: #800000;">"<span style="color: #000000;">),(2,<span style="color: #800000;">"<span style="color: #800000;">会员<span style="color: #800000;">"<span style="color: #000000;">),)
user_type = models.IntegerField(choices=<span style="color: #000000;">user_type_choice)
username = models.CharField(max_length=32,unique=<span style="color: #000000;">True)
password = models.CharField(max_length=64<span style="color: #000000;">)

<span style="color: #0000ff;">class<span style="color: #000000;"> UserToken(models.Model):
user = models.OnetoOneField(to=<span style="color: #000000;">UserInfo)
token = models.CharField(max_length=64)

订单视图

utils.permissions = [Authentication,] 添加认证 permission_classes = [MyPremission,] 添加权限控制 get(self,*args,**kwargs): ret = {:1000,:,:一个mac JsonResponse(ret,safe=True)

使用postman验证如下图,可以看到频率限制已经起作用了。

四、频率控制源码剖析在前面几篇文章中已经分析了DRF的认证、权限源码,频率控制也一样也从APIView的dispatch方法说起,参考注解:

dispatch()

dispatch(self,**kwargs): dispatch()` is pretty much the same as Django's regular dispatch,but with extra hooks for startup,finalize,and exception handling. =kwargs =kwargs 功能 获取原生request,request._request 获取认证类的对象,request.authticators request = self.initialize_request(request,**kwargs) self.request == self.default_response_headers
    <span style="color: #0000ff;"&gt;try</span><span style="color: #000000;"&gt;:
        self.initial(request,</span>*args,**<span style="color: #000000;"&gt;<a href="/tag/kwargs/" target="_blank" class="keywords">kwargs</a>)

        </span><span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; Get the appropriate handler method</span>
        <span style="color: #0000ff;"&gt;if</span> request.method.lower() <span style="color: #0000ff;"&gt;in</span><span style="color: #000000;"&gt; self.http_method_names:
            handler </span>=<span style="color: #000000;"&gt; getattr(self,request.method.lower(),self.http_method_not_allowed)
        </span><span style="color: #0000ff;"&gt;else</span><span style="color: #000000;"&gt;:
            handler </span>=<span style="color: #000000;"&gt; self.http_method_not_allowed

        response </span>= handler(request,**<span style="color: #000000;"&gt;<a href="/tag/kwargs/" target="_blank" class="keywords">kwargs</a>)

    </span><span style="color: #0000ff;"&gt;except</span><span style="color: #000000;"&gt; Exception as exc:
        response </span>=<span style="color: #000000;"&gt; self.handle_exception(exc)

    self.response </span>= self.finalize_response(request,response,**<span style="color: #000000;"&gt;<a href="/tag/kwargs/" target="_blank" class="keywords">kwargs</a>)
    </span><span style="color: #0000ff;"&gt;return</span> self.response</pre>

2.执行inital方法,initial方法中执行check_throttles则开始频率控制

initial(self,**kwargs): = self.get_format_suffix(**kwargs)
    </span><span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; Perform content negotiation and store the accepted info on the request</span>
    neg =<span style="color: #000000;"&gt; self.perform_content_negotiation(request)
    request.accepted_renderer,request.accepted_media_type </span>=<span style="color: #000000;"&gt; neg

    </span><span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; Deter<a href="/tag/mine/" target="_blank" class="keywords">mine</a> the API version,if versioning is in use.</span>
    version,scheme = self.deter<a href="/tag/mine/" target="_blank" class="keywords">mine</a>_version(request,**<span style="color: #000000;"&gt;<a href="/tag/kwargs/" target="_blank" class="keywords">kwargs</a>)
    request.version,request.versioning_scheme </span>=<span style="color: #000000;"&gt; version,scheme

    </span><span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; Ensure that the incoming request is permitted</span>
    <span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt;2.实现认证</span>

<span style="color: #000000;"> self.perform_authentication(request)
<span style="color: #008000;">#<span style="color: #008000;">3.权限判断
<span style="color: #000000;"> self.check_permissions(request)
<span style="color: #008000;">#<span style="color: #008000;">4.频率限制
self.check_throttles(request)

3.下面是check_throttles源码,与认证、权限一样采用列表对象方式,通过判断allow_request方法返回值判断频率是否通过

ottles(self,request): ottled. Raises an appropriate exception if the request is throttled. throttle self.get_throttles(): throttle.allow_request(request,self): self.throttled(request,throttle.wait())

4.get_throttles方法,采用列表生成生成频率控制对象,与认证、权限一直

ottles(self): ottles that this view uses. [throttle() throttle self.throttle_classes] 生成式生成控制频率对象列表

5.self.throttle_classes属性获取

</span><span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; The following policies may be set at either globally,or per-view.</span> renderer_classes =<span style="color: #000000;"&gt; api_settings.DEFAULT_RENDERER_CLASSES parser_classes </span>=<span style="color: #000000;"&gt; api_settings.DEFAULT_PARSER_CLASSES authentication_classes </span>=<span style="color: #000000;"&gt; api_settings.DEFAULT_AUTHENTICATION_CLASSES

<span style="color: #ff6600;"> throttle_classes <span style="color: #ff6600;">=<span style="color: #000000;"><span style="color: #ff6600;"> api_settings.DEFAULT_THRottLE_CLASSES #频率控制全局配置
permission_classes =<span style="color: #000000;"> api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class =<span style="color: #000000;"> api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
Metadata_class =<span style="color: #000000;"> apisettings.DEFAULTMetaDATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

6.通过以上分析,知道了频率控制是通过判断每个类中的allow_request放法的返回值来判断频率是否通过,下面我们来看看我们所使用的SimpleRateThrottle怎么实现的,分析部分请看注解:

SimpleRateThrottle类源码:

ottle(BaseThrottle): The rate (requests / seconds) is set by a `rate` 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') Prev<a href="/tag/IoU/" target="_blank" class="keywords">IoU</a>s request @R_506_<a href="/tag/404/" target="_blank" class="keywords">404</a>5@ion used for thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>ling is stored in the cache. </span><span style="color: #800000;"&gt;"""</span><span style="color: #000000;"&gt; cache </span>=<span style="color: #000000;"&gt; default_cache <span style="color: #ff9900;"&gt;# 存放请求时间,类似与示例中的大字典,这里使用的是django的缓存</span> timer </span>=<span style="color: #000000;"&gt; time.time cache_format </span>= <span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_%(s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e)s_%(ident)s</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt; s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e </span>=<span style="color: #000000;"&gt; None THR<a href="/tag/ott/" target="_blank" class="keywords">ott</a>LE_RATES </span>=<span style="color: #000000;"&gt; api_settings.DEFAULT_THR<a href="/tag/ott/" target="_blank" class="keywords">ott</a>LE_RATES </span><span style="color: #0000ff;"&gt;def</span> <span style="color: #800080;"&gt;<a href="/tag/init/" target="_blank" class="keywords">__init__</a></span><span style="color: #000000;"&gt;(self): </span><span style="color: #0000ff;"&gt;if</span> <span style="color: #0000ff;"&gt;not</span> getattr(self,<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;rate</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;,None): self.rate </span>=<span style="color: #000000;"&gt; self.get_rate() self.num_requests,self.duration </span>=<span style="color: #000000;"&gt; self.parse_rate(self.rate) </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; get_cache_key(self,view):<span style="color: #ff9900;"&gt; # <a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>请求的key标识,必须要有否则会报错,这里可以重写,使用<a href="/tag/yonghu/" target="_blank" class="keywords">用户</a>的<a href="/tag/yonghuming/" target="_blank" class="keywords">用户名</a>、或其他作为key,在示例中使用的get_ident<a href="/tag/fangfa/" target="_blank" class="keywords">方法</a><a href="/tag/yonghu/" target="_blank" class="keywords">用户</a><a href="/tag/huoqu/" target="_blank" class="keywords">获取</a><a href="/tag/yonghu/" target="_blank" class="keywords">用户</a>IP作为key </span></span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Should return a unique cache-key which can be used for thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>ling. Must be overridden. May return `None` if the request should not be thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>led. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;raise</span> NotImplementedError(<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;.get_cache_key() must be overridden</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;) </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; get_rate(self): <span style="color: #ff9900;"&gt;# <a href="/tag/huoqu/" target="_blank" class="keywords">获取</a><a href="/tag/peizhiwenjian/" target="_blank" class="keywords">配置文件</a>的配置速率 </span></span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Deter<a href="/tag/mine/" target="_blank" class="keywords">mine</a> the string representation of the allowed request rate. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;if</span> <span style="color: #0000ff;"&gt;not</span> getattr(self,<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;,None): <span style="color: #ff9900;"&gt;# 通过<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>共有<a href="/tag/shuxing/" target="_blank" class="keywords">属性</a>s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e来<a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>配置的速率</span> msg </span>= (<span style="color: #800000;"&gt;"</span><span style="color: #800000;"&gt;You must set either `.s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e` or `.rate` for '<a href="/tag/s/" target="_blank" class="keywords">%s</a>' thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le</span><span style="color: #800000;"&gt;"</span> %<span style="color: #000000;"&gt; self.</span><span style="color: #800080;"&gt;__class__</span>.<span style="color: #800080;"&gt;<a href="/tag/name/" target="_blank" class="keywords">__name__</a></span><span style="color: #000000;"&gt;) </span><span style="color: #0000ff;"&gt;raise</span><span style="color: #000000;"&gt; ImproperlyCon<a href="/tag/fig/" target="_blank" class="keywords">fig</a>ured(msg) </span><span style="color: #0000ff;"&gt;try</span><span style="color: #000000;"&gt;: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; self.THR<a href="/tag/ott/" target="_blank" class="keywords">ott</a>LE_RATES[self.s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e] </span><span style="color: #0000ff;"&gt;except</span><span style="color: #000000;"&gt; KeyError: msg </span>= <span style="color: #800000;"&gt;"</span><span style="color: #800000;"&gt;No default thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le rate set for '<a href="/tag/s/" target="_blank" class="keywords">%s</a>' s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e</span><span style="color: #800000;"&gt;"</span> %<span style="color: #000000;"&gt; self.s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e </span><span style="color: #0000ff;"&gt;raise</span><span style="color: #000000;"&gt; ImproperlyCon<a href="/tag/fig/" target="_blank" class="keywords">fig</a>ured(msg) </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; parse_rate(self,rate): <span style="color: #ff9900;"&gt;# 格式化速率 </span></span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Given the request rate string,return a two tuple of: <allowed number of requests>,<period of time in seconds> </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;if</span> rate <span style="color: #0000ff;"&gt;is</span><span style="color: #000000;"&gt; None: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; (None,None) num,period </span>= rate.split(<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;/</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;) <span style="color: #ff9900;"&gt;# 分离字符串</span> num_requests </span>=<span style="color: #000000;"&gt; int(num) duration </span>= {<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;s</span><span style="color: #800000;"&gt;'</span>: 1,<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;m</span><span style="color: #800000;"&gt;'</span>: 60,<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;h</span><span style="color: #800000;"&gt;'</span>: 3600,<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;d</span><span style="color: #800000;"&gt;'</span>: 86400<span style="color: #000000;"&gt;}[period[0]] <span style="color: #ff9900;"&gt;# <a href="/tag/zhuanhuanshijian/" target="_blank" class="keywords">转换时间</a>为数字,示例配置的5/m,m转为60秒 </span></span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; (num_requests,duration) </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; allow_request(self,view): <span style="color: #ff9900;"&gt; # 判断请求的速率是否通过 </span></span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Implement the check to see if the request should be thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>led. On success calls `thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_success`. On failure calls `thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_failure`. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;if</span> self.rate <span style="color: #0000ff;"&gt;is</span><span style="color: #000000;"&gt; None: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; True self.key </span>=<span style="color: #000000;"&gt; self.get_cache_key(request,view) </span><span style="color: #0000ff;"&gt;if</span> self.key <span style="color: #0000ff;"&gt;is</span><span style="color: #000000;"&gt; None: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; True self.history </span>=<span style="color: #000000;"&gt; self.cache.get(self.key,[]) self.<a href="/tag/Now/" target="_blank" class="keywords">Now</a> </span>=<span style="color: #000000;"&gt; self.timer() </span><span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; Drop any requests from the history which have <a href="/tag/Now/" target="_blank" class="keywords">Now</a> passed the</span> <span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le duration</span> <span style="color: #0000ff;"&gt;while</span> self.history <span style="color: #0000ff;"&gt;and</span> self.history[-1] <= self.<a href="/tag/Now/" target="_blank" class="keywords">Now</a> -<span style="color: #000000;"&gt; self.duration: <span style="color: #ff9900;"&gt;# 频率判断实现原理,已经举例进行了说明</span> self.history.pop() </span><span style="color: #0000ff;"&gt;if</span> len(self.history) >=<span style="color: #000000;"&gt; self.num_requests: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; self.thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_failure() </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; self.thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_success() </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_success(self): <span style="color: #ff9900;"&gt;# 频率通过返回true </span></span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Inserts the current request's timestamp along with the key into the cache. </span><span style="color: #800000;"&gt;"""</span><span style="color: #000000;"&gt; self.history.insert(0,self.<a href="/tag/Now/" target="_blank" class="keywords">Now</a>) self.cache.set(self.key,self.history,self.duration) </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; True </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_failure(self): <span style="color: #ff9900;"&gt;# 不通过返回false </span></span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Called when a request to the API has <a href="/tag/Failed/" target="_blank" class="keywords">Failed</a> due to thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>ling. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; False </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; wait(self): <span style="color: #ff9900;"&gt; # 返回等待时间 </span></span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Returns the recommended next request time in seconds. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;if</span><span style="color: #000000;"&gt; self.history: remaining_duration </span>= self.duration - (self.<a href="/tag/Now/" target="_blank" class="keywords">Now</a> - self.history[-1<span style="color: #000000;"&gt;]) </span><span style="color: #0000ff;"&gt;else</span><span style="color: #000000;"&gt;: remaining_duration </span>=<span style="color: #000000;"&gt; self.duration available_requests </span>= self.num_requests - len(self.history) + 1 <span style="color: #0000ff;"&gt;if</span> available_requests <=<span style="color: #000000;"&gt; 0: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; None </span><span style="color: #0000ff;"&gt;return</span> remaining_duration / float(available_requests)</pre>

get_ident方法源码,该方法用于获取请求的IP:

0. If not use all of HTTP_X_FORWARDED_FOR if it is available,if not use REMOTE_ADDR. = request.Meta.get(= request.Meta.get(Meta 这样也可以获取 num_proxies = </span><span style="color: #0000ff;"&gt;if</span> num_proxies <span style="color: #0000ff;"&gt;is</span> <span style="color: #0000ff;"&gt;not</span><span style="color: #000000;"&gt; None: </span><span style="color: #0000ff;"&gt;if</span> num_proxies == 0 <span style="color: #0000ff;"&gt;or</span> xff <span style="color: #0000ff;"&gt;is</span><span style="color: #000000;"&gt; None: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; remote_addr addrs </span>= xff.split(<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;,</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;) client_addr </span>= addrs[-<span style="color: #000000;"&gt;min(num_proxies,len(addrs))] </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; client_addr.strip() </span><span style="color: #0000ff;"&gt;return</span> <span style="color: #800000;"&gt;''</span>.join(xff.split()) <span style="color: #0000ff;"&gt;if</span> xff <span style="color: #0000ff;"&gt;else</span> remote_addr</pre>
五、内置频率控制类DRF内置了多种频率控制类提供我们使用,其核心原理都是通过判断request_allow方法返回值来判断频率是否通过,通过wait方法返回等待时间。

1.BaseThrottle:最基本的频率控制需要重写allow_request方法和wait方法

display: none;" onclick="cnblogs_code_hide('7c9591a1-8392-4da1-ab58-ca76b3977c4d',event)" src="/res/2019/03-04/13/405b18b4b6584ae338e0f6ecaf736533.gif" alt="">
ottle(object): ottling of requests.
<span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; allow_request(self,view):
    </span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt;
    Return `True` if the request should be allowed,`False` otherwise.
    </span><span style="color: #800000;"&gt;"""</span>
    <span style="color: #0000ff;"&gt;raise</span> NotImplementedError(<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;.allow_request() must be overridden</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;)

</span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; get_ident(self,if not use REMOTE_ADDR.
    </span><span style="color: #800000;"&gt;"""</span><span style="color: #000000;"&gt;
    xff </span>= request.<a href="/tag/Meta/" target="_blank" class="keywords">Meta</a>.get(<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;HTTP_X_FORWARDED_FOR</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;)
    remote_addr </span>= request.<a href="/tag/Meta/" target="_blank" class="keywords">Meta</a>.get(<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;REMOTE_ADDR</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;)
    num_proxies </span>=<span style="color: #000000;"&gt; api_settings.NUM_PROXIES

    </span><span style="color: #0000ff;"&gt;if</span> num_proxies <span style="color: #0000ff;"&gt;is</span> <span style="color: #0000ff;"&gt;not</span><span style="color: #000000;"&gt; None:
        </span><span style="color: #0000ff;"&gt;if</span> num_proxies == 0 <span style="color: #0000ff;"&gt;or</span> xff <span style="color: #0000ff;"&gt;is</span><span style="color: #000000;"&gt; None:
            </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; remote_addr
        addrs </span>= xff.split(<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;,len(addrs))]
        </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; client_addr.strip()

    </span><span style="color: #0000ff;"&gt;return</span> <span style="color: #800000;"&gt;''</span>.join(xff.split()) <span style="color: #0000ff;"&gt;if</span> xff <span style="color: #0000ff;"&gt;else</span><span style="color: #000000;"&gt; remote_addr

</span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; wait(self):
    </span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt;
    Optionally,return a recommended number of seconds to wait before
    the next request.
    </span><span style="color: #800000;"&gt;"""</span>
    <span style="color: #0000ff;"&gt;return</span> None</pre>
ottle(object)

2.SimpleRateThrottle:示例中已经使用,并对源码和原理进行了分析。

display: none;" onclick="cnblogs_code_hide('ed507b22-b269-44e2-9f6f-4252aa82b6b4',event)" src="/res/2019/03-04/13/405b18b4b6584ae338e0f6ecaf736533.gif" alt="">
ottle(BaseThrottle): Prev<a href="/tag/IoU/" target="_blank" class="keywords">IoU</a>s request @R_506_<a href="/tag/404/" target="_blank" class="keywords">404</a>5@ion used for thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>ling is stored in the cache. </span><span style="color: #800000;"&gt;"""</span><span style="color: #000000;"&gt; cache </span>=<span style="color: #000000;"&gt; default_cache timer </span>=<span style="color: #000000;"&gt; time.time cache_format </span>= <span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_%(s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e)s_%(ident)s</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt; s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e </span>=<span style="color: #000000;"&gt; None THR<a href="/tag/ott/" target="_blank" class="keywords">ott</a>LE_RATES </span>=<span style="color: #000000;"&gt; api_settings.DEFAULT_THR<a href="/tag/ott/" target="_blank" class="keywords">ott</a>LE_RATES </span><span style="color: #0000ff;"&gt;def</span> <span style="color: #800080;"&gt;<a href="/tag/init/" target="_blank" class="keywords">__init__</a></span><span style="color: #000000;"&gt;(self): </span><span style="color: #0000ff;"&gt;if</span> <span style="color: #0000ff;"&gt;not</span> getattr(self,view): </span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Should return a unique cache-key which can be used for thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>ling. Must be overridden. May return `None` if the request should not be thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>led. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;raise</span> NotImplementedError(<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;.get_cache_key() must be overridden</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;) </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; get_rate(self): </span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Deter<a href="/tag/mine/" target="_blank" class="keywords">mine</a> the string representation of the allowed request rate. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;if</span> <span style="color: #0000ff;"&gt;not</span> getattr(self,None): msg </span>= (<span style="color: #800000;"&gt;"</span><span style="color: #800000;"&gt;You must set either `.s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e` or `.rate` for '<a href="/tag/s/" target="_blank" class="keywords">%s</a>' thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le</span><span style="color: #800000;"&gt;"</span> %<span style="color: #000000;"&gt; self.</span><span style="color: #800080;"&gt;__class__</span>.<span style="color: #800080;"&gt;<a href="/tag/name/" target="_blank" class="keywords">__name__</a></span><span style="color: #000000;"&gt;) </span><span style="color: #0000ff;"&gt;raise</span><span style="color: #000000;"&gt; ImproperlyCon<a href="/tag/fig/" target="_blank" class="keywords">fig</a>ured(msg) </span><span style="color: #0000ff;"&gt;try</span><span style="color: #000000;"&gt;: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; self.THR<a href="/tag/ott/" target="_blank" class="keywords">ott</a>LE_RATES[self.s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e] </span><span style="color: #0000ff;"&gt;except</span><span style="color: #000000;"&gt; KeyError: msg </span>= <span style="color: #800000;"&gt;"</span><span style="color: #800000;"&gt;No default thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le rate set for '<a href="/tag/s/" target="_blank" class="keywords">%s</a>' s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e</span><span style="color: #800000;"&gt;"</span> %<span style="color: #000000;"&gt; self.s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e </span><span style="color: #0000ff;"&gt;raise</span><span style="color: #000000;"&gt; ImproperlyCon<a href="/tag/fig/" target="_blank" class="keywords">fig</a>ured(msg) </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; parse_rate(self,rate): </span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Given the request rate string,period </span>= rate.split(<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;/</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;) num_requests </span>=<span style="color: #000000;"&gt; int(num) duration </span>= {<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;s</span><span style="color: #800000;"&gt;'</span>: 1,<span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;d</span><span style="color: #800000;"&gt;'</span>: 86400<span style="color: #000000;"&gt;}[period[0]] </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; (num_requests,view): </span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Implement the check to see if the request should be thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>led. On success calls `thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_success`. On failure calls `thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_failure`. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;if</span> self.rate <span style="color: #0000ff;"&gt;is</span><span style="color: #000000;"&gt; None: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; True self.key </span>=<span style="color: #000000;"&gt; self.get_cache_key(request,[]) self.<a href="/tag/Now/" target="_blank" class="keywords">Now</a> </span>=<span style="color: #000000;"&gt; self.timer() </span><span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; Drop any requests from the history which have <a href="/tag/Now/" target="_blank" class="keywords">Now</a> passed the</span> <span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le duration</span> <span style="color: #0000ff;"&gt;while</span> self.history <span style="color: #0000ff;"&gt;and</span> self.history[-1] <= self.<a href="/tag/Now/" target="_blank" class="keywords">Now</a> -<span style="color: #000000;"&gt; self.duration: self.history.pop() </span><span style="color: #0000ff;"&gt;if</span> len(self.history) >=<span style="color: #000000;"&gt; self.num_requests: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; self.thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_failure() </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; self.thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_success() </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_success(self): </span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Inserts the current request's timestamp along with the key into the cache. </span><span style="color: #800000;"&gt;"""</span><span style="color: #000000;"&gt; self.history.insert(0,self.duration) </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; True </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le_failure(self): </span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Called when a request to the API has <a href="/tag/Failed/" target="_blank" class="keywords">Failed</a> due to thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>ling. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; False </span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; wait(self): </span><span style="color: #800000;"&gt;"""</span><span style="color: #800000;"&gt; Returns the recommended next request time in seconds. </span><span style="color: #800000;"&gt;"""</span> <span style="color: #0000ff;"&gt;if</span><span style="color: #000000;"&gt; self.history: remaining_duration </span>= self.duration - (self.<a href="/tag/Now/" target="_blank" class="keywords">Now</a> - self.history[-1<span style="color: #000000;"&gt;]) </span><span style="color: #0000ff;"&gt;else</span><span style="color: #000000;"&gt;: remaining_duration </span>=<span style="color: #000000;"&gt; self.duration available_requests </span>= self.num_requests - len(self.history) + 1 <span style="color: #0000ff;"&gt;if</span> available_requests <=<span style="color: #000000;"&gt; 0: </span><span style="color: #0000ff;"&gt;return</span><span style="color: #000000;"&gt; None </span><span style="color: #0000ff;"&gt;return</span> remaining_duration / float(available_requests)</pre>

3.AnonRateThrottle:匿名用户频率控制

display: none;" onclick="cnblogs_code_hide('9cb0305b-d117-41a1-9f44-86aae6856647',event)" src="/res/2019/03-04/13/405b18b4b6584ae338e0f6ecaf736533.gif" alt="">
ottle(SimpleRateThrottle): The IP address of the request will be used as the unique cache key. </span><span style="color: #800000;"&gt;"""</span><span style="color: #000000;"&gt; s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e </span>= <span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;anon</span><span style="color: #800000;"&gt;'</span> <span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; get_cache_key(self,view): </span><span style="color: #0000ff;"&gt;if</span><span style="color: #000000;"&gt; request.user.is_authenticated: </span><span style="color: #0000ff;"&gt;return</span> None <span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; Only thr<a href="/tag/ott/" target="_blank" class="keywords">ott</a>le unauthenticated requests.</span> <span style="color: #0000ff;"&gt;return</span> self.cache_format %<span style="color: #000000;"&gt; { </span><span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;: self.s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e,</span><span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;ident</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;: self.get_ident(request) }</span></pre>
ottle

4.UserRateThrottle:基于SimpleRateThrottle,对用户的频率控制

display: none;" onclick="cnblogs_code_hide('007616c7-111e-4e0f-aa50-0406e8c0c210',event)" src="/res/2019/03-04/13/405b18b4b6584ae338e0f6ecaf736533.gif" alt="">
ottle(SimpleRateThrottle): The user id will be used as a unique cache key if the user is authenticated. For anonymous requests,the IP address of the request will be used. </span><span style="color: #800000;"&gt;"""</span><span style="color: #000000;"&gt; s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e </span>= <span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;user</span><span style="color: #800000;"&gt;'</span> <span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; get_cache_key(self,view): </span><span style="color: #0000ff;"&gt;if</span><span style="color: #000000;"&gt; request.user.is_authenticated: ident </span>=<span style="color: #000000;"&gt; request.user.pk </span><span style="color: #0000ff;"&gt;else</span><span style="color: #000000;"&gt;: ident </span>=<span style="color: #000000;"&gt; self.get_ident(request) </span><span style="color: #0000ff;"&gt;return</span> self.cache_format %<span style="color: #000000;"&gt; { </span><span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;: self.s<a href="/tag/cop/" target="_blank" class="keywords">cop</a>e,</span><span style="color: #800000;"&gt;'</span><span style="color: #800000;"&gt;ident</span><span style="color: #800000;"&gt;'</span><span style="color: #000000;"&gt;: ident }</span></pre>
ottle
六、自定义频率控制自定义频率控制无非实现request_allow方法和wait方法,你可以根据实际需求来定制你的频率控制,下面是示例:

rest_framework.throttling ottle REQUEST_RECORD = {} <span style="color: #008000;">#<span style="color: #008000;"> 访问记录,可使用nosql数据库

<span style="color: #0000ff;">class<span style="color: #000000;"> VisitThrottle(BaseThrottle):
<span style="color: #800000;">'''<span style="color: #800000;">60s内最多能访问5次<span style="color: #800000;">'''

<span style="color: #0000ff;"&gt;def</span> <span style="color: #800080;"&gt;<a href="/tag/init/" target="_blank" class="keywords">__init__</a></span><span style="color: #000000;"&gt;(self):
    self.history </span>=<span style="color: #000000;"&gt; None

</span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; allow_request(self,view):
    </span><span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; <a href="/tag/huoqu/" target="_blank" class="keywords">获取</a><a href="/tag/yonghu/" target="_blank" class="keywords">用户</a>ip (get_ident)</span>
    remote_addr =<span style="color: #000000;"&gt; self.get_ident(request)
    ctime </span>=<span style="color: #000000;"&gt; time.time()

    </span><span style="color: #0000ff;"&gt;if</span> remote_addr <span style="color: #0000ff;"&gt;not</span> <span style="color: #0000ff;"&gt;in</span><span style="color: #000000;"&gt; REQUEST_RECORD:
        REQUEST_RECORD[remote_addr] </span>= [ctime,]  <span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; 保持请求的时间,形式{ip:[时间,]}</span>
        <span style="color: #0000ff;"&gt;return</span> True  <span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; True表示可以访问</span>
    <span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; <a href="/tag/huoqu/" target="_blank" class="keywords">获取</a>当前ip的历史访问记录</span>
    history =<span style="color: #000000;"&gt; REQUEST_RECORD.get(remote_addr)

    self.history </span>=<span style="color: #000000;"&gt; history


    </span><span style="color: #0000ff;"&gt;while</span> history <span style="color: #0000ff;"&gt;and</span> history[-1] < ctime - 60<span style="color: #000000;"&gt;:
        </span><span style="color: #008000;"&gt;#</span><span style="color: #008000;"&gt; while循环确保每列表中是最新的60秒内的请求</span>

<span style="color: #000000;">
history.pop()
<span style="color: #008000;">#<span style="color: #008000;"> 访问记录小于5次,将本次请求插入到最前面,作为最新的请求
<span style="color: #0000ff;">if len(history) < 5<span style="color: #000000;">:
history.insert(0,ctime)
<span style="color: #0000ff;">return<span style="color: #000000;"> True

</span><span style="color: #0000ff;"&gt;def</span><span style="color: #000000;"&gt; wait(self):
    </span><span style="color: #800000;"&gt;'''</span><span style="color: #800000;"&gt;返回等待时间</span><span style="color: #800000;"&gt;'''</span><span style="color: #000000;"&gt;
    ctime </span>=<span style="color: #000000;"&gt; time.time()
    </span><span style="color: #0000ff;"&gt;return</span> 60 - (ctime - self.history[-1])</pre>
七、总结1.使用方法

  • 继承BaseThrottle类
  • 重写request_allow方法和wait方法,request_allow方法返回true代表通过,否则拒绝,wait返回等待的时间

2.配置

= ottLE_CLASSES:[ottle.VisitThrottle],cope定义的值 <span style="color: #008000;">#<span style="color: #008000;">#单一视图使用
throttle_classes =<span style="color: #000000;"> [VisitThrottle,]

<span style="color: #008000;">#<span style="color: #008000;">#优先级
单一视图>全局

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐