Django Rest Framework使用者訪問頻率限制(轉載)
阿新 • • 發佈:2021-08-30
一.REST framework的請求生命週期
基於rest-framework的請求處理,與常規的url配置不同,通常一個django的url請求對應一個檢視函式,在使用rest-framework時,我們要基於檢視物件,然後呼叫檢視物件的as_view函式,as_view函式中會呼叫rest_framework/views.py中的dispatch函式,這個函式會根據request請求方法,去呼叫我們在view物件中定義的對應的方法,就像這樣:from app01 import views as app01_view urlpatterns = [ url(r'^limits/', api_view.LimitView.as_view()), ]
二. 例項程式碼
1. 程式碼
from rest_framework.views import APIView from rest_framework import exceptions from rest_framework.response import Response from rest_framework.throttling import SimpleRateThrottle class MySimpleRateThrottle(SimpleRateThrottle): scope = "limit" def get_cache_key(self, request, view): return self.get_ident(request) class LimitView(APIView): authentication_classes = [] permission_classes = [] throttle_classes = [MySimpleRateThrottle, ] # 自定義分流類 def get(self, request, *args, **kwargs): self.dispatchreturn Response('控制訪問頻率示例') def throttled(self, request, wait): class MyThrottled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = 'Expected available in {wait} second.' extra_detail_plural = '還需要再等待{wait}' raise MyThrottled(wait)
2. 執行流程
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 # 1. 對request進行加工 # request封裝了 """ request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context """ request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: # 初始化request # 確定request版本,使用者認證,許可權控制,使用者訪問頻率限制 self.initial(request, *args, **kwargs) # Get the appropriate handler method 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) # 6. 二次加工request self.response = self.finalize_response(request, response, *args, **kwargs) return self.responsedispatch
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. # 2. 確定request版本資訊 version, scheme = self.determine_version(request, *args, **kwargs) request.version, request.versioning_scheme = version, scheme # Ensure that the incoming request is permitted # 3. 使用者認證 self.perform_authentication(request) # 4. 許可權控制 self.check_permissions(request) # 5. 使用者訪問頻率限制 self.check_throttles(request)initial
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())check_throttles
def get_throttles(self): """ Instantiates and returns the list of throttles that this view uses. """ return [throttle() for throttle in self.throttle_classes]get_throttles
class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS metadata_class = api_settings.DEFAULT_METADATA_CLASS versioning_class = api_settings.DEFAULT_VERSIONING_CLASS # Allow dependency injection of other settings to make testing easier. settings = api_settings schema = DefaultSchema()throttle_classes
3. 執行throttle中allow_request方法
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()自定義類繼承SimpleRateThrottle
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')get_cache_key
4. 處理報錯異常
def throttled(self, request, wait): """ If request is throttled, determine what kind of exception to raise. """ raise exceptions.Throttled(wait)throttled
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)exceptions.Throttled
5. 重寫throttled方法處理異常
def throttled(self, request, wait): class MyThrottled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = 'Expected available in {wait} second.' extra_detail_plural = '還需要再等待{wait}' raise MyThrottled(wait)重寫throttled方法
三. settings.py配置全域性
1. 配置全侷限流速度
REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, 'DEFAULT_AUTHENTICATION_CLASSES': [ ], 'DEFAULT_PERMISSION_CLASSES': [], 'DEFAULT_THROTTLE_RATES': { 'anon': '5/minute', 'user': '10/minute', 'limit': '2/minute' # 設定每分鐘訪問次數 } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': 'cache', } }settings.py
2. 訪問2次