Flask-Limit详细说明

在flask项目中我们需要对全部或者一部分接口进行限制,又不想造轮子,那怎么办呢?

所以这就是flask-limit出现的原因,不过对于相对复杂的需求,还是自己造轮子吧!

安装与简单使用

安装:pip install Flask-Limiter

快速开始:

from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address app = Flask(__name__)
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
@app.route("/slow")
@limiter.limit("1 per day")
def slow():
return ":(" @app.route("/medium")
@limiter.limit("1/second", override_defaults=False)
def medium():
return ":|" @app.route("/fast")
def fast():
return ":)" @app.route("/ping")
@limiter.exempt
def ping():
return "PONG"

上诉频率限制说明:

  • 默认通过请求的remote_address进行限制。
  • 默认限制为200次/天,50次/小时;适用于所有路线
  • slow路由的限制将绕过默认的速率限制,为1次/天
  • medium路由继承默认限制,并增加了1次/秒的限制
  • ping路由不受任何默认速率限制的约束

注意: 静态路由不受速率限制

每次请求超出速率限制时,将不会调用view函数,而是会引发429http错误。

速率限制规则:

[count] [per|/] [n (optional)] [second|minute|hour|day|month|year]

可以使用自己选择的分隔符将多个速率限制组合起来。

示例:

  • 10 per hour
  • 10/hour
  • 10/hour;100/day;2000 per year
  • 100/day, 500/7days

使用的详细说明

看完上面的部分其实已经满足大部分需求了,但是真实的情况下,可能还存在其他的定制服务,以下就是详细说明。

初始化

初始化有两种方式:

  1. 使用构造函数

    from flask_limiter import Limiter
    from flask_limiter.util import get_remote_address
    .... limiter = Limiter(app, key_func=get_remote_address)
  2. 使用延迟应用初始化 init_app

    limiter = Limiter(key_func=get_remote_address)
    limiter.init_app(app)

实际开发中更有可能使用的是延迟初始化。

装饰器

我们所使用的是已创建的Limiter示例的limit方法,可根据喜好和使用场景,有以下几种使用方式:

单装饰

@app.route("....")
@limiter.limit("100/day;10/hour;1/minute")
def my_route()
...

多装饰

@app.route("....")
@limiter.limit("100/day")
@limiter.limit("10/hour")
@limiter.limit("1/minute")
def my_route():
...

新增自定义的功能

下方会有详细介绍此装饰器内的参数的说明

def my_key_func():
... @app.route("...")
@limiter.limit("100/day", my_key_func)
def my_route():
...

限制域

即指定根据什么进行限制,对应的参数为key_funcflask_limiter.util提供了两种方式:

  • flask_limiter.util.get_ipaddr(): 使用X-Forwarded-For标头中的最后一个IP地址,否则回退到请求的remote_address(不建议使用)
  • flask_limiter.util.get_remote_address(): 使用请求的remote_address

在真实开发中,大部分项目都配备了Nginx,所以如果直接使用get_remote_address的话获取到的是Nginx服务器的地址,非常危险!!!

所以项目中很有可能都是自定义key_func!

搭载Nginx服务器的key_func示例:

def limit_key_func():
return str(flask_request.headers.get("X-Forwarded-For", '127.0.0.1'))

不过以上设置的依据还是根据Nginx的配置决定的,有兴趣的同学还可以了解一下X-Forwarded-ForX-Real-IP的区别。

X-Forwarded-For 一般是每一个非透明代理转发请求时会将上游服务器的ip地址追加到X-Forwarded-For的后面,使用英文逗号分割 ;

X-Real-IP一般是最后一级代理将上游ip地址添加到该头中 ;

X-Forwarded-For是多个ip地址,而X-Real-IP是一个;

如果只有一层代理,这两个头的值就是一样的。

所以上方自定义的方法仅作参考。

动态加载限制字符串

常见的限制规则已在上文介绍过,这里介绍的在某些情况下,需要从代码外部的源(数据库,远程api等)中检索速率限制。

def rate_limit_from_config():
return current_app.config.get("CUSTOM_LIMIT", "10/s") @app.route("...")
@limiter.limit(rate_limit_from_config)
def my_route():
...

所装饰的路由上的每个请求都会调用提供的可调用对象。对于昂贵的检索,请考虑缓存响应。

豁免条件

