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

如何在Spring Boot中通过REST调用实现SSH脚本输出的实时流式传输

解决Spring Boot实时流式传输SSH脚本输出的问题

我来帮你梳理下问题的核心和解决方案:你当前的实现之所以会等待8秒才一次性返回所有内容,主要是因为commandWithContinousRead是阻塞方法,且你没有正确处理流式输出的刷新逻辑,另外StreamingResponseBody的使用方式也需要调整。下面是具体的优化方案和测试方法:

推荐方案:使用SSEEmitter实现实时推送

Server-Sent Events(SSE)其实非常适合你的场景——它就是为服务器主动向客户端流式推送实时数据设计的,而且Spring的SseEmitter能帮你自动处理SSE的格式规范(比如每个事件的data: ...\n\n结构),不用手动拼接。

1. 修改Spring接口代码

首先我们改用SseEmitter,并把SSH的阻塞调用放到异步线程池中,避免阻塞请求线程:

import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.*;

// 先配置一个异步线程池,专门处理SSH的阻塞任务
@Configuration
public class AsyncTaskConfig {
    @Bean(name = "sshTaskExecutor")
    public ThreadPoolTaskExecutor sshTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(10);
        executor.setThreadNamePrefix("SSH-Worker-");
        executor.initialize();
        return executor;
    }
}

// 你的Controller类
@RestController
public class StreamController {
    private final SSHManager sshManager;
    private final ThreadPoolTaskExecutor sshTaskExecutor;

    // 构造注入
    public StreamController(SSHManager sshManager, ThreadPoolTaskExecutor sshTaskExecutor) {
        this.sshManager = sshManager;
        this.sshTaskExecutor = sshTaskExecutor;
    }

    @GetMapping("/stream-this")
    public SseEmitter streamScript() {
        // 设置超时时间,根据你的脚本时长调整(这里设30秒)
        SseEmitter emitter = new SseEmitter(30000L);

        // 把SSH任务放到异步线程池执行,避免阻塞请求线程
        sshTaskExecutor.submit(() -> {
            try {
                sshManager.commandWithContinousRead("bash /scripts/stream-this.sh", echo -> {
                    try {
                        // 发送SSE事件,自动处理格式
                        emitter.send(SseEmitter.event().data(echo));
                        System.out.println("已推送内容:" + echo);
                    } catch (IOException e) {
                        // 客户端断开连接时,结束推送
                        emitter.completeWithError(e);
                    }
                });
                // 脚本执行完成,关闭Emitter
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });

        return emitter;
    }
}

2. 为什么这个方案能生效?

  • SseEmitter会保持HTTP连接打开,每次调用send()都会立刻把数据推送给客户端,不需要等待所有内容生成。
  • 异步线程池确保SSH的阻塞调用不会占用Spring的请求线程,避免影响其他接口的响应。
  • 自动处理SSE的格式规范,客户端可以直接用标准的EventSource API接收数据。

备选方案:修复StreamingResponseBody的实现

如果你坚持使用StreamingResponseBody,需要手动刷新输出流,确保数据实时发送:

@RequestMapping(value = "/stream-this", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<StreamingResponseBody> streamScript() {
    StreamingResponseBody stream = out -> {
        try (OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
             BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
            sshManager.commandWithContinousRead("bash /scripts/stream-this.sh", echo -> {
                try {
                    bufferedWriter.write(echo);
                    bufferedWriter.newLine();
                    bufferedWriter.flush(); // 关键:每次写入后强制刷新,立刻发送数据
                    System.out.println(echo);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    };

    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.CACHE_CONTROL, "no-cache");
    headers.add(HttpHeaders.CONNECTION, "keep-alive");
    return new ResponseEntity<>(stream, headers, HttpStatus.OK);
}

这个方案是普通的文本流,没有SSE的事件格式,客户端需要自己处理换行分隔的内容。

如何验证流式传输是否正常?

1. Postman测试

Postman完全支持SSE:

  • 发送GET请求到http://localhost:8080/stream-this
  • 切换到Events标签页(默认会自动识别SSE响应)
  • 你会看到每4秒刷新一条新的事件内容,而不是等待8秒一次性显示全部。

2. curl测试(最直接)

在终端执行:

curl -N http://localhost:8080/stream-this

-N参数禁用curl的缓冲,你会实时看到每4秒输出一行内容。

3. 前端页面测试

写一个简单的HTML页面,用标准的EventSource接收SSE:

<!DOCTYPE html>
<html>
<body>
<div id="output"></div>
<script>
const source = new EventSource('http://localhost:8080/stream-this');
source.onmessage = function(event) {
  const output = document.getElementById('output');
  output.innerHTML += event.data + '<br>';
};
source.onerror = function(error) {
  console.error('SSE连接出错:', error);
  source.close();
};
</script>
</body>
</html>

打开这个页面,就能看到内容实时刷新显示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 19:09:05