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()) }
修复说明
- 遍历所有响应头字段,将每个键值对转换为符合HTTP规范的字符串
- 用换行符分隔各个头字段
- 保持状态行、响应头、空行、响应体的标准HTTP响应结构
修改后,浏览器能正确识别Content-Type: text/event-stream头,进而触发SSE的ping事件,客户端脚本即可正常工作。
内容的提问来源于stack exchange,提问作者cooljacwty
相关产品推荐
相关产品推荐

