应重写key_func为get_real_ip(),优先解析可信X-Forwarded-For中首个非内网IP,再fallback到request.remote_addr,并必须配置Redis后端以支持多进程部署。

Flask-Limiter 的 key_func 怎么写才能精准按 IP 限流?
默认情况下 Flask-Limiter 用的是 get_ipaddr,但它在 Nginx 反向代理后会拿到 127.0.0.1 或代理 IP,而不是真实客户端 IP。必须重写 key_func 才能生效。
推荐做法是优先读取 X-Forwarded-For 头(注意防伪造),再 fallback 到 request.remote_addr:
from flask import request
from flask_limiter import Limiter
<p>def get_real_ip():
if request.headers.get("X-Forwarded-For"):</p><h1>取第一个非内网 IP(简单防御伪造)</h1><pre class="brush:php;toolbar:false;"> ips = [ip.strip() for ip in request.headers["X-Forwarded-For"].split(",")]
for ip in ips:
if not ip.startswith(("10.", "172.16.", "192.168.", "127.0.0.1")):
return ip
return request.remote_addr
- 不要直接信任
X-Forwarded-For最末尾的 IP —— 它可能被客户端篡改 - 如果你的 Nginx 配置了
set_real_ip_from和real_ip_header X-Real-IP,也可以改用request.headers.get("X-Real-IP", request.remote_addr) - 测试时用
curl -H "X-Forwarded-For: 1.2.3.4" http://localhost/xxx模拟
怎么给特定路由加限流,又不影响 Flask 的 errorhandler?
直接在路由装饰器上加 @limiter.limit("100 per day") 是最常见写法,但要注意:如果该路由抛出异常(比如 abort(404)),Flask-Limiter 默认仍会计数 —— 这会导致错误请求也占额度,不合理。
- 把限流逻辑放在视图函数内部,用
limiter.current_limit+limiter.hit()手动控制更可控 - 或者用
@limiter.exempt显式排除某些路由(如/health、/static) - 对全局 4xx/5xx 错误页,建议在初始化 limiter 时传入
key_func=get_real_ip,并确保exempt_when不误放限流逻辑
Redis 作为后端时,为什么限流不生效或计数错乱?
Flask-Limiter 默认用内存存储,多进程部署(比如 gunicorn 多 worker)下会各自维护一套计数,完全失效。必须显式指定 Redis 后端。
初始化示例:
from flask_limiter import Limiter from flask_limiter.util import get_remote_address <p>limiter = Limiter( app, key_func=get_real_ip, storage_uri="redis://localhost:6379/1", strategy="fixed-window", # 推荐先用这个,比 moving-window 更稳定 ) </p>
- 确认 Redis 服务可连通,且未启用密码认证(或正确配置
storage_options) - 避免用
strategy="moving-window"在高并发下因 Redis pipeline 竞态导致漏限(尤其 Python 3.8+ 的 asyncio 混用场景) - 如果用 Docker 部署,确保 Flask 容器和 Redis 容器在同一个 network,且 host 不写
localhost而是服务名
如何调试限流是否真在工作?
最直接的方式是看 Redis 中的 key —— flask-limiter 生成的 key 形如 limit_123.45.67.89:/api/v1/data:100_per_day,可用 redis-cli keys "limit_*" 查看。
- 用
redis-cli monitor实时观察 incr / expire 操作,确认每次请求是否触发计数 - 在视图中加日志:
app.logger.info(f"Rate limit remaining: {limiter.current_limit.remaining}") - 注意:测试时别用浏览器反复 F5 —— 浏览器可能复用连接或带缓存头,改用
curl -s -w "\n%{http_code}\n" -o /dev/null URL更可靠
真正麻烦的不是配限流,而是当业务路径变复杂(比如 JWT 认证后按 user_id 限流、或同时按 IP + token 组合限流)时,key_func 的逻辑容易漏边界情况,比如未登录用户没 token、token 解析失败、IP 提取为空字符串等 —— 这些都会让限流变成“形同虚设”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











