You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让客户端代发请求至外部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>

注意事项

  1. 生产环境禁止用内存字典存储临时数据,GAE是多实例架构,内存数据不共享,建议换成Redis或Cloud Datastore。
  2. 增加超时和过期数据清理逻辑,避免僵尸数据占用资源。
  3. 外部API需要特定请求头(如User-Agent)时,可直接使用前端浏览器的原生请求头,更贴近真实用户请求。
  4. 若需兼容IE,SSE可引入polyfill,不过目前IE使用率极低,一般无需处理。

内容的提问来源于stack exchange,提问作者Bear

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 10:55:59