Python+Django+Middleware实现访问接口的频率限制(附带捕获请求ip)

前文

  有时需要针对访问接口实现频率限制,在Django里用的比较多的就是用过中间件的形式来完成。以下介绍Django中间件的五种自定义方法的用途和举个项目实例来简单说明。

Django中间件的五个方法

  中间件中可以定义5个方法,分别是:

process_request(self,request)
process_view(self, request, callback, callback_args, callback_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response

  这五个方法从名字上就可以看出各自的用途,分别是request:针对请求前,view:针对请求已过到具体的函数之前调用,response:针对请求结束后,返回值发向网关的时候,这三个是最常用的,也是接下来举例的方法。而template/exception相对用的比较少,前者是针对返回是render时的response才回调用,后者则是在发生异常时才调用,用的不多。
  另外就是中间件的调用顺序,这在诸多博客里都被反复提及,这边就不多说了,简单理解就是双重装饰器的效果,请求前依次从上往下,请求结束后则从下往上。

利用中间件实现访问接口的频率拦截

  这边实现需求的频率拦截,连续请求函数1分钟频率超过10次就拦截,这个思路就是通过记录用户的ip或者用户名,然后在redis中用setex设置1分钟的key,设置value初始值为1,每请求一次,就利用redis.incrby自增一次,一分钟内超过十次,则在中间件进行拦截,一分钟后key消失,则计数重新开始。
  中间件5大方法,这边采用的是process_view,先介绍获取用户ip的方法:

#获取远程ip
def get_client_ip(request):
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

  Django自带的request.META就能捕获REMOTE_ADDR,但是只能捕获请求到服务端的ip,因为可能中途会经过代理,所以需要进行判断。这边是通过HTTP_X_FORWARDED_FOR的字段来判断,该字段是利用了HTTP传输数据包的原理,每经过代理,会自动在X_FORWARDED上加数,所以如果能get到值,则说明经过了代理,通过split分割成列表,则取索引0拿到第一个ip就是客户端ip。
  接下来定义一个类来进行判断键的存活时间和频率的控制。

import logging
from django.http import HttpResponse
import json

from django_redis import get_redis_connection
from django.utils.deprecation import MiddlewareMixin
from ..constants import *
# 定义记录日志
logger = logging.getLogger('django')

class RequestLimit:
    def __init__(self):
        self.redis = get_redis_connection('Middle')  #定义redis的连接,在setting里配置cache
    def check(self, code, user_id, increment): 
        store_key = 'limit_%s_%s' % (code, user_id)
        timers = self.redis.get(store_key)
        print('当前的时间次数是:', timers)
        timers = int(timers) if timers else 0
        if timers:
            if timers <= MAX_TIMERS:
                timers = self.redis.incrby(store_key, increment)  #采用incrby自增,不会影响到过期时间
                if timers == 1:  # 并发控制, 如果刚好过期, 则要重新设置过期时间
                    self.redis.expire(store_key, PERIOD)
        else:
            self.redis.set(store_key, 1, ex=PERIOD)  #PERIOD为1分钟,常量定义在constants.py,这边是设置过期1分钟的key,默认从1开始

        success = timers <= MAX_TIMERS  #判断当前次数是否小于最大次数,返回正负数,MAX_TIMERS是常数,定义在constants.py文件里
        if success:
            logger.debug('check success, code: %s, key: %s'
                        % (code, user_id))
        else:
            logger.info('check fail, code: %s, key: %s'
                        % (code, user_id))
        return success

  最后在中间件进行调用就行了:

def check_prevent(user_id, ip, increment): #调用类,返回success进行判断
    prevent_client = FrequencyLimitService()
    if user_id:
        success = prevent_client.check('user_sql_limit', user_id, increment)
    else:
        success = prevent_client.check('user_sql_limit', ip, increment)
    return success
    
# 对用户查询进行中间件频率控制
class PreventMiddleware(MiddlewareMixin):

    def process_request(self, request):
        pass

    # 这里没有用view_func,直接通过request.path来进行捕获请求(因为有2个路径),如果是请求路径的则进行判断
    def process_view(self, request, view_func, view_args, view_kwargs):
        data = {}
        path = request.path
        if path in ('path1', 'path2'):  #判断在2个路径的请求都需要经过中间件
            # print(request.path)
            user_id = request.session.get('user_id')  #获取用户id
            ip = get_client_ip(request) 
            increment = 1
            write_success = True
            write_success = check_prevent(user_id, ip, increment) #调用上方的函数

            if not write_success:
                data["status"] = 600
                data["msg"] = "您在一分钟内查询的过于频繁,请稍后查询"
                return HttpResponse(json.dumps(data),
                                    content_type="application/json") #超过次数就报警返回

总结

  用Django的中间件来控制接口的频率限制还是很常用的,也比较方便,当然以上代码还有很多要优化的,有兴趣的朋友可再探讨。

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值