如何让客户端代发请求至外部API,规避Flask应用的限流与IP封禁?
用反向Ajax实现客户端代发请求(替代WebSocket)
针对你在Google App Engine上部署Flask应用时遇到的外部API限流/IP封禁问题,用客户端代发请求的思路完全可行,下面给你两个经典的反向Ajax实现方案,比WebSocket更轻量:
方案一:长轮询(Long Polling)
这是兼容性最好的反向Ajax实现,原理是前端发起请求后,后端保持连接直到拿到结果或超时,之后前端再重新发起请求,完全适配所有浏览器。
Flask后端代码
from flask import Flask, render_template, request, jsonify, session import uuid import time app = Flask(__name__) app.secret_key = '生产环境换成安全的密钥' # 生产环境别用内存存储,换成Redis或GAE Cloud Datastore client_tasks = {} @app.route('/') def index(): # 生成唯一ID,关联前后端请求 req_id = str(uuid.uuid4()) session['req_id'] = req_id return render_template('index.html', req_id=req_id) # 通知前端发起外部API请求 @app.route('/trigger-client-request', methods=['POST']) def trigger_request(): req_id = request.json.get('req_id') # 传递外部API配置,也可前端硬编码 api_config = { 'url': 'https://目标外部API地址', 'method': 'GET', 'headers': {'Accept': 'application/json'} } client_tasks[req_id] = {'status': 'pending', 'config': api_config} return jsonify({'status': 'ok'}) # 接收前端提交的外部API结果 @app.route('/submit-api-result', methods=['POST']) def submit_result(): req_id = request.json.get('req_id') api_result = request.json.get('result') if req_id in client_tasks: client_tasks[req_id]['status'] = 'completed' client_tasks[req_id]['data'] = api_result return jsonify({'status': 'ok'}) return jsonify({'status': 'error', 'msg': '无效请求ID'}), 400 # 前端轮询获取处理后的结果 @app.route('/poll-processed-data/<req_id>') def poll_result(req_id): timeout_at = time.time() + 30 # 30秒超时 while time.time() < timeout_at: if req_id in client_tasks and client_tasks[req_id]['status'] == 'completed': raw_data = client_tasks[req_id]['data'] # 替换为你的数据处理逻辑 processed_data = int(raw_data) + 1 del client_tasks[req_id] # 清理临时数据 return jsonify({'final_data': processed_data}) time.sleep(1) return jsonify({'status': 'timeout'}), 202
前端代码(index.html)
<!DOCTYPE html> <html> <head> <title>客户端代请求</title> </head> <body> <div id="final-result"></div> <script> const reqId = "{{ req_id }}"; // 通知后端准备发起请求 async function notifyBackend() { const res = await fetch('/trigger-client-request', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({req_id: reqId}) }); const data = await res.json(); if (data.status === 'ok') { await sendExternalApiRequest(); } } // 发送请求到外部API async function sendExternalApiRequest() { const apiConfig = { url: 'https://目标外部API地址', method: 'GET', headers: {'Accept': 'application/json'} }; try { const apiRes = await fetch(apiConfig.url, { method: apiConfig.method, headers: apiConfig.headers }); const resultText = await apiRes.text(); // 提交结果给后端 await fetch('/submit-api-result', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ req_id: reqId, result: resultText }) }); } catch (err) { console.error('外部API请求失败:', err); } } // 轮询获取处理后的结果 async function getProcessedResult() { while (true) { const res = await fetch(`/poll-processed-data/${reqId}`); const data = await res.json(); if (data.final_data !== undefined) { document.getElementById('final-result').textContent = `处理结果:${data.final_data}`; break; } else if (data.status === 'timeout') { continue; // 超时后重新轮询 } } } window.onload = async function() { await notifyBackend(); await getProcessedResult(); }; </script> </body> </html>
方案二:Server-Sent Events(SSE)
SSE是HTML5标准特性,允许后端主动向前端推送数据,比长轮询更高效(无需频繁重建连接),唯一限制是IE浏览器不支持,目前主流浏览器都兼容。
Flask后端代码
from flask import Flask, render_template, request, jsonify, session, Response import uuid import time app = Flask(__name__) app.secret_key = '生产环境换成安全密钥' # 生产环境用Redis存储结果 client_results = {} @app.route('/') def index(): req_id = str(uuid.uuid4()) session['req_id'] = req_id return render_template('sse-page.html', req_id=req_id) # SSE流,后端主动推送处理后的结果 @app.route('/sse-stream/<req_id>') def sse_stream(req_id): def generate_updates(): while True: if req_id in client_results: raw_data = client_results[req_id] processed_data = int(raw_data) + 1 del client_results[req_id] # SSE格式要求:data: 内容\n\n yield f"data: {jsonify({'final_data': processed_data}).text}\n\n" break time.sleep(1) return Response(generate_updates(), mimetype='text/event-stream') # 前端获取外部API配置 @app.route('/get-api-config/<req_id>') def get_api_config(req_id): return jsonify({ 'url': 'https://目标外部API地址', 'method': 'GET', 'headers': {'Accept': 'application/json'} }) # 接收前端提交的外部API结果 @app.route('/send-api-result', methods=['POST']) def send_result(): req_id = request.json.get('req_id') result = request.json.get('result') client_results[req_id] = result return jsonify({'status': 'ok'})
前端代码(sse-page.html)
<!DOCTYPE html> <html> <head> <title>SSE代请求</title> </head> <body> <div id="final-result"></div> <script> const reqId = "{{ req_id }}"; // 建立SSE连接,等待后端推送结果 const sse = new EventSource(`/sse-stream/${reqId}`); sse.onmessage = function(event) { const data = JSON.parse(event.data); document.getElementById('final-result').textContent = `处理结果:${data.final_data}`; sse.close(); // 拿到结果后关闭连接 }; sse.onerror = function(err) { console.error('SSE连接出错:', err); sse.close(); }; // 发起外部API请求并提交结果 async function handleExternalApi() { const configRes = await fetch(`/get-api-config/${reqId}`); const config = await configRes.json(); try { const apiRes = await fetch(config.url, { method: config.method, headers: config.headers }); const resultText = await apiRes.text(); await fetch('/send-api-result', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ req_id: reqId, result: resultText }) }); } catch (err) { console.error('外部API请求失败:', err); sse.close(); } } window.onload = handleExternalApi; </script> </body> </html>
注意事项
- 生产环境禁止用内存字典存储临时数据,GAE是多实例架构,内存数据不共享,建议换成Redis或Cloud Datastore。
- 增加超时和过期数据清理逻辑,避免僵尸数据占用资源。
- 外部API需要特定请求头(如User-Agent)时,可直接使用前端浏览器的原生请求头,更贴近真实用户请求。
- 若需兼容IE,SSE可引入polyfill,不过目前IE使用率极低,一般无需处理。
内容的提问来源于stack exchange,提问作者Bear
相关产品推荐
相关产品推荐

