新增速率限制;增强兼容性
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
# API-Python
|
# API-Python
|
||||||
|
|
||||||
> 基于Python的本地Web API项目
|
> 基于Python3.9+的Web API项目
|
||||||
|
|
||||||
#### v260716补丁
|
#### v260728
|
||||||
1. 优化import、修改README
|
1. 优化(修复)import
|
||||||
2. 修复import异常
|
2. 修改README
|
||||||
|
3. 新增IP请求速率限制(默认30/min)
|
||||||
|
4. 增强兼容性
|
||||||
|
|
||||||
#### v260716
|
#### v260716
|
||||||
1. 新增代理接口(包含白名单及密码限制)
|
1. 新增代理接口(包含白名单及密码限制)
|
||||||
@@ -19,3 +21,5 @@
|
|||||||
|
|
||||||
#### v260512
|
#### v260512
|
||||||
1. 新增农历、运势、唐诗、地址、时间、谜语等接口及对应数据
|
1. 新增农历、运势、唐诗、地址、时间、谜语等接口及对应数据
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import os, json, random, socket, logging, ipaddress, urllib.error, urllib.parse, urllib.request
|
import os, json, random, socket, logging, ipaddress, urllib.error, urllib.parse, urllib.request, threading
|
||||||
|
from collections import defaultdict, deque
|
||||||
|
from time import monotonic
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from zoneinfo import ZoneInfo, available_timezones
|
from zoneinfo import ZoneInfo, available_timezones
|
||||||
@@ -90,6 +92,34 @@ for item in RIDDLE_DATA:
|
|||||||
RIDDLE_RIDDLE_MAP[riddle_lower].append(item)
|
RIDDLE_RIDDLE_MAP[riddle_lower].append(item)
|
||||||
RIDDLE_ANSWER_MAP[answer_lower].append(item)
|
RIDDLE_ANSWER_MAP[answer_lower].append(item)
|
||||||
|
|
||||||
|
RATE_LIMIT = 30
|
||||||
|
RATE_WINDOW_SECONDS = 60
|
||||||
|
_request_times = defaultdict(deque)
|
||||||
|
_rate_limit_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def enforce_rate_limit():
|
||||||
|
now = monotonic()
|
||||||
|
forwarded_for = request.headers.get("X-Forwarded-For", "")
|
||||||
|
client_ip = forwarded_for.split(",", 1)[0].strip() or request.remote_addr or "unknown"
|
||||||
|
with _rate_limit_lock:
|
||||||
|
timestamps = _request_times[client_ip]
|
||||||
|
cutoff = now - RATE_WINDOW_SECONDS
|
||||||
|
while timestamps and timestamps[0] <= cutoff:
|
||||||
|
timestamps.popleft()
|
||||||
|
if len(timestamps) >= RATE_LIMIT:
|
||||||
|
retry_after = max(1, int(timestamps[0] + RATE_WINDOW_SECONDS - now + 0.999))
|
||||||
|
response = jsonify({
|
||||||
|
"error": "请求过于频繁,请稍后再试",
|
||||||
|
"limit": f"{RATE_LIMIT}/min",
|
||||||
|
"retry_after": retry_after,
|
||||||
|
})
|
||||||
|
response.status_code = 429
|
||||||
|
response.headers["Retry-After"] = str(retry_after)
|
||||||
|
return response
|
||||||
|
timestamps.append(now)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/favicon.ico")
|
@app.route("/favicon.ico")
|
||||||
def favicon():
|
def favicon():
|
||||||
@@ -135,7 +165,7 @@ def lunar():
|
|||||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||||
if set(args_lower.keys()) - {"date"}:
|
if set(args_lower.keys()) - {"date"}:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"error": f"{set(args_lower.keys()) - {"date"}}参数非法",
|
"error": "{}参数非法".format(set(args_lower.keys()) - {"date"}),
|
||||||
"allowed_params": ["date=YYYY-MM-DD"],
|
"allowed_params": ["date=YYYY-MM-DD"],
|
||||||
})
|
})
|
||||||
ds = args_lower.get("date", "").strip()
|
ds = args_lower.get("date", "").strip()
|
||||||
@@ -193,7 +223,7 @@ def luck():
|
|||||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||||
if set(args_lower.keys()) - {"id", "title", "type"}:
|
if set(args_lower.keys()) - {"id", "title", "type"}:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"error": f"{set(args_lower.keys()) - {"id", "title", "type"}}参数非法",
|
"error": "{}参数非法".format(set(args_lower.keys()) - {"id", "title", "type"}),
|
||||||
"allowed_params": ["id=xxx", "title=xxx", "type=xxx"],
|
"allowed_params": ["id=xxx", "title=xxx", "type=xxx"],
|
||||||
})
|
})
|
||||||
luck_id = args_lower.get("id", "").strip()
|
luck_id = args_lower.get("id", "").strip()
|
||||||
@@ -251,7 +281,7 @@ def poem():
|
|||||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||||
if set(args_lower.keys()) - {"id", "title", "author", "type", "full"}:
|
if set(args_lower.keys()) - {"id", "title", "author", "type", "full"}:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"error": f"{set(args_lower.keys()) - {"id", "title", "author", "type", "full"}}参数非法",
|
"error": "{}参数非法".format(set(args_lower.keys()) - {"id", "title", "author", "type", "full"}),
|
||||||
"allowed_params": ["id=xxx", "title=xxx", "author=xxx", "type=xxx", "full=true/false"],
|
"allowed_params": ["id=xxx", "title=xxx", "author=xxx", "type=xxx", "full=true/false"],
|
||||||
})
|
})
|
||||||
poem_id = args_lower.get("id", "").strip()
|
poem_id = args_lower.get("id", "").strip()
|
||||||
@@ -347,7 +377,7 @@ def ip():
|
|||||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||||
if set(args_lower.keys()) - {"name"}:
|
if set(args_lower.keys()) - {"name"}:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"error": f"{set(args_lower.keys()) - {"name"}}参数非法",
|
"error": "{}参数非法".format(set(args_lower.keys()) - {"name"}),
|
||||||
"allowed_params": ["name=xxx"],
|
"allowed_params": ["name=xxx"],
|
||||||
})
|
})
|
||||||
query_name = args_lower.get("name", "").strip()
|
query_name = args_lower.get("name", "").strip()
|
||||||
@@ -417,7 +447,7 @@ def time_api():
|
|||||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||||
if set(args_lower.keys()) - {"tz"}:
|
if set(args_lower.keys()) - {"tz"}:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"error": f"{set(args_lower.keys()) - {"tz"}}参数非法",
|
"error": "{}参数非法".format(set(args_lower.keys()) - {"tz"}),
|
||||||
"allowed_params": ["tz=xxx"],
|
"allowed_params": ["tz=xxx"],
|
||||||
})
|
})
|
||||||
tz = args_lower.get("tz", "").strip()
|
tz = args_lower.get("tz", "").strip()
|
||||||
@@ -482,7 +512,7 @@ def riddle():
|
|||||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||||
if set(args_lower.keys()) - {"riddle", "answer"}:
|
if set(args_lower.keys()) - {"riddle", "answer"}:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"error": f"{set(args_lower.keys()) - {"riddle", "answer"}}参数非法",
|
"error": "{}参数非法".format(set(args_lower.keys()) - {"riddle", "answer"}),
|
||||||
"allowed_params": ["riddle=xxx", "answer=xxx"],
|
"allowed_params": ["riddle=xxx", "answer=xxx"],
|
||||||
})
|
})
|
||||||
query_riddle = args_lower.get("riddle", "").strip()
|
query_riddle = args_lower.get("riddle", "").strip()
|
||||||
@@ -532,7 +562,7 @@ def sci():
|
|||||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||||
if set(args_lower.keys()) - {"nane", "name"}:
|
if set(args_lower.keys()) - {"nane", "name"}:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"error": f"{set(args_lower.keys()) - {"nane", "name"}}参数非法",
|
"error": "{}参数非法".format(set(args_lower.keys()) - {"nane", "name"}),
|
||||||
"allowed_params": ["nane=xxx"],
|
"allowed_params": ["nane=xxx"],
|
||||||
})
|
})
|
||||||
publication_name = args_lower.get("nane", "").strip() or args_lower.get("name", "").strip()
|
publication_name = args_lower.get("nane", "").strip() or args_lower.get("name", "").strip()
|
||||||
@@ -604,7 +634,7 @@ def agent():
|
|||||||
extra_params = set(args_lower.keys()) - allowed_keys
|
extra_params = set(args_lower.keys()) - allowed_keys
|
||||||
if extra_params:
|
if extra_params:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"error": f"{extra_params}参数非法",
|
"error": "{}参数非法".format(extra_params),
|
||||||
"allowed_params": ["url=xxx", "ref=xxx", "pwd=xxx"],
|
"allowed_params": ["url=xxx", "ref=xxx", "pwd=xxx"],
|
||||||
})
|
})
|
||||||
target_url = args_lower.get("url", "").strip()
|
target_url = args_lower.get("url", "").strip()
|
||||||
@@ -692,7 +722,7 @@ def pan_file_download():
|
|||||||
extra_params = set(args_lower.keys()) - allowed_keys
|
extra_params = set(args_lower.keys()) - allowed_keys
|
||||||
if extra_params:
|
if extra_params:
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"error": f"{extra_params}参数非法",
|
"error": "{}参数非法".format(extra_params),
|
||||||
"allowed_params": ["name=xxx", "download=true/false"],
|
"allowed_params": ["name=xxx", "download=true/false"],
|
||||||
})
|
})
|
||||||
file_name = args_lower.get("name", "").strip()
|
file_name = args_lower.get("name", "").strip()
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ flask
|
|||||||
flask-cors
|
flask-cors
|
||||||
lunar-python
|
lunar-python
|
||||||
py-ip2region
|
py-ip2region
|
||||||
|
tzdata
|
||||||
Reference in New Issue
Block a user