A custom limiting
Also called limiting frequency component assembly, for controlling the client can request frequency the API, for example, one minute visit 3, if more than three times within one minute to the client to be limiting.
1, custom limiting
Suppose now that an API to access, access can not be more than 3 times in the 30s, how to achieve?
} = {VISIT_RECORD # define global variables, access record for storing class VisitThrottle (Object): DEF the __init__ (Self):
# await for calculating the remaining access time self.history = None DEF allow_request (Self, Request, View): # Get uniquely identifies the user as ip REMOTE_ADDR = request.META.get ( ' REMOTE_ADDR ' ) # Get the current time access the ctime = the time.time () # this is the first user access, which was recorded, and returns True, allow continued access IF REMOTE_ADDR not in VISIT_RECORD: VISIT_RECORD [REMOTE_ADDR] =[ctime,] return True # If this is not the first visit, get all the records History = VISIT_RECORD.get (REMOTE_ADDR) self.history = History # timeframe to determine the beginning of time and the present time whether the difference in the provision of within, for example, in the 60s, if not, # may be removed beginning time of recording the while History and History [-1] <the ctime - 30 : history.pop () # at this point in time are recorded in the list a predetermined time the range, i.e. the number of time determined number of visits IF len (History) <. 3 : history.insert (0, the ctime) return True DEF the wait (Self): # have to wait to access many seconds ctime = time.time() return 60 - (ctime - self.history[-1])
It is configured in the corresponding views:
class BookView(ListAPIView): throttle_classes = [VisitThrottle,] #配置限流组件 queryset = models.Book.objects.all() serializer_class = BookModelSerializer
2, limiting principle
In the rest framework frame, it is defined as the limiting list of classes, only global configuration or can be partially disposed. Said metering principle is to uniquely identify the client as a key, the time to access a list of values is formed as a dictionary is formed, and then by operating the dictionary:
{ http://127.0.0.1:8020/ :[11:43:30,11:42:22,11:42:20,11:42:09] }
As shown in the above dictionary, is inserted behind the access time to put on the left end, time is added to the current access 11:43 :: 30, then the beginning and 11:42:09 for calculating the difference between the access time and the predetermined time 30s were compared, if not within the 30s, then remove the recording leftmost, empathy compared in turn use a while loop, the last record within the specified time range:
{ http://127.0.0.1:8020/ :[11:43:30,] }
Then calculate the number of visits, which is the number of the list, the list obviously if the number is less than 3 can continue to access, otherwise not.
The above use global variables to be recorded, of course, also be used to store records of the cache, the cache need django API, from django.core.cache import cache, after introducing the API set and get methods may be used to set and retrieve stored in the object cache, only need to be replaced in addition to the operation of global variables:
from django.core.cache Import Cache AS default_cache Import Time class VisitThrottle (Object): Cache = default_cache DEF allow_request (Self, Request, View): ... ... # This is the first time a user visits, which was recorded and returns True, allow continued access IF not self.cache.get (REMOTE_ADDR): self.cache.set (REMOTE_ADDR, [ctime,]) return True # If this is not the first visit, get all the records History = Self. cache.get (REMOTE_ADDR) self.history = History ... ...
Limiting component rest framework is based cache to complete.
The wait method described above also indicate how long can access this API, suggesting to the client:
{ "detail": "Request was throttled. Expected available in 56 seconds." }
Second, the built-in current limit
In the rest framework has some limiting API that can be used:
1、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): 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)
If you need to use this API to implement functionality, you would need to do some configuration:
- Inheritance SimpleRateThrottle
Their definition of limiting class needs to inherit SimpleRateThrottle:
class VisitThrottle (SimpleRateThrottle): ...
- Setting scope
class VisitThrottle(SimpleRateThrottle): scope = 'book' ...
Disposed in a custom class scope parameter, and also need to configure the settings in DEFAULT_THROTTLE_RATES
= REST_FRAMEWORK { " DEFAULT_THROTTLE_RATES " : { " Book " : ' 6 / m ' , # 6 per minute access }
- Rewrite get_cache_key method
class VisitThrottle (SimpleRateThrottle): scope = ' Book ' DEF get_cache_key (Self, Request, View): "" " gain access to the label, such as in ip as indicated : param Request: : param View: : return: " "" REMOTE_ADDR = request.META.get ( ' REMOTE_ADDR ' ) return REMOTE_ADDR
Get access to uniquely identify ip, of course SimpleRateThrottle inherited BaseThrottle, there are ways to obtain ip in BaseThrottle, you only need to call.
class VisitThrottle(SimpleRateThrottle): scope = 'book' def get_cache_key(self,request,view): return self.get_ident(request)
- Local Configuration
Just add to the list of classes in the flow restrictor corresponding to the corresponding views:
class BookView (ListAPIView): ... throttle_classes = [VisitThrottle,] # Configure throttle assembly ...
- Global Configuration
Of course, also be arranged in the global settings in which:
REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES":["app01.utils.throttle.VisitThrottle"], "DEFAULT_THROTTLE_RATES": { "book": '6/m', }
This also corresponds to completion of the function, in addition to the internal API also provides other may be used.
2、AnonRateThrottle
class AnonRateThrottle(SimpleRateThrottle): """ Limits the rate of API calls that may be made by a anonymous users. The IP address of the request will be used as the unique cache key. """ scope = 'anon' def get_cache_key(self, request, view): if request.user.is_authenticated: return None # Only throttle unauthenticated requests. return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) }
Limit unauthenticated user. IP address of the incoming request generated by a unique key to be limiting.
3、UserRateThrottle
class UserRateThrottle(SimpleRateThrottle): """ Limits the rate of API calls that may be made by a given user. 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. """ scope = 'user' def get_cache_key(self, request, view): if request.user.is_authenticated: ident = request.user.pk else: ident = self.get_ident(request) return self.cache_format % { 'scope': self.scope, 'ident': ident }
API request by the user is limited to a given request frequency. The user identifier is used to generate a unique key to be limiting. Unauthenticated requests will fall back to using the IP address of the incoming request to generate a unique key for
limit.
4、ScopedRateThrottle
class ScopedRateThrottle(SimpleRateThrottle): """ Limits the rate of API calls by different amounts for various parts of the API. Any view that has the `throttle_scope` property set will be throttled. The unique cache key will be generated by concatenating the user id of the request, and the scope of the view being accessed. """ scope_attr = 'throttle_scope' def __init__(self): # Override the usual SimpleRateThrottle, because we can't determine # the rate until called by the view. pass def allow_request(self, request, view): # We can only determine the scope once we're called by the view. self.scope = getattr(view, self.scope_attr, None) # If a view does not have a `throttle_scope` always allow the request if not self.scope: return True # Determine the allowed request rate as we normally would during # the `__init__` call. self.rate = self.get_rate() self.num_requests, self.duration = self.parse_rate(self.rate) # We can now proceed as normal. return super(ScopedRateThrottle, self).allow_request(request, view) def get_cache_key(self, request, view): """ If `view.throttle_scope` is not set, don't apply this throttle. Otherwise generate the unique cache key by concatenating the user id with the '.throttle_scope` property of the view. """ if request.user.is_authenticated: ident = request.user.pk else: ident = self.get_ident(request) return self.cache_format % { 'scope': self.scope, 'ident': ident }
API can be used to restrict access to a specific portion. Only when the view is available that contains .throttle_scope
time properties will apply this limit. The only limit is then formed by the requested stream key "program" connected with a unique user identifier or IP address
Third, source code analysis
Limiting components and assemblies permissions, authentication component and the like, or start from as_view routing method corresponding view function, you can see the final go or dispatch method of APIView.
1、dispatch
def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs #rest-framework重构request对象 request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: self.initial(request, *args, **kwargs) # Get the appropriate handler method #这里和CBV一样进行方法的分发 if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc: response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
Here's dispatch method dispatch in APIView method, where the original request to be reconstructed, and by self.initial (request, * args, ** kwargs) added to the current limiting component.
2、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. version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = Version, scheme # Ensure® that incoming Request The IS permitted self.perform_authentication (Request) # authentication self.check_permissions (Request) # permission check self.check_throttles (Request) # restrictor assembly
3、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(): if not throttle.allow_request(request, self): self.throttled(request, throttle.wait())
def get_throttles(self): """ Instantiates and returns the list of throttles that this view uses. """ return [throttle() for throttle in self.throttle_classes]
You can see a list of current limiting loop is configured in class view, and apparently limiting each class must have allow_request and wait method, False is returned if allow_request explanation has restricted access to the Executive self.throttled (request , throttle.wait ()).
4、throttled
def throttled(self, request, wait): """ If request is throttled, determine what kind of exception to raise. """ raise exceptions.Throttled(wait)
That is if you have current limit, it will throw an exception to the client limiting tips.
Details Reference: https://q1mi.github.io/Django-REST-framework-documentation/api-guide/throttling/#anonratethrottle