For example, we have a user Big Wheel lottery functions, users need to be specified within an hour can only draw three times, and that this time limit on the frequency of access interface is particularly important
In fact, restframework already provides component frequency limit for us
First stroke about the process to APIview request:
as_view -> dispatch -> initialize_request -> initial -> perform_authentication -> check_permissions -> check_throttles (achieved here is frequency limit)
1 def initial(self, request, *args, **kwargs): 2 """ 3 Runs anything that needs to occur prior to calling the method handler. 4 """ 5 self.format_kwarg = self.get_format_suffix(**kwargs) 6 7 # Perform content negotiation and store the accepted info on the request 8 neg = self.perform_content_negotiation(request) 9 request.accepted_renderer, request.accepted_media_type = neg 10 11 # Determine the API version, if versioning is in use. 12 version, scheme = self.determine_version(request, *args, **kwargs) 13 request.version, request.versioning_scheme = version, scheme 14 15 # Ensure that the incoming request is permitted 16 17 # 身份验证 18 self.perform_authentication(request) 19 # 权限验证 20 self.check_permissions(request) 21 # 访问频率限制 22 self.check_throttles(request)
That check_throttles in the end to do anything at all?
1 def check_throttles(self, request): 2 """ 3 Check if request should be throttled. 4 Raises an appropriate exception if the request is throttled. 5 """ 6 for throttle in self.get_throttles(): 7 if not throttle.allow_request(request, self): 8 self.throttled(request, throttle.wait())
And check_permissions actually very similar, divided into the following steps:
1. self.get_throttles () to get a list of registered throttle class by deriving the formula, and returns instantiation
1 def get_throttles(self): 2 """ 3 Instantiates and returns the list of throttles that this view uses. 4 """ 5 return [throttle() for throttle in self.throttle_classes]
2. throttle.allow_request DESCRIPTION throttle must be achieved allow_request class methods, and the return value True to allow access right, it performs the next cycle, examined under a frequency control object
According to the previous routine, throttle assembly should have a basic throttle class, find it:
1 class BaseThrottle(object): 2 """ 3 Rate throttling of requests. 4 """ 5 6 def allow_request(self, request, view): 7 """ 8 Return `True` if the request should be allowed, `False` otherwise. 9 """ 10 raise NotImplementedError('.allow_request() must be overridden') 11 12 def get_ident(self, request): 13 """ 14 Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR 15 if present and number of proxies is > 0. If not use all of 16 HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR. 17 """ 18 xff = request.META.get('HTTP_X_FORWARDED_FOR') 19 remote_addr = request.META.get('REMOTE_ADDR') 20 num_proxies = api_settings.NUM_PROXIES 21 22 if num_proxies is not None: 23 if num_proxies == 0 or xff is None: 24 return remote_addr 25 addrs = xff.split(',') 26 client_addr = addrs[-min(num_proxies, len(addrs))] 27 return client_addr.strip() 28 29 return ''.join(xff.split()) if xff else remote_addr 30 31 def wait(self): 32 """ 33 Optionally, return a recommended number of seconds to wait before 34 the next request. 35 """ 36 return None
3. 如果返回值为False,就执行 self.throttled
# 抛出Throttled异常
1 def throttled(self, request, wait): 2 """ 3 If request is throttled, determine what kind of exception to raise. 4 """ 5 raise exceptions.Throttled(wait)
# Throttled异常类
1 class Throttled(APIException): 2 status_code = status.HTTP_429_TOO_MANY_REQUESTS 3 default_detail = _('Request was throttled.') 4 extra_detail_singular = 'Expected available in {wait} second.' 5 extra_detail_plural = 'Expected available in {wait} seconds.' 6 default_code = 'throttled' 7 8 def __init__(self, wait=None, detail=None, code=None): 9 if detail is None: 10 detail = force_text(self.default_detail) 11 if wait is not None: 12 wait = math.ceil(wait) 13 detail = ' '.join(( 14 detail, 15 force_text(ungettext(self.extra_detail_singular.format(wait=wait), 16 self.extra_detail_plural.format(wait=wait), 17 wait)))) 18 self.wait = wait 19 super(Throttled, self).__init__(detail, code)
实现:
一般来说,接口如果不做登录限制,那就会允许匿名用户和已登录用户都能访问。所以这个接口就要考虑能对匿名用户和登录用户都进行访问频率限制:
思路:
已经登录用户可以根据身份做判断,固定时间内,同一个用户的身份只能访问限定次数
未登录用户可通过IP地址判断,对同一个IP的请求进行限制
1 class TestThrottle(BaseThrottle): 2 ctime = time.time 3 4 def get_ident(self, request): 5 """ 6 根据用户IP和代理IP,当做请求者的唯一IP 7 Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR 8 if present and number of proxies is > 0. If not use all of 9 HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR. 10 """ 11 xff = request.META.get('HTTP_X_FORWARDED_FOR') 12 remote_addr = request.META.get('REMOTE_ADDR') 13 num_proxies = api_settings.NUM_PROXIES 14 15 if num_proxies is not None: 16 if num_proxies == 0 or xff is None: 17 return remote_addr 18 addrs = xff.split(',') 19 client_addr = addrs[-min(num_proxies, len(addrs))] 20 return client_addr.strip() 21 22 return ''.join(xff.split()) if xff else remote_addr 23 24 def allow_request(self, request, view): 25 """ 26 是否仍然在允许范围内 27 Return `True` if the request should be allowed, `False` otherwise. 28 :param request: 29 :param view: 30 :return: True,表示可以通过;False表示已超过限制,不允许访问 31 """ 32 # 获取用户唯一标识(如:IP) 33 34 # 允许一分钟访问10次 35 num_request = 10 36 time_request = 60 37 38 now = self.ctime() 39 ident = self.get_ident(request) 40 self.ident = ident 41 if ident not in RECORD: 42 RECORD[ident] = [now, ] 43 return True 44 history = RECORD[ident] 45 while history and history[-1] <= now - time_request: 46 history.pop() 47 if len(history) < num_request: 48 history.insert(0, now) 49 return True 50 51 def wait(self): 52 """ 53 多少秒后可以允许继续访问 54 Optionally, return a recommended number of seconds to wait before 55 the next request. 56 """ 57 last_time = RECORD[self.ident][0] 58 now = self.ctime() 59 return int(60 + last_time - now)
1 class MemberPrograms(APIView): 2 throttle_classes = [TestThrottle, ] 3 4 def get(self, request): 5 programs = MemberProgram.objects.all().values() 6 return JsonResponse(list(programs), safe=False)
测试: