restframework之节流
基本思路(原生Django而言):
在django2.x中,若出现节流(访问频率控制)的需求,我们首先想到的是使用一个字典(dict类型)来存储所有IP地址的访问时间记录,这是针对于匿名用户(IP)而言的
{ "111.222.3.44": [1000, 900, 700, 500, 10....], "1.333.44.555": [1200, 900, 20, 10.....] }
原生request的消息体中,在environ下的REMOTE_ADDR保存了客户端的IP地址
由于请求经过WSGIRequest封装后,必经中间件层,所以考虑通过中间件来实现节流功能
from django.utils.deprecation import MiddlewareMixin from django.http.response import HttpResponse import time MAX_PER_VISITED_NUMS = 3 VISITED_IP_THROTTLE = dict() class ThrottleMiddleware(MiddlewareMixin): """ 节流中间件 VISITED_IP_THROTTLE每分钟最大访问次数 """ def request_allowed(self, request): """自定义访问频率限制器, 用于限制匿名用户的访问频率限制""" ip = request.META['REMOTE_ADDR'] cur_time = time.time() # 如果ip在限制器中有记录 if ip in VISITED_IP_THROTTLE: visiting_record = VISITED_IP_THROTTLE[ip] while visiting_record: # 与列表中最后一次访问记录的时间差大于60秒, 移除最后一次记录并继续循环 if cur_time - 60 >= visiting_record[-1]: visiting_record.pop(-1) continue # 访问记录列表超过最大访问次数 if len(visiting_record) >= MAX_PER_VISITED_NUMS: return False # 与列表中最后一次访问记录的时间差小于60秒 visiting_record.insert(0, cur_time) return True # 无记录则新建该IP访问记录 VISITED_IP_THROTTLE[ip] = list() VISITED_IP_THROTTLE[ip].insert(0, cur_time) return True def process_request(self, request): if self.request_allowed(request): print(True) return HttpResponse("True") else: print(False) return HttpResponse("False")
运行结果:
这是针对在访问用户较少的情况下才采用的全局变量来存储访问记录, 实际情况中,最好使用redis作为缓存来存储记录。
restframework版:
自定义节流控制器
使用redis缓存访问记录(hash类型)
redispool.py(作连接池, 参数设置为你自己的redis连接信息)
import redis from vue_shop import settings REDIS_POOL = redis.ConnectionPool(**settings.REDIS_KWARGS)
throttle.py
import time import redis from utils.redispool import REDIS_POOL import json from rest_framework.throttling import SimpleRateThrottle class AnonymityUserThrottle(object): """ 自定义匿名用户节流器, 采用redis缓存访问记录 MAX_PER_VISITED_NUMS: 每分钟最高访问次数 name: 缓存(redis)中,hash类型的name """ MAX_PER_VISITED_NUMS = 3 name = "AnonymityVisitedRecord" def allow_request(self, request, view): """ 进行是否可访问的逻辑判断 :param request: drf's request :param view: :return: bool """ IP_ADDR = request.META.get("REMOTE_ADDR", None) cur_visit_time = time.time() redis_conn = redis.Redis(connection_pool=REDIS_POOL) ips_record = redis_conn.hget(self.name, IP_ADDR) print(ips_record) if ips_record: history_visit_list = json.loads(ips_record.decode()) while history_visit_list: # [120, 99, 40] # 最远一次访问时间B与当前时间A的时间差大于60秒,则删除B,如此循环 if cur_visit_time - history_visit_list[-1] >= 60: history_visit_list.pop(-1) redis_conn.hset(self.name, IP_ADDR, json.dumps(history_visit_list)) continue if len(history_visit_list) > self.MAX_PER_VISITED_NUMS: return False history_visit_list.insert(0, cur_visit_time) redis_conn.hset(self.name, IP_ADDR, json.dumps(history_visit_list)) return True new_ip_record = list() new_ip_record.insert(0, cur_visit_time) redis_conn.hset(self.name, IP_ADDR, json.dumps(new_ip_record)) return True def wait(self): """ Optionally, return a recommended number of seconds to wait before the next request. """ return None
运行结果:
内置的节流控制器
class SimpleAnonymityUsertThrottle(SimpleRateThrottle): """ 使用restframework内置的节流类,针对于的匿名用户的访问控制 """ scope = "Anonymity" def get_cache_key(self, request, view): """ 返回用户的唯一标识 """ # return request.META.get("REMOTE_ADDR") return self.get_ident(request) class CertifiedUserThrottle(SimpleRateThrottle): """ 对认证通过的用户进行节流控制 """ scope = "CertifiedUser" def get_cache_key(self, request, view): """返回唯一用户标识""" return request.user.username
scope参数依赖配置:
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': { 'Anonymity': '3/m', 'realUser': '6/m', }, }