接口访问控制(限流)
# 链接资料
drf官方文档
drf官方源码地址
GitHub - encode/django-rest-framework: Web APIs for Django. 🎸 (opens new window)
博客园
# 1. 限流介绍
开放平台的API接口调用需要限制其频率,以节约服务器资源和避免恶意的频繁调用。其实有的时候我们辛辛苦苦造的数据被爬虫份子给爬掉。爬虫在爬数据的时候数据量也比较大,那么这个时候就会给我们服务器造成了压力,影响用户的使用。在这个时候我们就要限制用户的使用频率。那怎么限制呐?我们给出一些限制的思路:
1
节流思路:
我现在想 限制 每个用户 1分钟之内 只能访问 3次(3/min),并且每个用户在访问的时候,我按一定的规则记录当前用户访问的记录,比如我按 ip地址记录:
{"127.0.0.1": {"12:29:50","12:29:40","12:29:20'"}}:指的是 我 127.0.0.1 ip地址在1分钟之内访问 3次。这个时候我在 12:30:10 去访问时,在1分钟之内,很明显无法访问。
那我如果是 12:30:21 去访问,这个时候 12:30:21 跟 {"127.0.0.1": {"12:29:50","12:29:40","12:29:20'"}} 中最后一个值 12:29:20 比,大于1分钟了,这个时候可以访问了。
那个这个时候 列表就变成: {"127.0.0.1": {"12:30:21","12:29:50","12:29:40"}} 就把 12:30:21 加到列表中第一个参数,把 12:29:20列表中删除掉。
依次类推...
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 2. SimpleRateThrottle 源码分析
# 2.1 导入 SimpleRateThrottle
from rest_framework.throttling import SimpleRateThrottle
1
# 2.2 查看源码
Ctrl + SimpleRateThrottle -> BaseThrottle
1
发现它是继承一个基础类 Ctrl + BaseThrottle ,有哪些东西:
class BaseThrottle(object):
"""
Rate throttling of requests.
"""
def allow_request(self, request, view): #必须要实现的1个方法,True:允许访问 False:禁止访问
"""
Return `True` if the request should be allowed, `False` otherwise.
"""
raise NotImplementedError('.allow_request() must be overridden')
def get_ident(self, request):
.....
def wait(self):
.....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
还是先dispatch 和上面一样
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
# 实现认证
self.perform_authentication(request)
# 权限判断
self.check_permissions(request)
# 限流
self.check_throttles(request)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def check_throttles(self, request):
"""
Check if request should be throttled.
Raises an appropriate exception if the request is throttled.
"""
throttle_durations = []
for throttle in self.get_throttles(): # 获取列表
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
if throttle_durations:
# Filter out `None` values which may happen in case of config / rate
# changes, see #1438
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
self.throttled(request, duration)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.3 自定义限流
# 自己写的方式使用(基本使用)
通过IP的方式自定义
# 重写限流类
class hisitThrottle(BaseThrottle):
"""60秒只能访问3次"""
def __init__(self):
self.history = None
def allow_request(self, request, view):
"""返回True表示可以访问"""
remote_addr = request.META.get('REMOTE_ADDR') # 获取当前用户的IP
ctime = time.time()
if remote_addr not in VISIT_RECORD:
VISIT_RECORD[remote_addr] = [ctime, ] # 注意这里是以列表形式添加
history = VISIT_RECORD.get(remote_addr) # 获取时间戳[]
self.history = history
print(self.history)
while history and history[-1] < ctime - 60: # 如果最后一个时间戳小于当前时间戳-60秒(就为True),
history.pop()
if len(history) < 3: # 只让访问3次
history.insert(0, ctime) # 把当前时间添加到第一个
return True
def wait(self):
"""还需要等待多少秒才可以执行"""
ctime = time.time()
return 60 - (ctime - self.history[0])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 使用
class AuthLoginView(APIView):
"""用户登录"""
authentication_classes = []
permission_classes = []
throttle_classes = [hisitThrottle] # 使用
def post(self, request, *args, **kwargs):
ret = {'code': 1000, 'message': 'ok!'}
try:
username = request._request.POST.get('username') # 获取前端传递过来的内容
password = request._request.POST.get('password')
obj = models.UserInfo.objects.filter(username=username, password=password).first()
if not obj:
ret['code'] = 1002
ret['message'] = "用户密码错误"
# 成功后为登录用户生产token
token = md5(username)
models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
ret['token'] = token
except Exception as e:
ret['code'] = 1003
ret['message'] = "请求异常"
return JsonResponse(ret)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3. 自定义限制频率(节流)
# 3.1 新建内置频率类
说明:在应用下新建一个throttlings文件。创建频率类:
from rest_framework.throttling import SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):
scope = "未认证用户"
def get_cache_key(self, request, view):
return self.get_ident(request) #ip地址当做key,可以会缓存到django的缓存里面
class UserThrottle(SimpleRateThrottle):
scope = "已认证用户"
def get_cache_key(self, request, view): #要写一个认证方法,告诉人家你拿什么当key
return request.user #当前登录用户当做key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
好了,频率类 新建好了。
# 3.2 全局配置
说明:在settings.py的REST_FRAMEWORK 下配置全局。
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ....,
'DEFAULT_PERMISSION_CLASSES': ...,
'DEFAULT_THROTTLE_CLASSES': ['app05.throttlings.UserThrottle', ],
'DEFAULT_THROTTLE_RATES': {
'未认证用户': '3/m',
'已认证用户': '10/m',
},
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
意思是:未认证用户 1分钟3次,认证用户是 1分钟10次。那这个时间定义在哪里定义的呐?SimpleRateThrottle源码中有,我们来看看:
class SimpleRateThrottle(BaseThrottle):
"""
...
Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day') #时间设置
....
"""
....
1
2
3
4
5
6
7
2
3
4
5
6
7
# 3.3 局部配置
说明:上面是全局配置,但是有的时候我不想全局的,我只想局部实现,那咋办呐?
from .throttlings import VisitThrottle #导入自定义频率类
# Create your views here.
class CartView(APIView):
# 节流 局部配置
throttle_classes = [VisitThrottle]
def get(self, request, *args, **kwargs):
....
return JsonResponse(ctx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
但是全局也写了,我只想在视图中什么限制也不想加,那我们可以是 throttle_classes 为空就行了,如下:
class CartView(APIView):
# 视图不加任何限制,但是全局已经配置了
throttle_classes = []
...
1
2
3
4
5
6
2
3
4
5
6
编辑 (opens new window)