新增速率限制;增强兼容性
This commit is contained in:
@@ -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 datetime import date, datetime
|
||||
from zoneinfo import ZoneInfo, available_timezones
|
||||
@@ -90,6 +92,34 @@ for item in RIDDLE_DATA:
|
||||
RIDDLE_RIDDLE_MAP[riddle_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")
|
||||
def favicon():
|
||||
@@ -135,7 +165,7 @@ def lunar():
|
||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||
if set(args_lower.keys()) - {"date"}:
|
||||
return jsonify({
|
||||
"error": f"{set(args_lower.keys()) - {"date"}}参数非法",
|
||||
"error": "{}参数非法".format(set(args_lower.keys()) - {"date"}),
|
||||
"allowed_params": ["date=YYYY-MM-DD"],
|
||||
})
|
||||
ds = args_lower.get("date", "").strip()
|
||||
@@ -193,7 +223,7 @@ def luck():
|
||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||
if set(args_lower.keys()) - {"id", "title", "type"}:
|
||||
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"],
|
||||
})
|
||||
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()}
|
||||
if set(args_lower.keys()) - {"id", "title", "author", "type", "full"}:
|
||||
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"],
|
||||
})
|
||||
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()}
|
||||
if set(args_lower.keys()) - {"name"}:
|
||||
return jsonify({
|
||||
"error": f"{set(args_lower.keys()) - {"name"}}参数非法",
|
||||
"error": "{}参数非法".format(set(args_lower.keys()) - {"name"}),
|
||||
"allowed_params": ["name=xxx"],
|
||||
})
|
||||
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()}
|
||||
if set(args_lower.keys()) - {"tz"}:
|
||||
return jsonify({
|
||||
"error": f"{set(args_lower.keys()) - {"tz"}}参数非法",
|
||||
"error": "{}参数非法".format(set(args_lower.keys()) - {"tz"}),
|
||||
"allowed_params": ["tz=xxx"],
|
||||
})
|
||||
tz = args_lower.get("tz", "").strip()
|
||||
@@ -482,7 +512,7 @@ def riddle():
|
||||
args_lower = {k.lower(): v for k, v in request.args.items()}
|
||||
if set(args_lower.keys()) - {"riddle", "answer"}:
|
||||
return jsonify({
|
||||
"error": f"{set(args_lower.keys()) - {"riddle", "answer"}}参数非法",
|
||||
"error": "{}参数非法".format(set(args_lower.keys()) - {"riddle", "answer"}),
|
||||
"allowed_params": ["riddle=xxx", "answer=xxx"],
|
||||
})
|
||||
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()}
|
||||
if set(args_lower.keys()) - {"nane", "name"}:
|
||||
return jsonify({
|
||||
"error": f"{set(args_lower.keys()) - {"nane", "name"}}参数非法",
|
||||
"error": "{}参数非法".format(set(args_lower.keys()) - {"nane", "name"}),
|
||||
"allowed_params": ["nane=xxx"],
|
||||
})
|
||||
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
|
||||
if extra_params:
|
||||
return jsonify({
|
||||
"error": f"{extra_params}参数非法",
|
||||
"error": "{}参数非法".format(extra_params),
|
||||
"allowed_params": ["url=xxx", "ref=xxx", "pwd=xxx"],
|
||||
})
|
||||
target_url = args_lower.get("url", "").strip()
|
||||
@@ -692,7 +722,7 @@ def pan_file_download():
|
||||
extra_params = set(args_lower.keys()) - allowed_keys
|
||||
if extra_params:
|
||||
return jsonify({
|
||||
"error": f"{extra_params}参数非法",
|
||||
"error": "{}参数非法".format(extra_params),
|
||||
"allowed_params": ["name=xxx", "download=true/false"],
|
||||
})
|
||||
file_name = args_lower.get("name", "").strip()
|
||||
|
||||
Reference in New Issue
Block a user