DRF之频率组件源码分析

【一】频率组件介绍

  • Django Rest Framework(DRF)中的频率组件是用于限制API端点的访问频率的一种机制。
  • 频率组件可以帮助你控制用户对API的请求频率,以防止滥用和DDoS攻击。
    • 比如某个接口,一分钟只能访问5次,超过了就得等
    • 按IP地址 限制
    • 按用户id 限制

【二】内置的频率类

  • Throttle 类:这是频率组件的基类,定义了频率限制的核心逻辑。它包括 allow_request()wait() 方法,用于检查是否允许请求以及在请求受限时应该等待多长时间。
  • AnonRateThrottle:基于认证用户的请求频率进行限制,继承自 Throttle 并实现了特定的频率限制算法
  • UserRateThrottle:基于匿名用户的请求频率进行限制,继承自 Throttle 并实现了特定的频率限制算法

【三】执行流程分析

  • DRF视图类可以包含一个 throttle_classes 属性,该属性定义了应用于特定视图的频率组件。
  • 在处理请求之前,DRF将遍历视图的 throttle_classes 列表,检查每个频率组件是否允许请求。
  • 频率组件的配置:你可以在DRF的设置中配置默认的频率限制,也可以在视图类中设置 throttle_classes 来覆盖默认配置。
  • 频率组件将根据这些配置来控制请求的频率。
  • 在请求到达API视图时,DRF会首先检查用户的请求是否在频率组件的允许范围内。

  • 如果请求超出了允许的频率限制,将返回一个HTTP 429 Too Many Requests响应。
def check_throttles(self, request):
"""
Check if request should be throttled.
Raises an appropriate exception if the request is throttled.
"""
# 创建一个空的 throttle_durations 列表,用于存储每个throttle对象返回的限制时间
throttle_durations = []
# 调用 self.get_throttles() 方法获取限制对象列表
# 遍历限制对象列表
for throttle in self.get_throttles():
# 对每个限制对象调用 allow_request(request, self) 方法来检查请求是否允许通过此限制
if not throttle.allow_request(request, self):
# 如果请求不允许通过限制(allow_request方法返回 False)
# 则调用 throttle.wait() 方法获取限制的时间间隔,并将其添加到 throttle_durations 列表中。
throttle_durations.append(throttle.wait()) # 如果存在 throttle_durations 列表,则说明请求被至少一个限制所影响
if throttle_durations:
# Filter out `None` values which may happen in case of config / rate
# changes, see #1438 # 在此列表中过滤掉 None 值(可能出现在配置或速率更改的情况下)
durations = [
duration for duration in throttle_durations
if duration is not None
] # 然后找到列表中的最大值作为限制的持续时间
duration = max(durations, default=None)
# 调用 self.throttled(request, duration) 方法
# 将请求和限制的持续时间传递给 throttled 方法,从而引发一个表示请求被频次限制的异常。
self.throttled(request, duration)
  • throttled
def throttled(self, request, wait):
"""
If request is throttled, determine what kind of exception to raise.
"""
# 抛出异常
raise exceptions.Throttled(wait)

【四】内置频率限制类源码分析

【0】BaseThrottle