个人觉得这可以从两个方面来谈,一是针对key,一是针对计次,以下我们分别进行介绍。

  • 白名单:

    • 方式一:参数为exempt_when,设置这个参数将不被频率限制。

      @app.route("/expensive")
      @limiter.limit("100/day", exempt_when=lambda: current_user.is_admin)
      def expensive_route():
      ...
    • 方式二:请求过滤器Limiter.request_filter()方法(没研究)

      @limiter.request_filter
      def header_whitelist():
      return request.headers.get("X-Internal", "") == "true" @limiter.request_filter
      def ip_whitelist():
      return request.remote_addr == "127.0.0.1"
  • 不计次情况:参数为deduct_when,判断某些情况不计入使用频率的次数。

    def func_deduct(response):
    """
    频率限制之根据response决定是否计次
    :param response: flask.wrappers.Response对象
    :return: 计次返回True
    """
    # 正常响应状态码:200
    res = response.response if response._status_code == 200 else None
    if res:
    res = json.loads(res[0])
    # 有响应数据,记一次
    return res.get("code") == 200 return False @api.route('/captcha')
    @limit.limit("5/day;3/hour", deduct_when=func_deduct)
    def expensive_route():
    ...
  • 路由豁免:此情况特殊,属于某个路由不参与频率限制,使用方式为

    limiter.exempt()

共享限制

适用于速率限制应由多条路由共享的情况。

命名共享限制

mysql_limit = limiter.shared_limit("100/hour", scope="mysql")

@app.route("..")
@mysql_limit
def r1():
... @app.route("..")
@mysql_limit
def r2():
...

动态共享限制:将可调用对象作为范围传递时,该函数的返回值将用作范围。

def host_scope(endpoint_name):
return request.host
host_limit = limiter.shared_limit("100/hour", scope=host_scope) @app.route("..")
@host_limit
def r1():
... @app.route("..")
@host_limit
def r2():
...

共享限制使用上与单个限制一致

配置

参数 说明
RATELIMIT_DEFAULT 默认策略, 逗号分隔('1/minute,100/hour')
RATELIMIT_DEFAULTS_PER_METHOD 是按方法/路线应用默认限制,还是按方法将所有方法组合应用默认限制。
RATELIMIT_DEFAULTS_EXEMPT_WHEN 默认豁免条件
RATELIMIT_APPLICATION 应用策略,用于将限制应用于整个应用程序(即,由所有路由共享)。
RATELIMIT_STORAGE_URL 存储位置:
  • 内存:memcached://host:port
  • Redis: redis://host:port |

    | RATELIMIT_STORAGE_OPTIONS | 一个字典,用于设置要在初始化时传递给存储实现的其他选项。 |

    | RATELIMIT_STRATEGY | 使用的限速策略。详见限速策略 |

    | RATELIMIT_HEADERS_ENABLED | 是否返回速率限制的相关信息到reponse header中。默认为False,与上一条一样可以忽视。 |

    | RATELIMIT_ENABLED | 速率限制的总体终止开关。默认为True |

    | RATELIMIT_HEADER_LIMIT | 当前速率限制的标题。默认为X-RateLimit-Limit |

    | RATELIMIT_HEADER_RESET | 当前速率限制的重置时间的标题。默认为X-RateLimit-Reset |

    | RATELIMIT_HEADER_REMAINING | 当前速率限制中剩余的请求数的标头。默认为X-RateLimit-Remaining |

    | RATELIMIT_HEADER_RETRY_AFTER | 客户端应何时重试请求的标头。默认为Retry-After |

    | RATELIMIT_SWALLOW_ERRORS | 默认False即可 |

    | RATELIMIT_IN_MEMORY_FALLBACK_ENABLED | 如果启用,则当配置的存储关闭时,内存中的速率限制器将用作备用。与RATELIMIT_IN_MEMORY_FALLBACK原始速率限制结合使用时,将不会继承该限制 |

    | RATELIMIT_IN_MEMORY_FALLBACK | 后端存储异常使用的策略配置 |

    | RATELIMIT_KEY_PREFIX | 存储key的前缀配置 |

速度限制策略

Flask-Limiter内置了三种不同的速率限制策略。

分别为: Fixed Window、Fixed Window with Elastic Expiry、Moving Window

暂未研究,不做介绍。

错误响应

超出限制的请求返回的都是429状态码,示例如下:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>429 Too Many Requests</title>
<h1>Too Many Requests</h1>
<p>1 per 1 day</p>

如果要配置响应,可对路由状态码判断后响应,示例如下:

@app.errorhandler(429)
def ratelimit_handler(e):
return make_response(
jsonify(error="ratelimit exceeded %s" % e.description)
, 429
)

当然,还可以自定义错误信息:

app = Flask(__name__)
limiter = Limiter(app, key_func=get_remote_address) def error_handler():
return app.config.get("DEFAULT_ERROR_MESSAGE") @app.route("/")
@limiter.limit("1/second", error_message='chill!')
def index():
.... @app.route("/ping")
@limiter.limit("10/second", error_message=error_handler)
def ping():
....

