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

安装与简单使用

安装:pip install Flask-Limiter

快速开始:

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

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

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

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"]
) # slow路由的限制将绕过默认的速率限制,为1次/天
@app.route("/slow")
@limiter.limit("1 per day")
def slow():
return ":(" # @limiter.exempt: 被装饰的视图不受全局速率限制
@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"

上诉频率限制说明:

  • 默认通过请求的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)
  1. 使用延迟应用初始化 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代理的情况需要更复杂的操作,不过在查看官方文档的时候,还发现了一个简单的方法,说明如下:

0.9+,则可以使用werkzeug.contrib.fixers.ProxyFix 修复程序可靠地获取用户的远程地址,同时保护您的应用程序免于通过标头进行ip欺骗。

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)

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)

Flask-Limit详细说明:接口限流的更多相关文章

  1. 库存秒杀问题-redis解决方案- 接口限流

    <?php/** * Created by PhpStorm. * redis 销量超卖秒杀解决方案 * redis 文档:http://doc.redisfans.com/ * ab -n 1 ...

  2. 【Dnc.Api.Throttle】适用于.Net Core WebApi接口限流框架

    Dnc.Api.Throttle    适用于Dot Net Core的WebApi接口限流框架 使用Dnc.Api.Throttle可以使您轻松实现WebApi接口的限流管理.Dnc.Api.Thr ...

  3. Spring Cloud Alibaba基础教程:使用Sentinel实现接口限流

    最近管点闲事浪费了不少时间,感谢网友libinwalan的留言提醒.及时纠正路线,继续跟大家一起学习Spring Cloud Alibaba. Nacos作为注册中心和配置中心的基础教程,到这里先告一 ...

  4. SpringCloud(8)---zuul权限校验、接口限流

    zuul权限校验.接口限流 一.权限校验搭建 正常项目开发时,权限校验可以考虑JWT和springSecurity结合进行权限校验,这个后期会总结,这里做个基于ZuulFilter过滤器进行一个简单的 ...

  5. 基于注解的接口限流+统一session认证

    代码心得: 一个基本的做法:对于用户身份认证做到拦截器里,针对HandlerMethod进行统一拦截认证,根据方法上的注解标识,判别是否需要身份验证,并将查找出来的User实体存入ThreadLoca ...

  6. Spring Cloud(7):Zuul自定义过滤器和接口限流

    上文讲到了Zuul的基本使用: https://www.cnblogs.com/xuyiqing/p/10884860.html 自定义Zuul过滤器: package org.dreamtech.a ...

  7. Guava的RateLimiter实现接口限流

    最近开发需求中有需要对后台接口进行限流处理,整理了一下基本使用方法. 首先添加guava依赖: <dependency> <groupId>com.google.guava&l ...

  8. SpringCloud之Zuul高并发情况下接口限流(十二)

    高并发下接口限流技术gauva(谷歌的框架) MySql最大连接数3000: 原理:框架每秒向桶里放100个令牌,接口请求来了先去拿令牌,拿到令牌后才能继续向后走,否则不允许向后执行:当接口请求太频繁 ...

  9. 使用google的guova开发高并发下的接口限流

    使用google的guova开发高并发下的接口限流 使用google的guova进行限流 1.guova的限流方式,在定时产生定量的令牌,令牌的数量限制了流量 2.增加一个订单接口限流类OrderRa ...

  10. Spring Cloud Alibaba 使用Sentinel实现接口限流

    Sentinel是什么 Sentinel的官方标题是:分布式系统的流量防卫兵.从名字上来看,很容易就能猜到它是用来作服务稳定性保障的.对于服务稳定性保障组件,如果熟悉Spring Cloud的用户,第 ...

随机推荐

  1. HarmonyOS 3百机升级计划,来了!

    HarmonyOS 3规模升级来了! 为大家奉上百余款机型升级计划! 你的手机什么时候可以升级? 赶快下滑查看!

  2. 面试连环炮系列(二十六):什么情况下JVM频繁发生full GC

    1. 什么情况下JVM频繁发生full GC? full gc触发条件是老年代空间不足,具体原因有四个: 系统并发高.执行耗时长或者创建对象过多,导致 young gc频繁,且gc后存活对象太多,但是 ...

  3. Graph Embedding-DeepWalk

    一言以蔽之,DeepWalk是在graph上,通过随机游走来产生一段定长的结点序列,并将其通过word2vec的方式获得各个结点的embedding的算法. DeepWalk一共涉及以下几个内容: 随 ...

  4. 介绍一个气缸控制的FB程序块

    关键词: 气缸,双控.单控.电磁阀.感应器.初始位置(简称"始位").末端位置(简称"端位").屏蔽功能.延时功能.报警功能 正文: 1.为什么要做气缸FB功能 ...

  5. 重磅发布 阿里云数据中台全新产品DataTrust聚焦企业数据安全保障

    简介: DataTrust(隐私增强计算产品)是基于阿里云底层多项基础安全能力,经过阿里云数据中台丰富的客户业务实践,构建的一款为企业数据安全流通的产品. 随着包括零售.制造.金融等多行业数字化转型加 ...

  6. 建立成功平台工程的关键:自助式 IaC

    从技术上讲,云一直都是自助式服务,但由于其在实践中的复杂性,许多开发人员并不喜欢.随着公司采用现代架构(云原生.无服务器等)和新的提供商(多云.SaaS 应用程序),以及云提供商发布更多服务,云变得更 ...

  7. CF877F Ann and Books (分类统计贡献+普通莫队)

    CF877F Ann and Books 题意: 商店里有 \(n\) 本书,每本书中有 \(a_i\) 个 \(t_i=1/2\) 类问题. \(m\) 次询问,每次询问给出一个区间,求有多少个符合 ...

  8. 【Python爬虫案例】用python爬1000条哔哩哔哩搜索结果

    目录 一.爬取目标 二.讲解代码 三.同步讲解视频 四.完整源码 一.爬取目标 大家好,我是 @马哥python说 ,一名10年程序猿. 今天分享一期爬虫的案例,用python爬哔哩哔哩的搜索结果,也 ...

  9. 算法~PBKDF2-SHA让密码更安全

    摘要:在当今的数字世界中,密码安全是至关重要的.为了保护用户密码免受未经授权的访问和破解,Password-Based Key Derivation Function 2 (PBKDF2)算法成为了一 ...

  10. Java中的多态、抽象类和接口简述

    1. 引言 本文对Java编程中的多态.抽象类和接口概念进行了简明扼要的讲解,并对extends和implements进行了辨析. 2. 多态 多态是指所调用的方法只有在运行的时候才可以明确,如下例所 ...