DAY100 - Rest Framework(五)- 頻率控制組件和解釋器

一·、頻率組件

1.自定義頻率簡單使用

# myThrottle.py

import time
class VisitThrottle():
    '''
        {'ip1':[時間1 ,時間2],
        'ip2':[時間1, ],
        }
    '''
    visit_dic = {}
    def __init__(self):
        self.history = None
        self.time = time.time

    def allow_request(self, request, view):
        # 取出訪問者ip
        ip = request.META.get('REMOTE_ADDR')
        now_time = self.time()
        # 判斷當前ip不在訪問字典裏,添加進去,而且直接返回True,表示第一次訪問
        if ip not in self.visit_dic:
            self.visit_dic[ip] = [now_time, ]
            return True

        # 用戶不是第一次訪問,直接到這裏執行
        # 取出用戶對應的列表
        self.history = self.visit_dic[ip]

        # 循環判斷當前ip的列表,有值,而且列表的最後一個時間加上60s仍是小於當前時間,把這種數據pop掉,這樣列表中只有60s之內的訪問時間
        while self.history and self.history[-1] + 60 < now_time:
            self.history.pop()

        # 判斷,當列表小於3,說明一分鐘之內訪問不足三次,把當前時間插入到列表第一個位置,返回True,順利經過
        if len(self.history)<3:
            self.visit_dic[ip].insert(0,now_time)
            return True
        # 當大於等於3,說明一分鐘內訪問超過三次,返回False驗證失敗
        return False

    # 當訪問被限制後,距離下次能夠訪問還剩的時間
    def wait(self):
        now_time = self.time()
        return 60 - (now_time - self.history[0])
#views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from app01.myThrottle import VisitThrottle
from rest_framework.exceptions import Throttled


# Create your views here.

class Book(APIView):
    # 局部使用
    throttle_classes = [VisitThrottle, ]

    def get(self, request):
        return HttpResponse('ok')
    
    # 訪問頻率限制超出,錯誤信息(中文)
    def throttled(self, request, wait):
        # 自定義異常,並繼承Throttled異常
        class MyThrottled(Throttled):
            default_detail = '距離下次訪問'
            extra_detail_singular = '還有 {wait} second.'
            extra_detail_plural = '還有 {wait} seconds.'
        # 拋出異常
        raise MyThrottled(wait)
     
# 全局使用
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': ['app01.myThrottle.VisitThrottle', ]
}

# 全局使用下局部禁用
class Book(APIView):
    throttle_classes = []

2.內置頻率簡單使用

# settings.py
REST_FRAMEWORK = {
    # 全局使用
    'DEFAULT_THROTTLE_CLASSES':['app01.myThrottle.VisitThrottle',],
    'DEFAULT_THROTTLE_RATES': {
        # time: 3次/每分鐘
        'time': '3/m'
    }
}
# myThrottle.py
from rest_framework.throttling import SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    # 全局配置訪問頻率
    scope = 'time'
    # 局部配置訪問頻率
    # rate = '3/m'
    
    def get_cache_key(self, request, view):
        # 對什麼的訪問頻率限制就返回什麼
        # get_ident():SimpleRateThrottle的父類的方法,得到訪問的IP
        return self.get_ident(request)#①

# ①
# get_ident()
def get_ident(self, request):
    .....
    
    xff = request.META.get('HTTP_X_FORWARDED_FOR')
    # request.META.get('REMOTE_ADDR')
    # 從META裏得到訪問的IP地址
    remote_addr = request.META.get('REMOTE_ADDR')
    num_proxies = api_settings.NUM_PROXIES
    .....
    
    return ''.join(xff.split()) if xff else remote_addr
#views.py
from django.shortcuts import render, HttpResponse
from rest_framework.views import APIView
from app01.myThrottle import VisitThrottle
from rest_framework.exceptions import Throttled


# Create your views here.