CBV与Blueprint使用

FBV可以使用装饰器的方式进行限制,但是对于CBV就有些不适用了,以下就是CBV的使用方式。

app = Flask(__name__)
limiter = Limiter(app, key_func=get_remote_address) class MyView(flask.views.MethodView):
decorators = [limiter.limit("10/second")]
def get(self):
return "get" def put(self):
return "put"

CBV的方式还是有些麻烦了,如果能对蓝图下所有的路由都进行限制就更好了,也可以对某个蓝图进行豁免。

app = Flask(__name__)
login = Blueprint("login", __name__, url_prefix = "/login")
regular = Blueprint("regular", __name__, url_prefix = "/regular")
doc = Blueprint("doc", __name__, url_prefix = "/doc") @doc.route("/")
def doc_index():
return "doc" @regular.route("/")
def regular_index():
return "regular" @login.route("/")
def login_index():
return "login" limiter = Limiter(app, default_limits=["1/second"], key_func=get_remote_address)
limiter.limit("60/hour")(login)
limiter.exempt(doc) app.register_blueprint(doc)
app.register_blueprint(login)
app.register_blueprint(regular)

关于代理

虽然上文说过Nginx代理的情况需要更复杂的操作,不过在查看官方文档的时候,还发现了一个简单的方法,说明如下:

如果您的应用程序位于代理之后,并且您使用的是werkzeug> 0.9+,则可以使用werkzeug.contrib.fixers.ProxyFix 修复程序可靠地获取用户的远程地址,同时保护您的应用程序免于通过标头进行ip欺骗。

from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from werkzeug.contrib.fixers import ProxyFix app = Flask(__name__)
# for example if the request goes through one proxy
# before hitting your application server
app.wsgi_app = ProxyFix(app.wsgi_app, num_proxies=1)
limiter = Limiter(app, key_func=get_remote_address)

API

flask_limit.Limiter类初始化属性,Limiter(app=None, key_func=None, global_limits=[], default_limits=[], default_limits_per_method=False, default_limits_exempt_when=None, default_limits_deduct_when=None, application_limits=[], headers_enabled=False, strategy=None, storage_uri=None, storage_options={}, auto_check=True, swallow_errors=False, in_memory_fallback=[], in_memory_fallback_enabled=False, retry_after=None, key_prefix='', enabled=True)

参数 说明
app 即flask的项目
key_func 限制域
default_limits 默认限制策略
default_limits_per_method 默认限制是按方法/路线应用还是按每种方法所有方法的组合应用。
default_limits_exempt_when 默认豁免条件
default_limits_deduct_when 接收response对象并返回True / False以决定是否应从默认速率限制中扣除的函数
application_limits 所有路由的共享限制
headers_enabled 是否写入响应头
storage_uri 存储位置
storage_options 意义不明的额外配置
auto_check 是否自动检查应用程序的before_request链中的速率限制。默认True
swallow_errors 达到速率限制时会记录异常。默认False
in_memory_fallback 字符串或可调用项的可变列表,返回表示存储空间不足时要应用的回退限制的字符串
in_memory_fallback_enabled 仅在主存储关闭并继承原始限制时才退回到内存存储中。
key_prefix 前缀
strategy 策略

方法:

check()

exempt()

ini_app()

request_filter()

reset()

limit(limit_value, key_func=None, per_method=False, methods=None, error_message=None, exempt_when=None, override_defaults=True, deduct_when=None)

shared_limit(limit_value, scope, key_func=None, error_message=None, exempt_when=None, override_defaults=True, deduct_when=None)