from rest_framework.throttling import BaseThrottle
class BaseThrottle:
"""
Rate throttling of requests.
""" # 用于确定是否应该允许请求。
# 当请求到达API视图时,DRF会调用这个方法,传递request和view参数。
# 在自定义频率组件需要在这个方法中实现请求的频率控制逻辑。
# 如果允许请求,返回True;如果不允许请求,返回False。
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.
"""
# 从HTTP_X_FORWARDED_FOR头部获取客户端IP地址
xff = request.META.get('HTTP_X_FORWARDED_FOR')
# 获取请求地址IP
remote_addr = request.META.get('REMOTE_ADDR')
num_proxies = api_settings.NUM_PROXIES # 如果存在代理服务器且NUM_PROXIES设置大于0
if num_proxies is not None:
# 如果不存在代理服务器或者NUM_PROXIES设置为0
if num_proxies == 0 or xff is None:
# 返回REMOTE_ADDR中的IP地址
return remote_addr addrs = xff.split(',')
client_addr = addrs[-min(num_proxies, len(addrs))]
return client_addr.strip() # 提取最后一个代理服务器的IP地址
return ''.join(xff.split()) if xff else remote_addr # 返回一个建议的等待时间(以秒为单位),在下一个请求之前应该等待多长时间。
# 如果你的频率组件希望为某些请求推荐等待时间,可以在这个方法中实现。
# 否则,可以返回None。
def wait(self):
"""
Optionally, return a recommended number of seconds to wait before
the next request.
"""
return None

【1】AnonRateThrottle

from rest_framework.throttling import 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 and request.user.is_authenticated:
# 不做处理
return None # Only throttle unauthenticated requests. # 将 scope 和 ident 插入到缓存键格式中,生成最终的缓存键
return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request)
}

【2】UserRateThrottle

from rest_framework.throttling import 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 and request.user.is_authenticated:
# 返回用户的ID
ident = request.user.pk
else:
# 默认使用请求地址IP进行限制
ident = self.get_ident(request) # 将 scope 和 ident 插入到缓存键格式中,生成最终的缓存键
return self.cache_format % {
'scope': self.scope,
'ident': ident
}

【3】SimpleRateThrottle

from rest_framework.throttling import 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 Throttle
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.
""" # 这是用于存储频率限制信息的缓存,通常是Django中的缓存设置。
# 默认情况下,它使用了default_cache,但你可以根据需要更改为其他缓存。
cache = default_cache # timer 属性:用于获取当前时间的函数,默认是time.time,用于计算请求的时间间隔。
timer = time.time # 缓存键的格式,其中的 %(scope)s 和 %(ident)s 将在生成缓存键时替换为相应的值。
cache_format = 'throttle_%(scope)s_%(ident)s' # 用于确定频率限制的作用域。
# 你可以将其设置为特定的作用域,以便为不同的API端点应用不同的频率限制。
# 这里是我们在配置文件中配置的自定义的限制频率字段
scope = None # 包含默认频率限制的字典。
# 你可以在DRF的设置中配置默认频率限制,然后在频率组件中使用scope来引用这些默认限制。
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES def __init__(self): # 解析 rate 属性,该属性是频率限制的字符串表示形式,例如 '5/minute'
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.
"""
# 从请求对象本身校验是否存在 scope 限制频率关键字
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:
# 不存在则返回None
return (None, None) # 按照 / 切割限制条件 , 这就是上面我们定义 5/minute 格式的原因
num, period = rate.split('/') # 转换我们的到的前面的数字
num_requests = int(num) # minute 只拿第一个字母
# second : s 秒
# minute : m 分
# hour : h 时
# day : d 天
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:
# 不存在则返回True,继续执行视图函数,不对视图函数做限制
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.
""" # 如果请求历史记录 self.history 存在(表示之前有请求被记录)
if self.history:
# 首先计算剩余的持续时间 remaining_duration。
# 这个剩余的持续时间表示离下一个请求窗口还有多少秒。
remaining_duration = self.duration - (self.now - self.history[-1])
else:
# 如果请求历史记录不存在(即没有之前的请求被记录)
# remaining_duration 设置为频率限制的持续时间 self.duration。
remaining_duration = self.duration # 计算可用的请求数 available_requests,这是当前请求窗口内可用的请求数
# 总请求数 self.num_requests 减去 已记录的请求数量len(self.history) 并加上1来计算的
# 加1是为了包括当前请求
available_requests = self.num_requests - len(self.history) + 1 # 如果可用的请求数小于等于0(表示请求已经超过了频率限制),则返回 None
if available_requests <= 0:
return None # 否则返回建议的等待时间,即 remaining_duration 除以 可用的请求数 available_requests。
return remaining_duration / float(available_requests)

【4】ScopedRateThrottle

