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

Spring中无法维持SSE连接开放的问题求助

问题分析与解决方案

你的SSE连接失败主要是因为缺少响应头配置、未处理Emitter的生命周期回调,以及没有提供消息发送的测试入口,导致连接无法正常维持。以下是具体修复步骤和代码调整:


1. 修复后端SSE控制器

需要为SSE连接设置正确的响应头,并处理Emitter的超时、错误、完成事件,避免无效连接堆积:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@RestController
public class SSETestController {
    private final List<SseEmitter> sseEmitters = new ArrayList<>();

    @GetMapping("/message")
    public SseEmitter eventEmitter() {
        // 设置30分钟超时,避免连接被过早关闭
        SseEmitter sseEmitter = new SseEmitter(TimeUnit.MINUTES.toMillis(30));
        
        // 必须设置SSE专属响应头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.TEXT_EVENT_STREAM);
        sseEmitter.setHeaders(headers);

        // 连接完成时移除无效Emitter
        sseEmitter.onCompletion(() -> sseEmitters.remove(sseEmitter));
        // 超时处理
        sseEmitter.onTimeout(() -> {
            sseEmitters.remove(sseEmitter);
            try {
                sseEmitter.send(SseEmitter.event().name("timeout").data("连接超时"));
                sseEmitter.complete();
            } catch (IOException e) {
                sseEmitter.completeWithError(e);
            }
        });
        // 错误处理
        sseEmitter.onError((e) -> {
            sseEmitters.remove(sseEmitter);
            sseEmitter.completeWithError(e);
        });

        sseEmitters.add(sseEmitter);
        return sseEmitter;
    }

    // 添加测试消息发送接口
    @PostMapping("/send-message")
    public String sendMessage(String content) {
        // 遍历副本避免并发修改异常
        for (SseEmitter emitter : new ArrayList<>(sseEmitters)) {
            try {
                // 发送自定义事件名的消息,和前端监听对应
                emitter.send(SseEmitter.event().name("message").data(content));
                // 若需发送JSON:emitter.send(SseEmitter.event().name("message").data("{\"content\":\"" + content + "\"}", MediaType.APPLICATION_JSON));
            } catch (IOException e) {
                sseEmitters.remove(emitter);
            }
        }
        return "消息已发送";
    }
}

2. 调整前端代码

优化事件监听逻辑,添加测试消息发送按钮,同时处理可能的解析错误:

<!DOCTYPE html>
<html>
<body>

<h1>SSE实时消息测试</h1>
<p>控制台将打印收到的消息:</p>
<button onclick="sendTestMessage()">发送测试消息</button>

<script>
    window.onload = function() {
        const eventSource = new EventSource("/message");
        
        // 监听后端发送的"message"事件
        eventSource.addEventListener("message", (event) => {
            console.log("收到消息:", event.data);
            // 若后端发送JSON,可启用以下解析逻辑
            // try {
            //     const message = JSON.parse(event.data);
            //     console.log("解析后的消息:", message);
            // } catch (e) {
            //     console.log("非JSON消息:", event.data);
            // }
        });

        // 监听超时事件
        eventSource.addEventListener("timeout", (event) => {
            console.log("连接超时:", event.data);
        });

        eventSource.onerror = function (e) {
            console.error("连接出错:", e);
            // EventSource会自动重试连接,无需手动重连
        }

        eventSource.addEventListener("open", (event) => {
            console.log('SSE连接已建立');
        });
    }

    // 发送测试消息的函数
    function sendTestMessage() {
        fetch("/send-message", {
            method: "POST",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded"
            },
            body: "content=这是一条测试消息"
        }).then(res => res.text()).then(msg => console.log(msg));
    }
</script>

</body>
</html>

3. 检查额外配置

  • Spring Security拦截:如果项目启用了Spring Security,需允许SSE接口匿名访问:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/message", "/send-message").permitAll()
        .anyRequest().authenticated();
}
  • 异步超时配置:在application.properties中确保异步请求超时足够长:
spring.mvc.async.request-timeout=1800000 # 30分钟,单位毫秒

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 03:23:35