Flask-Limit使用详细说明的更多相关文章

  1. 服务器配置:ECS+Nginx+uWSGI+Flask——各部分详细介绍

    希望在阿里云ECS上搭建一个flask框架的web应用,经典的形式便是flask+uWSGI+nginx模式 服务器:CentOS 7.3 python版本:3.8.0 先贴一张全局图,这张图很清楚的 ...

  2. windows下python+flask环境配置详细图文教程

    本帖是本人在安装配置python和flask环境时所用到的资源下载及相关的教程进行了整理罗列,来方便后面的人员,省去搜索的时间.如果你在安装配置是存在问题可留言给我. 首先罗列一下python+fla ...

  3. flask-admin章节四:flask session的使用

    1. 关于session flask session可能很多人根本都没有使用过,倒是cookie大家可能使用得比较多.flask cookie使用起来比较简单,就两个函数,读取和设置. 具体使用方式如 ...

  4. sqlalchemy模块介绍、单表操作、一对多表操作、多对多表操作、flask集成.

    今日内容概要 sqlalchemy介绍和快速使用 单表操作增删查改 一对多 多对多 flask集成 内容详细 1.sqlalchemy介绍和快速使用 # SQLAlchemy是一个基于 Python实 ...

  5. python Tornado(招聘的一个比较经常问到的知识)

    Tornado既是一个webserver也是一个web框架 这是一个总结的比较详细的内容 http://www.nowamagic.net/academy/detail/1332612 开源中国中的关 ...

  6. 【转】持久化消息队列之MEMCACHEQ

    G MEMCACHEQ AS MESSAGE QUEUE PHP,消息队列,MEMCACHEQ 使用消息队列(MESSAGE QUEUE)可以把某些耗时的工作推后,然后在后台慢慢地去执行,这样就不会让 ...

  7. EXTJS 4.2 资料 控件之Grid 行编辑绑定下拉框,并点一次触发一次事件

    主要代码: { header: '属性值', dataIndex: 'PropertyValueName', width: 130, editor: new Ext.form.field.ComboB ...

  8. mysq常用l性能分析方法

    orzdba查看读写./orzdba.pl --mysql -S /data/mysql30001/mysql.sock 语句查看读写命令数量,以及数据库TPS,传输的大小 查看processlist ...

  9. python gunicorn详解

    Gunicorn是一个unix上被广泛使用的高性能的Python WSGI UNIX HTTP Server.和大多数的web框架兼容,并具有实现简单,轻量级,高性能等特点. gunicorn 安装 ...

  10. AI+云原生,把卫星遥感虐的死去活来

    摘要:遥感影像,作为地球自拍照,能够从更广阔的视角,为人们提供更多维度的辅助信息,来帮助人类感知自然资源.农林水利.交通灾害等多领域信息. 本文分享自华为云社区<AI+云原生,把卫星遥感虐的死去 ...

随机推荐

  1. C# 反射与特性(十):EMIT 构建代码

    目录 构建代码 1,程序集(Assembly) 2,模块(Module) 3,类型(Type) 4,DynamicMethod 定义方法与添加 IL 前面,本系列一共写了 九 篇关于反射和特性相关的文 ...

  2. Python SimpleHTTPServer (python3 -m http.server 6789)

    搭建FTP,或者是搭建网络文件系统,这些方法都能够实现Linux的目录共享.但是FTP和网络文件系统的功能都过于强大,因此它们都有一些不够方便的地方.比如你想快速共享Linux系统的某个目录给整个项目 ...

  3. 【JMeter_09】JMeter逻辑控制器__临界部分控制器<Critical Section Controller>

    临界部分控制器<Critical Section Controller> 业务逻辑: 根据锁名来控制并发,同一个锁名之下,在同一时间点只能存在一个运行中,适用于控制并发的场景 锁名类型: ...

  4. router-view中绑定key='$route.fullPath'

    原文链接https://www.jianshu.com/p/cf2fb443620f 来源:简书 作者:myzony 不设置 router-view 的 key 属性 由于 Vue 会复用相同组件, ...

  5. vc6.0打开类向导时报错-Parsing error: Expected ";".Input Line: "解决方法

    --------------------------- Microsoft Visual C++ --------------------------- Parsing error:  Expecte ...

  6. Idea 快捷生成类注释与方法注释

    这篇博客应该在刚使用IDEA 的时候就写了.  但是一直忘了.  今天使用新的api 接口文档生成工具 JApiDocs  的时候,看其上面有编码规范, 注释规范. 就想起了IDEA 类中, 方法中快 ...

  7. Elasticsearch的query phase和fetch phase

    对于一次query查询到数据返回到客户端,经历了两个过程  query phase和fetch phase的过程 query phase 查询阶段     fetch phase 获取阶段. 1 qu ...

  8. 7-4 是否同一棵二叉搜索树 (25分) JAVA

    给定一个插入序列就可以唯一确定一棵二叉搜索树.然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到. 例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始为空的二叉搜索树,都得到一样的结 ...

  9. base64格式的图片上传阿里云

    base64格式的图片上传阿里云 上传图片的时候,除了普通的图片上传,还有一张图片信息是以base64格式发送到后台的. 后台接受base64格式的图片,上传至阿里云代码:(主要是将base64转化成 ...

  10. ECSHOP 2.5.1 二次开发文档【文件结构说明和数据库表分析】

    ecshop文件架构说明 /* ECShop 2.5.1 的结构图及各文件相应功能介绍 ECShop2.5.1_Beta upload 的目录 ┣ activity.php 活动列表 ┣ affich ...