class Book(APIView):
    # 局部使用
    throttle_classes = [VisitThrottle, ]
    # 局部禁用
    # throttle_classes = []

    def get(self, request):
        return HttpResponse('ok')
    
    # 訪問頻率限制超出,錯誤信息(中文)
    def throttled(self, request, wait):
        # 自定義異常,並繼承Throttled異常
        class MyThrottled(Throttled):
            default_detail = '距離下次訪問'
            extra_detail_singular = '還有 {wait} second.'
            extra_detail_plural = '還有 {wait} seconds.'
        # 拋出異常
        raise MyThrottled(wait)

3.頻率認證源碼分析

# 第一步
# APIView類
def dispatch(self, request, *args, **kwargs):
    ........
    # 重點是這個,這是認證、頻率以及權限相關的
    self.initial(request, *args, **kwargs)
    ........
# 第二步
# APIView類
def initial(self, request, *args, **kwargs):
    ........
    self.perform_authentication(request)
    self.check_permissions(request)
    # 頻率
    self.check_throttles(request)
# 第三步
# APIView類
def check_throttles(self, request):
    for throttle in self.get_throttles():
        # throttle.allow_request的返回值是bool類型
        if not throttle.allow_request(request, self):
            # 若是返回False,觸發self.throttled()的執行,拋出異常
            # throttle.wait():返回值是數值型,倒計時時間
           self.throttled(request, throttle.wait())
        
            
            
# self.get_throttles()的來歷
def get_throttles(self):
    return [throttle() for throttle in self.throttle_classes]

# self.throttled(request, throttle.wait())
def throttled(self, request, wait):
    raise exceptions.Throttled(wait)

4.內置頻率控制類SimpleRateThrottle源碼分析

# 第一步
# 從自定義的頻率控制看出allow_request()是控制頻率的主要代碼
class SimpleRateThrottle(BaseThrottle):
    ......
    timer = time.time
    ......
    def allow_request(self, request, view):
        # 1.self.rate
         if self.rate is None:
            return True
        
        # 2.get_cache_key 得到限制的對象
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True
        
        # self.cache.get(self.key, [])
        # 這個就至關於自定義頻率控制中的大字典,是從緩存中得到
        # 若是self.cache中有對應self.key的列表,就賦值給self.history
        # 沒有則建立並給默認值[]空列表
        self.history = self.cache.get(self.key, [])
        
        # timer = time.time
        # 加上()執行,得到當前的時間戳
        self.now = self.timer()
        
        # 3.self.num_requests和self.duration
        # 循環判斷,當前ip的列表有值,而且列表的當前時間減去self.duration小於最後一個時間,把這種數據pop掉,這樣列表中只有self.duration之內的訪問時間
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        
        # 4.self.throttle_failure()和self.throttle_success()
        # 判斷當列表大於self.num_requests,說明一分鐘之內訪問大於或等於self.num_requests次,執行self.throttle_failure()方法返回False驗證失敗
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        
        # 當列表小於self.num_requests時,執行self.throttle_success()方法返回True
        return self.throttle_success()
# 1.
# self.rate
class SimpleRateThrottle(BaseThrottle):
    .......
    # 全局屬性
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES 
    .......
    def __init__(self):
        
        # 初始化時判斷繼承SimpleRateThrottle類的裏面有沒有rate,沒有就執行self.get_rate()去找子類裏scope
        if not getattr(self, 'rate', None):
            # self.rate = scope所表明的值
            self.rate = self.get_rate()
            
        self.num_requests, self.duration = self.parse_rate(self.rate)

    
# self.get_rate
    def get_rate(self):
        # 若是沒有scope就會報錯
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %self.__class__.__name__)
            raise ImproperlyConfigured(msg)
        # 若是有的話就去self的THROTTLE_RATES裏找,也就是去setting裏的DEFAULT_THROTTLE_RATES中找scope值對應的
        try:
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)
# 2.
# self.get_cache_key
class SimpleRateThrottle(BaseThrottle):
# 若是繼承SimpleRateThrottle類的裏面不寫,就會拋出異常,因此必須寫
    def get_cache_key(self, request, view):
        raise NotImplementedError('.get_cache_key() must be overridden')
