rest framework throttle

A simple example of the throttle

The so-called throttling is to control user access frequency, here is divided into an anonymous user (non-login user) and the user's login restrictions.

  • Anonymous User: limit their frequency based on their IP
  • Login User: IP, user name is OK

Obtain a user request IP:request.META.get('REMOTE_ADDR')

Requirements: one minute anonymous users can only access a maximum of three times

1、app/utils/throttle.py

import time

# 用户保存用户访问时间记录,以用户 IP 为键,值为当前时间组成的列表,最多只能有三个
VISIT_RECORD = {}

# {'127.0.0.1': [1561284634.3823733, 1561284633.0358386, 1561284631.2291381]}


class VisitThrottle(object):
    """节流,限制访问频率"""
    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        """允许过去的请求"""
        # 获取 ip
        remote_addr = request.META.get('REMOTE_ADDR')
        ctime = time.time()
        # 第一次访问
        if remote_addr not in VISIT_RECORD:
            VISIT_RECORD[remote_addr] = [ctime, ]
            return True
        
        # 获取记录
        history = VISIT_RECORD[remote_addr]
        self.history = history

        # 若列表最后一个和当前时间的差大于 1 分钟,就删除最后一个
        while history and history[-1] < ctime - 60:
            history.pop()   # 弹出最后一个

        if len(history) < 3:
            history.insert(0, ctime)        # 插入到列表第一个位置
            return True

    def wait(self):
        """剩余计时"""
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

2、app/views.py

from .utils.throttle import VisitThrottle


class UserInfo(APIView):
    """用户个人信息"""
    authentication_classes = []
    permission_classes = []
    throttle_classes = [VisitThrottle, ]

    def get(self, request, *args, **kwargs):
        # user_name = request.user.username
        # return HttpResponse(user_name)

        # self.dispatch

        return HttpResponse('UserInfo')

3, visit: http://127.0.0.1:8000/api/v1/info/here I visited an anonymous user, after three:

The above is based on user IP as a key to limit user access frequency, if you want to restrict a user name, simply replace the IP user name.

Second, the global configuration

# 设置全局认证
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": ['app.utils.throttle.VisitThrottle', ],
}

Local settings:

throttle_classes = [VisitThrottle, ]

Third, the built-in throttling

1, and the certification authority, like, rest framework also has a built-in throttling associated implementation, generally custom throttle classes need to inherit BaseThrottle:

import time
from rest_framework.throttling import BaseThrottle


VISIT_RECORD = {}


class VisitThrottle(BaseThrottle):
    """节流,限制访问频率"""
    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        """允许过去的请求"""
        # 获取 ip
        ...

2, class BaseThrottlesource code:

BaseThrottleIt is implemented in allow_request()、get_indent() 以及 wait()the method, but in addition to get_ident()the method of acquiring the user IP, the other two functions are not fulfill its function.

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')

    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')       # 获取 IP
        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, we take a look at BaseThrottlethe following categories SimpleRateThrottle:

class SimpleRateThrottle(BaseThrottle):
    """
    A simple cache implementation, that only requires `.get_cache_key()`
    to be overridden. 一个简单的缓存实现,只需要重写 get_cache_key()方法

    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'.
    速率格式为:request/分钟

    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       # Django 内置的缓存
    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):
        """
        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.
        """
        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):
        """
        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)
        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

        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()

        # 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 self.throttle_failure()
        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)

4, it inherits BaseThrottleand overrides the allow_request()and wait()methods, additionally provided to control the access rate property. Therefore, we custom throttling class, just inherit SimpleRateThrottle, and override the get_cache_key()method to app/utils/throttle.py:

from rest_framework.throttling import BaseThrottle, SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    scope = 'Hubery'

    def get_cache_key(self, request, view):
        return self.get_ident(request)

5、settings.py

# 设置全局认证
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": ['app.utils.throttle.VisitThrottle', ],
    "DEFAULT_THROTTLE_RATES": {
        "Hubery": '4/m',        # 匿名用户设置每分钟只能访问 4 次
    }
}

Fourth, the anonymous user and logged in user while limiting

1、app/utils/throttle.py

class VisitThrottle(SimpleRateThrottle):
    """匿名用户根据 IP 限制每分钟访问 4 次"""
    scope = 'Hubery'

    def get_cache_key(self, request, view):
        return self.get_ident(request)


class UserThrottle(SimpleRateThrottle):
    """登录用户限制每分钟可以访问 10 次"""
    scope = 'Jun'       # 随便定义

    def get_cache_key(self, request, view):
        return request.user.username

2、settings.py

# 设置全局认证
REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": ['app.utils.throttle.UserThrottle', ],
    "DEFAULT_THROTTLE_RATES": {
        "Hubery": '4/m',        # 匿名用户设置每分钟只能访问 4 次
        "Jun": '10/m',          # 登录用户设置每分钟只能访问 10 次
    }
}

Global Settings login user restrictions (UserThrottle) , set up local anonymous user can limit

3、app/views.py

from .utils.throttle import VisitThrottle

class OrderView(APIView):
    """订单管理"""
    # authentication_classes = [MyAuthentication, ]  # 添加认证

    # authentication_classes = []
    # permission_classes = [SVIPPermission, ]

    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None, }
        ret['data'] = ORDER_DICT
        print(request.user)
        return JsonResponse(ret)


    
class UserInfo(APIView):
    """用户个人信息"""
    authentication_classes = []
    permission_classes = []
    throttle_classes = [VisitThrottle, ]        # 局部设置匿名用户访问频率

    def get(self, request, *args, **kwargs):

        return HttpResponse('UserInfo')

Source flow chart

to sum up

  • Custom throttle shall inherit SimpleRateThrottle, and override get_cache_key()methods
  • Global and local restrictions may not be provided limiting throttle_classes = []

Guess you like

Origin www.cnblogs.com/midworld/p/11075983.html