如何使用Bun与Hono实现Shell脚本输出的实时流式传输?
实现Bun + Hono执行Shell脚本的实时流式输出
我正尝试使用Bun和Hono实现Shell脚本执行的实时流式输出。目前输出会被缓冲,仅在脚本完成后一次性显示,但我需要脚本产生输出时就进行实时流式传输。
Shell脚本(deploy-container.sh)
#!/bin/bash # Script that deploys a container and shows progress echo "🚀 Starting deployment process..." echo " → Deploying Docker container..." # ... more echo statements and actual deployment logic echo "✅ Deployed successfully!"
当前问题
当前使用Bun的$操作符执行命令时,会缓冲所有输出直到脚本完全执行完毕,无法实现实时流式输出。要解决这个问题,需要改用支持流式处理的进程调用方式,并实时将输出写入Hono的响应流。
修改后的实现
deploy.ts
import { spawn } from "bun"; export const runDeployment = async ( imageName: string, subDomain: string, port: number, onOutput: (data: string) => Promise<void> ) => { try { // 使用Bun.spawn创建子进程,实时获取输出流 const process = spawn(["sudo", "-S", "./deploy-container.sh", imageName, subDomain, port], { stdin: "pipe", stdout: "pipe", stderr: "pipe", }); // 向sudo输入密码 process.stdin.write(`${process.env.SYSTEM_PASSWORD}\n`); process.stdin.end(); // 实时读取标准输出 for await (const chunk of process.stdout) { await onOutput(chunk.toString()); } // 实时读取标准错误 for await (const chunk of process.stderr) { await onOutput(`[ERROR] ${chunk.toString()}`); } // 检查进程退出码 const exitCode = await process.exited; if (exitCode !== 0) { throw new Error(`Deployment failed with exit code ${exitCode}`); } } catch (e) { console.error("Error running deployment:", e); throw { success: false, error: e instanceof Error ? e.message : String(e), }; } };
index.ts
import { stream } from "hono/streaming"; import { runDeployment } from "../lib/deploy"; app.post("/deployContainer/:repoName", async (c) => { try { const repoName = c.req.param("repoName"); const imageName = `${repoName}:latest`; const port = generateRandomPort(); const subDomain = generateSubdomain(repoName); return stream(c, async (clientStream) => { // 可选:心跳机制,防止长连接被中断 const heartbeat = setInterval(async () => { try { await clientStream.write(" "); } catch (err) { console.error("Heartbeat failed:", err); clearInterval(heartbeat); } }, 1000); try { // 传入回调函数,实时处理脚本输出 await runDeployment(imageName, subDomain, port, async (data) => { // 按行分割输出,模拟终端实时打印效果 const lines = data.split("\n"); for (const line of lines) { if (line.trim()) { await clientStream.writeln(line); } } }); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); await clientStream.write(`Error deploying container: ${errorMsg}\n`); } finally { clearInterval(heartbeat); await clientStream.close(); } }); } catch (e) { console.error(e); return c.json({ success: false, error: e }, 500); } });
关键改动说明
- 替换进程调用方式:用
Bun.spawn替代$操作符,spawn允许实时访问子进程的输出流,而非缓冲所有内容到进程结束。 - 实时输出回调:新增
onOutput回调函数,每次捕获到脚本输出时立即触发,将内容写入Hono的响应流。 - 拆分输出内容:将捕获到的二进制chunk转成字符串后按行分割,确保每一行输出都能实时发送,模拟终端的输出体验。
- 处理错误流:同时监听标准错误输出,确保脚本执行中的错误信息也能实时展示。
内容的提问来源于stack exchange,提问作者user19126561
相关产品推荐
相关产品推荐