# 3.
# self.num_requests和self.duration
class SimpleRateThrottle(BaseThrottle):
    .......
    # 全局屬性
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES 
    .......
    def __init__(self):
        if not getattr(self, 'rate', None):
            # self.rate = scope所表明的值,好比‘3/m’
            self.rate = self.get_rate()
        # 把self.rate看成參數給self.parse_rate(),而且把返回值解壓分給了self.num_requests和self.duration
        self.num_requests, self.duration = self.parse_rate(self.rate)
        
        
# self.parse_rate
    def parse_rate(self, rate):
        if rate is None:
            return (None, None)
        # 假設rate = ‘3/m’
        # num = '3',period = 'm'
        num, period = rate.split('/')
        # num_requests = 3 
        num_requests = int(num)
        # duration = 60
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        # return(3,60) 表示3次/60秒
        return (num_requests, duration)
#4
# self.throttle_failure()和self.throttle_success()
class SimpleRateThrottle(BaseThrottle):
    def throttle_success(self):
        # 把當前訪問時間插入self.history的第一個
        self.history.insert(0, self.now)
        # 而且存入緩存中
        self.cache.set(self.key, self.history, self.duration)
        return True

    def throttle_failure(self):
        # 直接返回False
        return False

2、解釋器

根據請求頭 content-type 選擇對應的解析器對請求體內容進行處理。python

有application/json,x-www-form-urlencoded,form-data等格式django

1.簡單使用

from rest_framework.parsers import FileUploadParser, MultiPartParser, JSONParser, FormParser

# 局部所有使用
# 視圖裏
    parser_classes = [FileUploadParser, MultiPartParser, JSONParser, FormParser]
    
# 局部單個使用,只使用指定的解釋器解釋
# 視圖裏
    parser_classes = [FileUploadParser,]
    
# 全局使用
# setting.py
REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES':[
        'rest_framework.parsers.JSONParser'
        'rest_framework.parsers.FormParser'
        'rest_framework.parsers.MultiPartParser'
    ]

}

2.源碼解析

# 在調用request.data時,才進行解析,由此入手
# request
    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            self._load_data_and_files()
        return self._full_data
# request
#  self._load_data_and_files()
def _load_data_and_files(self):
    if not _hasattr(self, '_data'):
        # self._parse() 解析以後 把解析的分到data和files裏
        self._data, self._files = self._parse()
    .......
# request
# self._parse()
def _parse(self):
    # media_type = 用戶請求頭裏content_type的值
    media_type = self.content_type

    
    #self裏就有content_type,傳入此函數
    parser = self.negotiator.select_parser(self, self.parsers)
    
    
# self.content_type 
def content_type(self):
        meta = self._request.META
        return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))
    
# self.parsers
# self.parsers Request對象實例化時傳進來的參數self.get_parsers()
def initialize_request(self, request, *args, **kwargs):
    
    parser_context = self.get_parser_context(request)
    return Request(
        request,
        parsers=self.get_parsers(),
        authenticators=self.get_authenticators(),
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context
    )

# self.get_parsers()
def get_parsers(self):
    # 先從類自己找,找不到去父類找即APIVIew 中的
    return [parser() for parser in self.parser_classes]
# self.negotiator.select_parser(self, self.parsers)
def select_parser(self, request, parsers):
    # parsers:parser_classes,也就是解析器列表
    # 循環解析器列表
    for parser in parsers:
        # parser.media_type:解析器所能解析的類型
        # request.content_type:請求中的解析方式
        # 若是一致返回解析器,不然返回None
        if media_type_matches(parser.media_type, request.content_type):
            return parser
    return None
相關文章
相關標籤/搜索