AJAX调用长耗时run.py脚本仅显示部分输出的问题排查
问题根源
普通AJAX是一次性请求-响应模型:前端发请求后,服务器会默认把脚本的所有输出先缓冲起来,直到脚本完全执行完毕,才一次性把响应返回给前端。你的run.py要跑2分钟,自然拿不到中间的输出内容。
解决方案1:用Server-Sent Events(SSE)实时推送
这是最适合这种单向实时输出场景的方案,实现起来简单,不需要额外依赖。
前端JS代码
把你的AJAX换成SSE连接,实时接收服务器推送的输出:
$(document).ready(function(){ $('#sub').click(function(){ alert("connecting to host"); // 创建SSE连接,指向你的Python脚本 const eventSource = new EventSource('/cgi-bin/run.py'); // 每次收到服务器消息,追加到页面的输出容器(比如<div id="output"></div>) eventSource.onmessage = function(event) { $('#output').append(event.data + '<br>'); }; // 脚本执行完后,服务器会关闭连接,这里处理结束逻辑 eventSource.onclose = function() { alert("脚本执行完成"); eventSource.close(); }; // 处理连接错误 eventSource.onerror = function(error) { console.error('连接出错:', error); eventSource.close(); }; }); });
修改run.py脚本
要输出SSE格式的内容,同时强制刷新输出缓冲区,避免服务器缓冲:
#!/usr/bin/env python3 import sys import time # 设置SSE要求的响应头,告诉浏览器这是流式事件 print("Content-Type: text/event-stream") print("Cache-Control: no-cache") print("Connection: keep-alive") print() # 必须用空行分隔响应头和内容 # 模拟你的长时间任务,这里每12秒输出一次,总时长2分钟 for step in range(1, 11): # SSE的消息格式是:data: 内容\n\n print(f"data: 当前执行第 {step} 步...") print() sys.stdout.flush() # 关键:强制把输出推送给前端,不要缓冲 time.sleep(12) # 发送执行完成的消息,然后关闭连接 print("data: 脚本执行完成!") print() sys.stdout.flush()
解决方案2:轮询获取输出(兼容旧浏览器)
如果你的场景需要兼容不支持SSE的旧浏览器,可以用轮询方案:前端定期请求服务器,获取脚本的最新输出。
前端JS代码
$(document).ready(function(){ let lastOutputLength = 0; let pollTimer; $('#sub').click(function(){ alert("connecting to host"); // 先发送请求启动脚本 $.ajax({ type:'POST', url:'/cgi-bin/start_run.py', success:function(){ // 每5秒轮询一次,获取最新输出 pollTimer = setInterval(function(){ $.get('/cgi-bin/get_output.py', function(currentOutput){ // 只显示新增的内容,避免重复输出 const newContent = currentOutput.slice(lastOutputLength); if(newContent){ $('#output').append(newContent + '<br>'); lastOutputLength = currentOutput.length; } // 判断脚本是否执行完成(比如输出包含特定关键词) if(currentOutput.includes("脚本执行完成")){ clearInterval(pollTimer); alert("脚本执行结束"); } }); }, 5000); } }); }); });
配套Python脚本
- start_run.py:负责后台启动run.py,并把输出写到临时文件
#!/usr/bin/env python3 import subprocess import os # 临时文件路径,用来保存脚本输出 OUTPUT_FILE = '/tmp/run_script_output.txt' # 启动run.py,后台运行,把标准输出和错误输出都写到临时文件 subprocess.Popen( ['python3', '/cgi-bin/run.py'], stdout=open(OUTPUT_FILE, 'w'), stderr=subprocess.STDOUT ) # 返回响应给前端 print("Content-Type: text/html") print() print("脚本已启动,正在执行...")
- run.py:你的原有脚本,正常输出内容即可
#!/usr/bin/env python3 import time for step in range(1, 11): print(f"执行第 {step} 步,处理中...") time.sleep(12) print("脚本执行完成")
- get_output.py:读取临时文件的内容,返回给前端
#!/usr/bin/env python3 import os OUTPUT_FILE = '/tmp/run_script_output.txt' # 设置响应头 print("Content-Type: text/plain") print() # 读取并返回输出内容 if os.path.exists(OUTPUT_FILE): with open(OUTPUT_FILE, 'r') as f: print(f.read(), end='')
关键注意事项
- 服务器长连接配置:如果用Apache,需要调整
ScriptTimeout(默认300秒,足够覆盖你的2分钟任务);如果用Nginx,要调整proxy_read_timeout等参数,避免服务器提前断开长连接。 - 强制刷新输出:Python脚本里一定要加
sys.stdout.flush(),否则输出会被系统缓冲,直到脚本结束才发送。 - CGI运行限制:有些服务器会限制CGI脚本的运行时间,需要检查并调整对应配置。
内容的提问来源于stack exchange,提问作者Rithesh Bhat
相关产品推荐
相关产品推荐