from rest_framework.throttling import 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.
""" # 该属性定义了视图中用于确定频率限制作用域的属性名称,默认为 'throttle_scope'。
# 这意味着在视图中,你可以通过设置 throttle_scope 属性来指定频率限制作用域。
scope_attr = 'throttle_scope' def __init__(self):
# 在初始化时,它覆盖了通常的 SimpleRateThrottle 初始化,因为在初始化时无法确定频率限制,而需要在视图被调用时才能确定。
# 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. # 首先检查视图是否有 throttle_scope 属性,如果没有,就直接允许请求
self.scope = getattr(view, self.scope_attr, None) # If a view does not have a `throttle_scope` always allow the request # 如果视图有 throttle_scope 属性,它会根据作用域来确定频率限制
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) # 继续调用 super().allow_request(request, view) 来执行实际的频率限制检查
return super().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 and request.user.is_authenticated:
# 如果是,将用户的主键作为标识符
ident = request.user.pk
else:
# 如果未认证,将使用 get_ident 方法获取请求的标识符
ident = self.get_ident(request) # 将 scope 和 ident 插入到缓存键格式中,生成最终的缓存键
return self.cache_format % {
'scope': self.scope,
'ident': ident
}

【五】频率类的一般使用步骤(固定用法)

【1】创建自定义频次认证类

  • 首先,需要创建一个频次认证类,并让它继承自SimpleRateThrottle
  • 这个类负责限制对某些操作或资源的访问频次。

【2】重写 get_cache_key 方法

  • 在创建频次认证类后,需要重写其中的get_cache_key方法。
  • 这个方法决定了如何从请求中获取缓存键值,以便在缓存中存储和检索频次信息。
  • 可以考虑使用以下信息作为缓存键值的组成部分:
    • 用户身份:可以使用用户的唯一标识符或者请求中的某些认证信息。
    • 资源标识:如果需要对不同的资源进行频次限制,可以加入资源标识。
    • 操作类型:如果需要对不同的操作类型进行频次限制,可以加入操作类型。
  • 根据具体情况,可以将这些信息组合起来构成一个唯一的缓存键值,并在get_cache_key方法中返回。
  • 最后,在频次认证类中,可以添加一个类属性来自定义命名。
  • 这个属性可以用于在缓存中存储频次信息时使用。
  • 可以考虑使用以下方式定义类属性:
    • 在使用时,可以通过MyThrottle.cache_name来访问这个自定义属性。
from rest_framework.throttling import SimpleRateThrottle

class SimpleRate_Throttle(SimpleRateThrottle):
# 自定义限制频次关键字,在配置文件中需要以这个关键字限制频率
scope = "scope_throttle" # 重写 get_cache_key 方法
# 返回什么就用什么作为限制条件
def get_cache_key(self, request, view):
# 限制条件:IP地址/用户ID
# 返回客户端IP地址
return request.META.get('REMOTE_ADDR') # 选择性重写 -- 这里是伪代码
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 # 自定义频率限制的等待时间计算
# 例如,你可以根据客户端IP来调整等待时间,更频繁的IP等待时间短一些
ip = self.get_cache_key(self.request, self.view)
if ip == "特定IP地址":
# 对于特定IP,可以设置更短的等待时间
return remaining_duration / float(available_requests) * 0.5
else:
# 对于其他IP,使用默认等待时间
return remaining_duration / float(available_requests)

【3】配置文件中配置

  • 配置文件中需要对频次认证类进行相关配置
  • 具体配置内容包括认证类的名称、参数设置和限制的频次等信息。
    • 这里的限制关键字的变量名要和上面自定义限制类中的变量名一致
  • 例如,在Django框架中,可以在settings.py或其他相关的配置文件中添加如下内容
    • 'myapp.apithrottling.SimpleRate_Throttle' 是频次认证类的名称
    • scope_throttle 是前述创建的频次认证类的类属性自定义命名
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
# 自己的频率限制类的位置 --- 全局生效
'myapp.apithrottling.SimpleRate_Throttle',
),
'DEFAULT_THROTTLE_RATES': {
'scope_throttle': '5/minute', # 设置该频次认证类的限制频次为每分钟最多5次请求
}
}

【4】局部使用

  • 注意,只有继承了APIView 及其 子类的视图才会走三大认证
from rest_framework.decorators import throttle_classes

# @throttle_classes([MyThrottle]) 方式一:作为类的装饰器使用
class BookView(APIView):
# 方式二:在视图类中应用限制类
throttle_classes = [SimpleRate_Throttle]

【5】全局使用,局部禁用

class BookView(APIView):

    # 将频率限制类的限制类列表清空即可
throttle_classes = []

DRF之频率组件源码分析的更多相关文章

  1. Django框架之drf:8、断点调试,权限、认证、频率组件源码分析,基于APIView编写分页,异常处理

    Django框架之drf 一.断点调式使用 ​ 指,在我们编写代码的时候,程序运行出现报错是无可避免的,当程序 出现报错时,我们需要找到出现报错的代码进行修改,如果时简短的代码很容易就可以找到报错位置 ...

  2. DRF的Serializer组件(源码分析)

    DRF的Serializer组件(源码分析) 1. 数据校验 drf中为我们提供了Serializer,他主要有两大功能: 对请求数据校验(底层调用Django的Form和ModelForm) 对数据 ...

  3. DRF的认证组件(源码分析)

    DRF认证组件(源码分析) 1. 数据库建立用户表 在drf中也给我们提供了 认证组件 ,帮助我们快速实现认证相关的功能,例如: # models.py from django.db import m ...

  4. Django框架之DRF 认证组件源码分析、权限组件源码分析、频率组件源码分析

    认证组件 权限组件 频率组件

  5. Django的rest_framework的权限组件和频率组件源码分析

    前言: Django的rest_framework一共有三大组件,分别为认证组件:perform_authentication,权限组件:check_permissions,频率组件:check_th ...

  6. DRF的限流组件(源码分析)

    DRF限流组件(源码分析) 限流,限制用户访问频率,例如:用户1分钟最多访问100次 或者 短信验证码一天每天可以发送50次, 防止盗刷. 对于匿名用户,使用用户IP作为唯一标识. 对于登录用户,使用 ...

  7. Django-restframework 源码之认证组件源码分析

    Django-restframework 源码之认证组件源码分析 一 前言 之前在 Django-restframework 的流程分析博客中,把最重要的关于认证.权限和频率的方法找到了.该方法是 A ...

  8. Django(64)频率认证源码分析与自定义频率认证

    前言 有时候我们发送手机验证码,会发现1分钟只能发送1次,这是做了频率限制,限制的时间次数,都由开发者自己决定 频率认证源码分析 def check_throttles(self, request): ...

  9. element-ui 组件源码分析整理笔记目录

    element-ui button组件 radio组件源码分析整理笔记(一) element-ui switch组件源码分析整理笔记(二) element-ui inputNumber.Card .B ...

  10. ceph-csi组件源码分析(1)-组件介绍与部署yaml分析

    更多ceph-csi其他源码分析,请查看下面这篇博文:kubernetes ceph-csi分析目录导航 ceph-csi组件源码分析(1)-组件介绍与部署yaml分析 基于tag v3.0.0 ht ...

随机推荐

  1. K8S组件详解

    K8S的控制平面.和工作节点是集群正常运行的核心,通过这两部分的协同工作,K8S才能够实现高效的容器编排.管理.和自动化运维. K8S Kubernetes(简称K8s),是一个开源的容器编排平台,用 ...

  2. Ubuntu修改启动顺序以及系统时间同步问题

    Ubuntu修改启动顺序以及系统时间同步问题 修改启动顺序 选择要优先启动的序号,从0开始计数 修改配置文件 sudo vim /etc/default/grub 使用这个命令刷新一下 sudo up ...

  3. Superfetch/SysMain

    卡的不要不要的 Windows 服务中有一个叫 Superfetch. 现在换了个马甲叫 SysMain 本意是好的,超级预读功能可以帮助大型软件极大提升启动加载时间,但是经常抽风至少我觉得 导致磁盘 ...

  4. 让 AI 对接和 MySQL 数据库对话

    一.场景说明: 通过 AI 连接 MySQL 结构化数据库表,预期实现通过AI对话数据库表,快速了解数据情况,能够进行简单的汇总统计,快是实现问答. 二.资源准备: 需提前准备以下内容: AI大语言模 ...

  5. 快速理解 MCP 与 A2A 协议的关系,它们如何协作构建复杂AI系统

    近期关于MCP协议的讨论非常热门,主要因为通过MCP协议通过标准化接口为 AI 应用与外部数据源建立统一交互通道,这使得大模型可以与外部数据源或工具进行交互,从而实现各种专业场景下的智能应用.关于如何 ...

  6. 微信支付功能的设计实现与关键实践(UniApp+Java)全代码

    感觉本篇对你有帮助可以关注一下我的微信公众号(深入浅出谈java),会不定期更新知识和面试资料.技巧!!! 温馨提醒:阅读时可打开导航栏 概述 在移动互联网时代,支付功能已成为应用开发的核心能力之一. ...

  7. 聊聊@Autowired与@Resource的区别

    1. 前言 从事过很多家公司,见过很多项目,发现@Autowired和@Resource的使用都是一样的乱, 一个项目中有使用@Autowired的,有使用@Resource的, 甚至有的类中一会儿使 ...

  8. 【记录】IDA和Ollydbg查看指令地址及地址对应的指令

    文章目录 在IDA中查看指令地址 在Ollydbg中查看指令地址 在Ollydbg中查看地址对应的指令 在IDA中查看指令地址 在Ollydbg中查看指令地址 ollydbg在对应指令处,右键-查看- ...

  9. 挑战零基础用CodeBuddy做一款音视频处理软件

    朋友们,我最近不是一直在捣鼓小软件嘛!手头这个叫TransDuck的音视频处理工具刚有点小爆的趋势,反馈意见里也是收到不少关于SaaS版本的问题,比如: "翻译效果真的顶!但每次上传比较大的 ...

  10. python中print函数参数解析

    print(*values: object, sep: Optional[Text]=..., end: Optional[Text]=..., file: Optional[_Writer]=... ...