Flask-Limiter详细使用说明

本文首发于:行者AI

速率限制通常作为服务的防御措施予以实施。服务需要保护自身以免过度使用(无论是有意还是无意),从而保持服务可用性。在Flask项目开发过程中,遇到了需要对接口进行限制的需求,又不想去造轮子,这时候就需要用到Flask-Limiter这个三方库。本文将对Flask-Limiter的使用进行详细说明。

1. 安装

安装依赖环境。

pip install Flask==1.1.1 Flask-Limiter==1.4

2. 快速开始

有两种方式表示速率限制:

  • "100 per day"、"20 per hour"、"5 per minute"、"1 per second"
  • "100/day"、"20/hour"、"5/minute"、"1/second"

速率限制可以设置全局配置,针对所有接口进行限制;也可以通过装饰器进行局部限制;对于不想限制的接口,可以通过装饰器@limiter.exempt进行解除限制。示例代码如下所示:

app = Flask(__name__)

# 该配置为全局配置、适用于所有接口
limiter = Limiter(app, key_func=get_remote_address, default_limits=["100 per day", "10/hour"])

# @limiter.limit: 将覆盖全局limiter配置
@app.route("/slow")
@limiter.limit("1 per day")
def slow():
    return ":("

# override_defaults: 表示该limiter是否覆盖全局limiter限制,默认为True
@app.route("/medium")
@limiter.limit("1/second", override_defaults=False)
def medium():
    return ":|"

# 完整继承全局limiter配置
@app.route("/fast")
def fast():
    return ":)"

# @limiter.exempt: 被装饰的视图不受全局速率限制
@app.route("/ping")
@limiter.exempt
def ping():
    return "PONG"

3. 装饰器

根据个人喜好和使用场景,有以下几种方式:
单一修饰:限制字符串可以是单个限制,也可以是定界符分隔的字符串。

@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():
  ...

自定义功能:默认情况下,根据Limiter实例初始化时所使用的关键功能来应用速率限制。开发者可以实现自己的功能。

def my_key_func():
  ...

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

动态加载限制的字符串:在某些情况下,需要从代码外部的源(数据库,远程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():
    ...

4. 限制域

指根据什么进行限制,对应的参数为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服务器的地址,相当于来自该Nginx服务器的所有请求会被当做同一个IP访问,所以项目中一般都是自定义key_func!

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

获取IP的依据是根据Nginx的配置决定的:

X-Forwarded-For # 一般是每一个非透明代理转发请求时会将上游服务器的ip地址追加到X-Forwarded-For的后面,使用英文逗号分割;
X-Real-IP # 一般是最后一级代理将上游ip地址添加到该头中;
X-Forwarded-For # 是多个ip地址,而X-Real-IP是一个;
# 如果只有一层代理,这两个头的值就是一样的。

5. 共享限制

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

命名共享限制:通过相同的shared_limit对象进行装饰。

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

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

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

动态共享限制:将可调用对象作为范围传递,该函数的返回值将用作范围。注意,callable具有一个参数:表示请求端点的字符串。

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():
   ...

6. 储存后端

记录IP访问次数,用于判断该IP访问次数是否达到限制。Limiter默认使用内存作为储存后端,但是在实际开发中,可能会涉及到多进程资源不共享、服务器内存消耗等问题,一般是使用redis作为储存后端。

  • 需要redis服务器的位置,以及可选的数据库号。 redis://localhost:6379redis://localhost:6379/n(对于数据库n)。

  • 如果redis服务器正在通过unix域套接字监听,则可以使用redis+unix:///path/to/sockredis+unix:///path/to/socket?db=n(对于数据库n)。

  • 如果数据库受密码保护,则可以在URL中提供密码,例如, redis://:foobared@localhost:6379或者redis+unix//:foobered/path/to/socket

# LIMITS_REDIS_STORAGE就是上文提到的redis的URI
limiter = Limiter(default_limits=["100 per day"], key_func=limit_key_func, storage_uri=LIMITS_REDIS_STORAGE)

7. 总结

通过对API请求的限制和配额,在一定程度上能避免系统收到超出其处理能力的数据,确保系统的资源能得到合理的使用。以上就是本文的全部内容,希望能给大家学习带来帮助。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • 快速开始 在安装Sanic之前,让我们一起来看看Python在支持异步的过程中,都经历了哪些比较重大的更新。 首先...
    hugoren阅读 19,454评论 0 23
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • typora-copy-images-to: ipic [TOC] 快速开始 在安装Sanic之前,让我们一起来看...
    君惜丶阅读 14,073评论 3 18
  • 1. 入门 Sanic 是一款类似Flask的Web服务器,它运行在Python 3.5+上。 除了与Flask功...
    JasonJe阅读 13,876评论 4 37
  • 1、谈谈对http协议的认识流程:1.域名解析域名解析检查顺序为:浏览器自身DNS缓存---》OS自身的DNS缓存...
    Zzmi阅读 684评论 0 0