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函式,而是會引發429
http錯誤。
速率限制規則:
[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
使用的詳細說明
看完上面的部分其實已經滿足大部分需求了,但是真實的情況下,可能還存在其他的定製服務,以下就是詳細說明。
初始化
初始化有兩種方式:
-
使用建構函式
from flask_limiter import Limiter from flask_limiter.util import get_remote_address .... limiter = Limiter(app, key_func=get_remote_address)
-
使用延遲應用初始化
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_func
,flask_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-For
和X-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)