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

Server-Sent Event流未触发事件问题排查求助

问题:Server-Sent Events 事件未触发,响应被解析为纯文本

我尝试用以下脚本让客户端创建事件流,并将Server-Sent Event消息打印到列表中:

const eventSrc = new EventSource("game");

// 为每个事件消息创建列表项
eventSrc.addEventListener( "ping", (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");

newElement.textContent = `message: ${event.data}`;
eventList.appendChild(newElement);
            
eventSrc.close();
});

发送请求后,没有触发任何事件。客户端确实收到了数据,但响应被解析为纯文本。服务器关闭连接后,调试面板显示数据以纯文本形式返回。

我原本以为响应头设置正确,curl返回的结果如下:

HTTP/1.1 200
{"content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache"}

已确认服务器每次发送消息后都会刷新输出,服务器Rust代码如下:

match req.headers().get("accept"){
    Some(v) => {
        if v == "text/event-stream" {
            let message = "event: ping\ndata: connection established!\n\n";
            let res = Response::builder()
                .status(StatusCode::OK)
                .header("Content-Type", "text/event-stream; charset=utf-8")
                .header("Cache-Control", "no-cache")
                .body(message.to_string()).unwrap();

            send_response(&mut stream, res).unwrap(); // 写入初始响应头并刷新TCPStream
            for _ in 1..10 {
                thread::sleep(time::Duration::from_secs(1));
                stream.write_all(message.as_bytes()).unwrap();
                match stream.flush() { Ok(_) => {println!("sent!")}, Err(_) => println!("error!") }; // 将消息写入TCPStream
            }
        }
            else {
            send_response(&mut stream, get_page(req.uri().path())).unwrap();
        }
    },
    None => {}
};

补充:stream对象是std::net crate中的TcpStream。

send_response()函数负责将请求格式化为字符串写入流:

fn send_response(stream: &mut TcpStream, res: Response<String>) -> Result<(), std::io::Error> {
    let (header, body) = res.into_parts(); 
    let response = format!("{:?} {:?}\n{:?}\r\n\r\n{body}", header.version, header.status, header.headers);
    return stream.write_all(response.as_bytes())
}

用curl测试时,能正常获取流数据,每秒接收一次消息,终端输出如下:

*   Trying 127.0.0.1:7878...
* Connected to localhost (127.0.0.1) port 7878 (#0)
> GET /game HTTP/1.1
> Host: localhost:7878
> User-Agent: curl/7.81.0
> Accept: text/event-stream
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< {"content-type": "text/event-stream; charset=utf-8", "cache-control": "no-cache"}
* no chunk, no close, no size. Assume close to signal end
< 
event: ping
data: connection established!

event: ping
data: connection established!

event: ping
data: connection established!

event: ping
data: connection established!

event: ping
data: connection established!

event: ping
data: connection established!

event: ping
data: connection established!

event: ping
data: connection established!

event: ping
data: connection established!

event: ping
data: connection established!

* Closing connection 0

问题根源与修复

问题出在send_response函数的响应头格式化逻辑上。你用{:?}格式化header.headers,这会输出Rust结构体的Debug字符串(也就是{"content-type": "..."}这种格式),但HTTP响应头要求每个字段必须是键: 值的格式,每行一个。浏览器无法解析这种错误格式的响应头,因此不会识别这是SSE流,而是当成普通纯文本处理。

修复代码

修改send_response函数,按照HTTP规范格式化响应头:

fn send_response(stream: &mut TcpStream, res: Response<String>) -> Result<(), std::io::Error> {
    let (header, body) = res.into_parts();
    // 构建状态行
    let status_line = format!("{:?} {:?}", header.version, header.status);
    // 遍历头字段,格式化为"Key: Value"
    let headers = header.headers.iter()
        .map(|(name, value)| format!("{}: {}", name, value.to_str().unwrap_or("")))
        .collect::<Vec<_>>()
        .join("\n");
    // 拼接完整响应
    let response = format!("{status_line}\n{headers}\r\n\r\n{body}");
    stream.write_all(response.as_bytes())
}

修复说明

  1. 遍历所有响应头字段,将每个键值对转换为符合HTTP规范的字符串
  2. 用换行符分隔各个头字段
  3. 保持状态行、响应头、空行、响应体的标准HTTP响应结构

修改后,浏览器能正确识别Content-Type: text/event-stream头,进而触发SSE的ping事件,客户端脚本即可正常工作。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 14:35:00