目录
一、实现自定义访问频率控制逻辑 - BaseThrottle
1-1 访问频率实现思路(一个ip一分钟只能访问3次)
1-2 访问频率逻辑实现
1-3 源码分析
二、DRF 内置频率类 - SimpleRateThrottle
2-1 频率控制实现
2-2 源码分析
三、频率类的使用配置
3-1 局部配置
3-2 全局配置 及 局部禁用
四、频率错误信息的中文显示 - 即 throttled 方法重写
4-1 源码分析
4-2 重写throttled方法
一、实现自定义访问频率控制逻辑 - BaseThrottle
1-1 访问频率实现思路(一个ip一分钟只能访问3次)
- 取出访问者ip - request.MATE.get('REMOTE_ADDR ')
- 第一次访问:判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问 - {‘ip1’:[time1,]}
- 非第一次访问:判断当前ip在访问字典里,继续往下走 - {‘ip1’:[time1,time2]}
- 循环当前ip的列表,若非空,则取出符合 当前时间 - 列表末尾时间 > 60s 的对象时间,进行pop删除操作。- 保证列表中只存在未超过间隔60s的访问时间
- 判断操作 :
- 当列表长度<3 - 一分钟内访问不超过三次,将当前时间插入列表第一位,返回True,表示验证成功
- 当列表长度>3 - 一分钟内访问次数唱过三次,返回False,表示验证失败
1-2 访问频率逻辑实现
总结:
- 频率类
- 必须重写allow_request(self, request, view) 方法
- 若频率验证通过 : 返回True
- 若频率验证不通过:返回False
- 必须重写 wait 方法用于频率验证不通过之后等待
- 视图类
- 局部使用 - throttle_classes = [drfAuth.MyThrottle, ]
''' drfAuth.py - 基于DRF的自定义频率实现 ''' import time class MyThrottle(): # 用于存放ip列表 visitor_dic = {} def __init__(self): self.history = None def allow_request(self, request, view): # MATA:请求header内数据 # REMOTE_ADDR:获取ip地址 ip = request.META.get('REMOTE_ADDR') ctime = time.time() # 不在字典中 - 第一次访问 if ip not in self.visitor_dic: self.visitor_dic[ip] = [ctime, ] return True # 根据当前访问者ip,取出访问的时间列表 history = self.visitor_dic[ip] self.history = history # history[-1]:访问时间列表最后一个 while history and ctime - history[-1] > 60: history.pop() if len(history) < 3: # 把当前时间放到第0个位置上 history.insert(0, ctime) return True return False # 若前方频率范围Flase则60s后才能进行操作 def wait(self): # 剩余时间 ctime = time.time() return 60 - (ctime - self.history[-1]) ''' view视图函数 ''' class FrequencyTest(APIView): throttle_classes = [drfAuth.MyThrottle, ] def get(self, request): return HttpResponse('get') def post(self, request): return HttpResponse('post')
1-3 源码分析
''' check_throttles APIView - dispatch - initial - check_throttles ''' def check_throttles(self, request): """ Check if request should be throttled. Raises an appropriate exception if the request is throttled. 检查是否应控制请求。如果控制了请求,则引发适当的异常。 """ for throttle in self.get_throttles(): # 判断指定对象内是否存在allow_request,必须传入request,和基于APIView的试图类 # 所以,在自己的频率控制中,返回必须为True or False if not throttle.allow_request(request, self): # 若频率控制返回False 则执行类内的wait()方法抛出异常 self.throttled(request, throttle.wait()) ''' get_throttles ''' def get_throttles(self): """ Instantiates and returns the list of throttles that this view uses. 实例化并返回视图使用的节流阀列表。 """ return [throttle() for throttle in self.throttle_classes] ''' throttled ''' def throttled(self, request, wait): """ If request is throttled, determine what kind of exception to raise. 如果已对请求进行了控制,请确定要引发哪种异常 """ raise exceptions.Throttled(wait) ''' 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 - wait ''' def wait(self): """ Optionally, return a recommended number of seconds to wait before the next request. 或者,返回在下一个请求之前等待的推荐秒数。 """ return None
二、DRF 内置频率类 - SimpleRateThrottle
2-1 频率控制实现
总结:
- 频率控制类
- 必须写入scope
- 必须重写get_cache_key
- get_cache_key返回值表示存入的查询信息,例如存入ip、存入用户名等
- settings配置
- REST_FRAMEWORK - 'DEFAULT_THROTTLE_RATES'
''' 频率控制类 - SimpleRateThrottle - scope - 对应settings内DEFAULT_THROTTLE_RATE的key值,用于获取value - get_cache_key - 返回ip - get_ident:源码内用于返回ip地址 BaseThrottle类 - get_ident(self, request)方法 返回值 return remote_addr - remote_addr = request.META.get('REMOTE_ADDR') ''' from rest_framework.throttling import SimpleRateThrottle class MyThrottle(SimpleRateThrottle): scope = 'aaa' def get_cache_key(self, request, view): # 返回ip地址,下面二者相同 # return request.META.get('REMOTE_ADDR') return self.get_ident(request) ''' settings.py 配置 DEFAULT_THROTTLE_RATES - 配置scope频率 10次/m每分钟 ''' REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES':{ 'aaa':'10/m' } }
2-2 源码分析
''' 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 `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') 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): # 若self内查询不到rate if not getattr(self, 'rate', None): # 将get_rate方法内返回的值赋给rate (3/m) self.rate = self.get_rate() self.num_requests, self.duration = self.parse_rate(self.rate) def get_rate(self): """ Determine the string representation of the allowed request rate. """ # 若在self内查询不到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: # THROTTLE_RATES settings配置文件内 - 获取一个字典,并且其中获取scope的value值 # 即,返回了settings配置文件内THROTTLE_RATES字典内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:
, """ 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): """ Implement the check to see if the request should be throttled. On success calls `throttle_success`. On failure calls `throttle_failure`. """ if self.rate is None: return True # key 为ip self.key = self.get_cache_key(request, view) if self.key is None: return True # history 相当于visitor_dic,放到缓存中 self.history = self.cache.get(self.key, []) # now 当前时间 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 self.throttle_success() 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) ''' BaseThrottle - allow_request :若继承BaseThrottle,却没有重写allow_request方法,则直接抛出异常 - get_ident:用于获取ip地址 - wait:返回None ''' class BaseThrottle(object): """ Rate throttling of requests. """ def allow_request(self, request, view): """ Return `True` if the request should be allowed, `False` otherwise. 如果请求应该被允许,返回“True”,否则返回“False”。 """ raise NotImplementedError('.allow_request() must be overridden') 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. 通过解析HTTP_X_FORWARDED_FOR来识别发出请求的机器 如果当前代理和代理数量为> 0。如果不使用全部 如果可用,则使用HTTP_X_FORWARDED_FOR,如果不使用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
三、频率类的使用配置
3-1 局部配置
''' 试图类内 - 局部使用 ''' throttle_classes = [drfAuth.MyThrottle, ]
3-2 全局配置 及 局部禁用
'''settings.py 内全局使用''' REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': ['app01.drfAuth.MyThrottle', ], } ''' 试图类内 - 局部禁用 ''' throttle_classes = []
四、频率错误信息的中文显示 - 即 throttled 方法重写
4-1 源码分析
总结: 调用 throttled 方法触发异常抛出 exceptions.Throttled(wait) ,所以若在 throttled 方法内重写错误字段,会覆盖Throttled内的同名属性
''' throttled ''' def throttled(self, request, wait): """ If request is throttled, determine what kind of exception to raise. 如果已对请求进行了控制,请确定要引发哪种异常 """ raise exceptions.Throttled(wait) ''' 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) ''' APIException - 异常类 ''' class APIException(Exception): """ Base class for REST framework exceptions. Subclasses should provide `.status_code` and `.default_detail` properties. 用于REST框架异常的基类。 子类应该提供'.status_code '和' .default_detail '属性。 """ status_code = status.HTTP_500_INTERNAL_SERVER_ERROR default_detail = _('A server error occurred.') default_code = 'error' def __init__(self, detail=None, code=None): if detail is None: detail = self.default_detail if code is None: code = self.default_code self.detail = _get_error_details(detail, code) def __str__(self): return six.text_type(self.detail)
4-2 重写throttled方法
思路总结:
- 重写错误信息
- default_detail = _('Request was throttled.')
- extra_detail_singular = 'Expected available in {wait} second.'
- extra_detail_plural = 'Expected available in {wait} seconds.'
- 最后应该需要和原来的 throttled方法 一样抛出异常
- 由于源码抛出exceptions.Throttled(wait) - 则我的异常类必须继承 exceptions.Throttled
from rest_framework import exceptions class FrequencyTest(APIView): throttle_classes = [drfAuth.MyThrottle, ] def get(self, request): return HttpResponse('get') def post(self, request): return HttpResponse('post') def throttled(self, request, wait): class MyThrottled(exceptions.Throttled): default_detail = '您好' extra_detail_singular = '还剩 {wait} 秒.' extra_detail_plural = '还剩 {wait} 秒' raise MyThrottled(wait)