必须手动设置响应头、禁用缓存、显式刷新缓冲区,否则客户端收不到任何事件:因Iris默认缓冲输出,不调Flush()数据滞留内存;Content-Type和Cache-Control须在首次写入前设置;WriteString()/JSON()会自动关闭连接,破坏SSE协议。

必须手动设置响应头、禁用缓存、显式刷新缓冲区,否则客户端收不到任何事件
为什么 ctx.ResponseWriter().WriteString() 不生效
SSE 要求服务端持续写入、客户端按行解析,而 Iris 默认的响应机制会缓冲输出直到请求结束。不显式调用 Flush(),数据就卡在内存里发不出去。
-
ctx.Response().Header().Set("Content-Type", "text/event-stream")必须在第一次写入前设置,否则浏览器不识别为 SSE 流 -
ctx.Response().Header().Set("Cache-Control", "no-cache")防止中间代理或浏览器缓存断连后重放旧事件 - 每次写完一个
data: ...块后,必须紧跟ctx.Response().Flush(),不能只在循环外调一次 - 若使用
ctx.WriteString()或ctx.JSON(),它们内部会自动加Content-Length并关闭连接,彻底破坏 SSE 协议
如何避免连接被意外中断
常见错误是 ticker 循环中没检查客户端是否还在线,导致 WriteString() 返回 io.ErrClosedPipe 或 net/http.ErrHandlerTimeout 后继续执行,最终 panic 或阻塞。
- 每次
ctx.ResponseWriter().WriteString(msg)后必须检查err != nil,并直接return退出 handler - 不要用
time.Sleep()替代time.Ticker:Sleep 无法响应客户端断连信号,Ticker 的 channel 在连接关闭时会自动停发 - 若需携带 ID 或事件类型,格式必须严格:
id: 123\nevent: ping\ndata: ...\n\n,少一个换行或冒号都会让浏览器丢弃整条消息 - 客户端 JavaScript 中建议监听
onerror并主动重连,Iris 层不做重连管理
与普通 HTTP handler 的关键差异
SSE handler 本质是一个长期存活的连接,生命周期远超常规接口,对资源管理和错误处理要求更高。
- 不能在 handler 内启 goroutine 处理业务逻辑后立即返回——这会让主协程退出,连接被框架回收
- 所有状态变量(如计数器、用户 session)必须是并发安全的,多个事件可能在同一个 handler 内并发写入
- 日志打印要节制,高频写日志(尤其带锁的)会拖慢 flush 速度,导致事件堆积或超时断连
- 若搭配 JWT 鉴权,token 解析和校验必须在 handler 开头完成,不能放到 ticker 循环里重复执行
最易被忽略的是:SSE 连接不会因路由匹配失败而返回 404,而是直接挂起或报错 500;调试时务必先确认 handler 是否真正进入执行,再查流内容格式。


















