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

保持Ajax连接存活以规避防火墙超时问题

这种防火墙硬超时的问题我太熟了——之前帮团队解决过类似的批量数据计算场景,既然改不了防火墙规则,绝对不能死磕单个长连接,得换个思路。给你几个经过实践验证的可行方案:

可行解决方案

1. 任务异步化 + 定时轮询(最稳妥)

把原来的单长请求拆成两步,彻底避开防火墙超时限制:

  • 第一步:发起短请求提交计算任务,服务器立即返回唯一任务ID,同时后台异步启动计算(别让Web进程阻塞)。
  • 第二步:前端每隔20秒(小于防火墙30秒阈值)发起短请求,用任务ID查询进度/结果,直到拿到最终数据。

示例代码

前端(JS):

// 第一步:提交计算任务
fetch('/start-calc.php', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({ rawData: yourInputData })
})
.then(res => res.json())
.then(taskInfo => {
  // 第二步:定时轮询结果
  const pollTimer = setInterval(() => {
    fetch(`/check-result.php?taskId=${taskInfo.id}`)
    .then(res => res.json())
    .then(result => {
      if (result.status === 'done') {
        clearInterval(pollTimer);
        // 处理最终计算结果
        renderResult(result.data);
      } else if (result.status === 'failed') {
        clearInterval(pollTimer);
        alert('计算出错,请稍后重试');
      }
      // 仍在处理中则继续轮询
    });
  }, 20000); // 20秒一次,留足缓冲区避免触发超时
});

后端(PHP):

  • start-calc.php:生成任务ID,启动异步计算进程:
<?php
// 生成唯一任务ID
$taskId = uniqid('calc_', true);
// 用Redis保存任务状态(也可以用数据库)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->setex("task:$taskId", 3600, json_encode([
  'status' => 'processing',
  'input' => json_decode(file_get_contents('php://input'), true)
]));

// 启动后台计算进程(避免阻塞Web服务)
exec('php background-calc.php ' . escapeshellarg($taskId) . ' > /dev/null 2>&1 &');

echo json_encode(['id' => $taskId]);
?>
  • background-calc.php:后台执行耗时计算,完成后更新状态:
<?php
$taskId = $argv[1];
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$taskData = json_decode($redis->get("task:$taskId"), true);

// 模拟耗时计算(比如数据库批量统计、复杂运算)
sleep(120);

// 计算完成后更新任务状态
$redis->set("task:$taskId", json_encode([
  'status' => 'done',
  'data' => '这里是最终计算结果'
]));
?>
  • check-result.php:查询任务状态接口:
<?php
$taskId = $_GET['taskId'];
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$taskStatus = $redis->get("task:$taskId");
echo $taskStatus ?: json_encode(['status' => 'not_found']);
?>

2. 长轮询(Long Polling)

和普通轮询不同,长轮询是前端发起请求后,服务器hold住连接直到有结果或接近超时(比如25秒)再返回,前端收到响应后立即发起下一次请求。既能减少请求次数,又能避开防火墙超时。

示例代码

前端:

function longPollTask(taskId) {
  fetch(`/long-poll.php?taskId=${taskId}`)
  .then(res => res.json())
  .then(result => {
    if (result.status === 'done') {
      renderResult(result.data);
    } else {
      // 立即发起下一次长轮询
      setTimeout(() => longPollTask(taskId), 1000);
    }
  })
  .catch(err => {
    // 网络出错后重试
    setTimeout(() => longPollTask(taskId), 5000);
  });
}

// 先提交任务,再启动长轮询
fetch('/start-calc.php', { method: 'POST', body: ... })
.then(res => res.json())
.then(taskInfo => longPollTask(taskInfo.id));

后端(long-poll.php):

<?php
$taskId = $_GET['taskId'];
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// 设置25秒超时,比防火墙阈值少5秒
$timeout = time() + 25;
while (time() < $timeout) {
  $task = json_decode($redis->get("task:$taskId"), true);
  if ($task['status'] === 'done' || $task['status'] === 'failed') {
    echo json_encode($task);
    exit;
  }
  // 每秒查一次,避免CPU空转
  sleep(1);
}

// 超时后返回"处理中",触发前端重新发起请求
echo json_encode(['status' => 'processing']);
?>

3. Server-Sent Events(SSE)

SSE是HTML5原生特性,允许服务器主动向客户端推送数据,连接会自动保持心跳,相比普通Ajax长连接更不容易被防火墙判定为“闲置连接”。不过要注意,部分老防火墙可能对SSE有特殊限制,建议先在测试环境验证。

示例代码

前端:

// 先提交任务
fetch('/start-calc.php', { method: 'POST', body: ... })
.then(res => res.json())
.then(taskInfo => {
  // 建立SSE连接
  const eventSource = new EventSource(`/sse-push.php?taskId=${taskInfo.id}`);
  
  eventSource.onmessage = function(event) {
    const result = JSON.parse(event.data);
    if (result.status === 'done') {
      eventSource.close();
      renderResult(result.data);
    } else if (result.status === 'failed') {
      eventSource.close();
      alert('计算失败');
    }
  };
  
  eventSource.onerror = function() {
    eventSource.close();
    // 出错后重试连接
    setTimeout(() => {/* 重新建立SSE连接 */}, 5000);
  };
});

后端(sse-push.php):

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');

$taskId = $_GET['taskId'];
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

while (true) {
  $task = json_decode($redis->get("task:$taskId"), true);
  if ($task['status'] === 'done' || $task['status'] === 'failed') {
    echo "data: " . json_encode($task) . "\n\n";
    ob_flush();
    flush();
    exit;
  }
  
  // 每10秒发送心跳,避免连接被判定为闲置
  echo ": ping\n\n";
  ob_flush();
  flush();
  
  sleep(10);
}
?>

关键注意事项

  • 所有方案都要保证后台计算是异步的,绝对不能让Web服务器进程长时间阻塞,否则会耗尽服务器连接数。
  • 任务ID要足够唯一,建议用带时间戳的UUID,避免冲突。
  • 要处理任务超时场景,比如计算超过1小时仍未完成,前端要提示用户并终止轮询。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:26:22