Django rest framework原始碼分析(3)----節流
目錄
新增節流
自定義節流的方法
- 限制60s內只能訪問3次
(1)API資料夾下面新建throttle.py,程式碼如下:
# utils/throttle.py from rest_framework.throttling import BaseThrottle import time VISIT_RECORD = {} #儲存訪問記錄 class VisitThrottle(BaseThrottle): '''60s內只能訪問3次''' def __init__(self): self.history = None #初始化訪問記錄 def allow_request(self,request,view): #獲取使用者ip (get_ident) remote_addr = self.get_ident(request) ctime = time.time() #如果當前IP不在訪問記錄裡面,就新增到記錄 if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr] = [ctime,] #鍵值對的形式儲存 returnTrue #True表示可以訪問 #獲取當前ip的歷史訪問記錄 history = VISIT_RECORD.get(remote_addr) #初始化訪問記錄 self.history = history #如果有歷史訪問記錄,並且最早一次的訪問記錄離當前時間超過60s,就刪除最早的那個訪問記錄, #只要為True,就一直迴圈刪除最早的一次訪問記錄 while history and history[-1] < ctime - 60: history.pop()#如果訪問記錄不超過三次,就把當前的訪問記錄插到第一個位置(pop刪除最後一個) if len(history) < 3: history.insert(0,ctime) return True def wait(self): '''還需要等多久才能訪問''' ctime = time.time() return 60 - (ctime - self.history[-1])
(2)settings中全域性配置節流
#全域性 REST_FRAMEWORK = { #節流 "DEFAULT_THROTTLE_CLASSES":['API.utils.throttle.VisitThrottle'], }
(3)現在訪問auth看看結果:
- 60s內訪問次數超過三次,會限制訪問
- 提示剩餘多少時間可以訪問
接著訪問
節流原始碼分析
(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 #對原始request進行加工,豐富了一些功能 #Request( # request, # parsers=self.get_parsers(), # authenticators=self.get_authenticators(), # negotiator=self.get_content_negotiator(), # parser_context=parser_context # ) #request(原始request,[BasicAuthentications物件,]) #獲取原生request,request._request #獲取認證類的物件,request.authticators #1.封裝request request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: #2.認證 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) self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
(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 the incoming request is permitted #4.實現認證 self.perform_authentication(request) #5.許可權判斷 self.check_permissions(request) #6.控制訪問頻率 self.check_throttles(request)
(3)check_throttles
裡面有個allow_request
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())
(4)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]
(5)thtottle_classes
內建節流類
上面是寫的自定義節流,drf內建了很多節流的類,用起來比較方便。
(1)BaseThrottle
- 自己要寫allow_request和wait方法
- get_ident就是獲取ip
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') 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
(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): 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)
我們可以通過繼承SimpleRateThrottle類,來實現節流,會更加的簡單,因為SimpleRateThrottle裡面都幫我們寫好了
(1)throttle.py
from rest_framework.throttling import SimpleRateThrottle class VisitThrottle(SimpleRateThrottle): '''匿名使用者60s只能訪問三次(根據ip)''' scope = 'NBA' #這裡面的值,自己隨便定義,settings裡面根據這個值配置Rate def get_cache_key(self, request, view): #通過ip限制節流 return self.get_ident(request) class UserThrottle(SimpleRateThrottle): '''登入使用者60s可以訪問10次''' scope = 'NBAUser' #這裡面的值,自己隨便定義,settings裡面根據這個值配置Rate def get_cache_key(self, request, view): return request.user.username
(2)settings.py
#全域性 REST_FRAMEWORK = { #節流 "DEFAULT_THROTTLE_CLASSES":['API.utils.throttle.UserThrottle'], #全域性配置,登入使用者節流限制(10/m) "DEFAULT_THROTTLE_RATES":{ 'NBA':'3/m', #沒登入使用者3/m,NBA就是scope定義的值 'NBAUser':'10/m', #登入使用者10/m,NBAUser就是scope定義的值 } }
(3)views.py
區域性配置方法
class AuthView(APIView): . . . # 預設的節流是登入使用者(10/m),AuthView不需要登入,這裡用匿名使用者的節流(3/m) throttle_classes = [VisitThrottle,]
.
.
from django.shortcuts import render,HttpResponse from django.http import JsonResponse from rest_framework.views import APIView from API import models from rest_framework.request import Request from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication from API.utils.permission import SVIPPremission,MyPremission from API.utils.throttle import VisitThrottle ORDER_DICT = { 1:{ 'name':'apple', 'price':15 }, 2:{ 'name':'dog', 'price':100 } } def md5(user): import hashlib import time #當前時間,相當於生成一個隨機的字串 ctime = str(time.time()) m = hashlib.md5(bytes(user,encoding='utf-8')) m.update(bytes(ctime,encoding='utf-8')) return m.hexdigest() class AuthView(APIView): '''用於使用者登入驗證''' authentication_classes = [] #裡面為空,代表不需要認證 permission_classes = [] #不裡面為空,代表不需要許可權 # 預設的節流是登入使用者(10/m),AuthView不需要登入,這裡用匿名使用者的節流(3/m) throttle_classes = [VisitThrottle,] def post(self,request,*args,**kwargs): ret = {'code':1000,'msg':None} try: user = request._request.POST.get('username') pwd = request._request.POST.get('password') obj = models.UserInfo.objects.filter(username=user,password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = '使用者名稱或密碼錯誤' #為使用者建立token token = md5(user) #存在就更新,不存在就建立 models.UserToken.objects.update_or_create(user=obj,defaults={'token':token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret) class OrderView(APIView): ''' 訂單相關業務(只有SVIP使用者才能看) ''' def get(self,request,*args,**kwargs): self.dispatch #request.user #request.auth ret = {'code':1000,'msg':None,'data':None} try: ret['data'] = ORDER_DICT except Exception as e: pass return JsonResponse(ret) class UserInfoView(APIView): ''' 訂單相關業務(普通使用者和VIP使用者可以看) ''' permission_classes = [MyPremission,] #不用全域性的許可權配置的話,這裡就要寫自己的區域性許可權 def get(self,request,*args,**kwargs): print(request.user) return HttpResponse('使用者資訊')views.py
說明:
- API.utils.throttle.UserThrottle 這個是全域性配置(根據ip限制,10/m)
- DEFAULT_THROTTLE_RATES --->>>設定訪問頻率的
- throttle_classes = [VisitThrottle,] --->>>區域性配置(不適用settings裡面預設的全域性配置)
總結
基本使用
- 建立類,繼承BaseThrottle, 實現:allow_request ,wait
- 建立類,繼承SimpleRateThrottle, 實現: get_cache_key, scope='NBA' (配置檔案中的key)
全域性
#節流 "DEFAULT_THROTTLE_CLASSES":['API.utils.throttle.UserThrottle'], #全域性配置,登入使用者節流限制(10/m) "DEFAULT_THROTTLE_RATES":{ 'NBA':'3/m', #沒登入使用者3/m,NBA就是scope定義的值 'NBAUser':'10/m', #登入使用者10/m,NBAUser就是scope定義的值 } }
區域性
throttle_classes = [VisitThrottle,]
所有程式碼
認證、許可權和節流
# MyProject/urls.py from django.contrib import admin from django.urls import path from API.views import AuthView,OrderView,UserInfoView urlpatterns = [ path('admin/', admin.site.urls), path('api/v1/auth/',AuthView.as_view()), path('api/v1/order/',OrderView.as_view()), path('api/v1/info/',UserInfoView.as_view()), ]MyProject/urls.py
#全域性 REST_FRAMEWORK = { #認證 "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',], #許可權 "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPermission'], #節流 "DEFAULT_THROTTLE_CLASSES":['API.utils.throttle.UserThrottle'], #全域性配置,登入使用者節流限制(10/m) "DEFAULT_THROTTLE_RATES":{ 'NBA':'3/m', #沒登入使用者3/m,NBA就是scope定義的值 'NBAUser':'10/m', #登入使用者10/m,NBAUser就是scope定義的值 } }settings.py
# API/models.py from django.db import models class UserInfo(models.Model): USER_TYPE = ( (1,'普通使用者'), (2,'VIP'), (3,'SVIP') ) user_type = models.IntegerField(choices=USER_TYPE) username = models.CharField(max_length=32) password = models.CharField(max_length=64) class UserToken(models.Model): user = models.OneToOneField(UserInfo,on_delete=models.CASCADE) token = models.CharField(max_length=64)API/models.py
# API/views.py from django.shortcuts import render,HttpResponse from django.http import JsonResponse from rest_framework.views import APIView from API import models from rest_framework.request import Request from rest_framework import exceptions from rest_framework.authentication import BaseAuthentication from API.utils.permission import SVIPPermission,MyPermission from API.utils.throttle import VisitThrottle ORDER_DICT = { 1:{ 'name':'apple', 'price':15 }, 2:{ 'name':'dog', 'price':100 } } def md5(user): import hashlib import time #當前時間,相當於生成一個隨機的字串 ctime = str(time.time()) m = hashlib.md5(bytes(user,encoding='utf-8')) m.update(bytes(ctime,encoding='utf-8')) return m.hexdigest() class AuthView(APIView): '''用於使用者登入驗證''' authentication_classes = [] #裡面為空,代表不需要認證 permission_classes = [] #不裡面為空,代表不需要許可權 # 預設的節流是登入使用者(10/m),AuthView不需要登入,這裡用匿名使用者的節流(3/m) throttle_classes = [VisitThrottle,] def post(self,request,*args,**kwargs): ret = {'code':1000,'msg':None} try: user = request._request.POST.get('username') pwd = request._request.POST.get('password') obj = models.UserInfo.objects.filter(username=user,password=pwd).first() if not obj: ret['code'] = 1001 ret['msg'] = '使用者名稱或密碼錯誤' #為使用者建立token token = md5(user) #存在就更新,不存在就建立 models.UserToken.objects.update_or_create(user=obj,defaults={'token':token}) ret['token'] = token except Exception as e: ret['code'] = 1002 ret['msg'] = '請求異常' return JsonResponse(ret) class OrderView(APIView): ''' 訂單相關業務(只有SVIP使用者才能看) ''' def get(self,request,*args,**kwargs): self.dispatch #request.user #request.auth ret = {'code':1000,'msg':None,'data':None} try: ret['data'] = ORDER_DICT except Exception as e: pass return JsonResponse(ret) class UserInfoView(APIView): ''' 訂單相關業務(普通使用者和VIP使用者可以看) ''' permission_classes = [MyPermission,] #不用全域性的許可權配置的話,這裡就要寫自己的區域性許可權 def get(self,request,*args,**kwargs): print(request.user) return HttpResponse('使用者資訊')API/views.py
# API/utils/auth/py from rest_framework import exceptions from API import models from rest_framework.authentication import BaseAuthentication class Authentication(BaseAuthentication): '''用於使用者登入驗證''' def authenticate(self,request): token = request._request.GET.get('token') token_obj = models.UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed('使用者認證失敗') #在rest framework內部會將這兩個欄位賦值給request,以供後續操作使用 return (token_obj.user,token_obj) def authenticate_header(self, request): passAPI/utils/auth/py
相關推薦
3---Django rest framework原始碼分析(3)----節流
Django rest framework原始碼分析(3)----節流 目錄 新增節流 自定義節流的方法 限制60s
Django rest framework原始碼分析(3)----節流
目錄 新增節流 自定義節流的方法 限制60s內只能訪問3次 (1)API資料夾下面新建throttle.py,程式碼如下: # utils/throttle.py from rest_framework.throttling import BaseThrottle impo
Django rest framework原始碼分析(4)----版本
目錄 版本 新建一個工程Myproject和一個app名為api (1)api/models.py from django.db import models class UserInfo(models.Model): USER_TYPE = ( (1,'普通
Django rest framework原始碼分析(2)----許可權
目錄 新增許可權 (1)API/utils資料夾下新建premission.py檔案,程式碼如下: message是當沒有許可權時,提示的資訊 # utils/permission.py class SVIPPremission(object): message =
Django rest framework原始碼分析(1)----認證
目錄 一、基礎 1.1.安裝 兩種方式: pip install djangorestframework 1.2.需要先了解的一些知識 理解下面兩個知識點非常重要,django-rest-framework原始碼中到處都是基於CBV和麵向物件的封裝 (1)面向物件封裝的兩大特性
Mybatis原始碼分析(3)—— 從Mybatis的視角去看Bean的初始化流程
不涉及Spring完整的啟動流程,僅僅從Mybatis的視角去分析幾個關鍵的方法,找到Mybatis是如何通過這幾個擴充套件點植入進去的,反過來看Spring是如何設計,埋下這些伏筆,實現其可擴充套件性。 springContext-mybatis.xml的配置: <!--
PackageManagerService 原始碼分析 (3) ApplicationInfo 相關
PackageParser.Package.ApplicationInfo 類 Application 是PackageParser.Package 的一個成員 一 . 呼叫情況: 1.PackageParser.parseBaseApk
Netflix Eureka原始碼分析(3)——listener(EurekaBootStrap監聽類)分析
web.xml中的listener: <listener> <listener-class>com.netflix.eureka.EurekaBootStrap</listener-class> </listener>
JDK原始碼分析(3)HashSet
HashSet HashSet簡介 HashSet特點 非執行緒安全 允許null值 新增值得時候會先獲取物件的hashCode方法,如果hashCode 方法返回的值一致,則再呼叫equals方法判斷是否一致,如果不一致才add元素。 注意: 對於HashS
JDK原始碼分析(3)HashMap
JDK版本 HashMap簡介 HashMap基於雜湊表的 Map 介面的實現。此實現提供所有可選的對映操作,並允許使用 null 值和 null 鍵。(除了不同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證對映的順序,特別是它不保證該順序恆久不變。
tensorflowV1.11-原始碼分析(3)
取得環境配置變數,環境變數設定提示問題獲得環境變數值,只能為True或False def get_var(environ_cp, var_name, query_item, enabled_by_default,
【學以致用】android功能實現7---android8.0 Launcher獲取快捷方式原始碼分析(3)
獲取完快捷方式的資訊之後,便是如何具體的將快捷方式的名字,圖示和開啟方式放置到桌面上。 最後一步addAndBindAddedWorkspaceItems( new LazyShortcutsProvider(context.getAppl
Mahout協同過濾演算法原始碼分析(3)--parallelALS
Mahout版本:0.7,hadoop版本:1.0.4,jdk:1.7.0_25 64bit。接上篇,此篇分析parallelALS的initializeM函式和for迴圈(for迴圈裡面含有一個QR分解,此篇只分析到這裡為止)。parallelALS的原始碼對應為:org.
TensorFlow學習筆記之原始碼分析(3)---- retrain.py
"""簡單呼叫Inception V3架構模型的學習在tensorboard顯示了摘要。 這個例子展示瞭如何採取一個Inception V3架構模型訓練ImageNet影象和訓練新的頂層,可以識別其他類的影象。 每個影象裡,頂層接收作為輸入的一個2048維向量。這
【Web容器】Tomcat原始碼分析(3)-生命週期管理
前言 從server.xml檔案解析出來的各個物件都是容器,比如:Server、Service、Connector等。這些容器都具有新建、初始化完成、啟動、停止、失敗、銷燬等狀態。tomcat的實現提供了對這些容器的生命週期管理,本文將通過對Tomcat7.0的
HLS學習(四)HLSDownloader原始碼分析(3)總體流程
總體流程 下載一個媒體檔案的流程 1、初始化 2、根據URL下載m3u8檔案(即PlayList檔案) 3、判斷m3u8檔案的型別,是Master PlayList還是Media PlayList 4、如果是Master PlayList,那麼分析這個PlayList
第二人生原始碼分析(3)程式入口點
#041 llwarns << "Our exception handler (" << (void *)LLWinDebug::handleException << ") replaced with " << prev_filter <&
AFNetWorking(3.0)原始碼分析(五)——AFHTTPRequestSerializer & AFHTTPResponseSerializer
在前面的幾篇部落格中,我們分析了AFURLSessionMangerd以及它的子類AFHTTPSessionManager。我們對AF的主要兩個類,有了一個比較全面的瞭解。 對於AFHTTPSessionManager,當其在要傳送請求時,會呼叫AFHTTPRequestSerial
AFNetWorking(3.0)原始碼分析(四)——AFHTTPSessionManager(2)
在上一篇部落格中,我們分析了AFHTTPSessionManager,以及它是如何實現GET/HEAD/PATCH/DELETE相關介面的。 我們還剩下POST相關介面沒有分析,在這篇部落格裡面,我們就來分析一下POST相關介面是如何實現的。 multipart/form-data請
Android進階3:Activity原始碼分析(2) —— Activity啟動和銷燬流程(8.0)
上篇文章講述了app從啟動建立Activity呼叫onCreate,onStart, onResume方法,這篇文章講述一下Activity啟動的另一個切入點:startActivity方法,啟動Activity。 通過上一篇文章,我們總結一下: 1:A