必须手动调用ctx.Response().Flush(),因为Fiber默认缓冲响应,即使设了text/event-stream并持续SendString,数据也只在handler结束时才发出;每条SSE消息后必须flush才能实时推送,否则前端EventSource一直pending。

为什么 ctx.Response().Flush() 必须手动调用
Fiber 默认禁用响应流式写入,即使你写了 ctx.Set("Content-Type", "text/event-stream") 并持续 ctx.SendString(),数据也会被缓冲在内存里,直到 handler 返回才一次性发出。这会导致前端收不到任何事件——因为连接没断、但也没发数据。
关键动作是每次写完一个事件后立即调用 ctx.Response().Flush(),它强制把当前缓冲区内容推送到客户端。注意:不能只在 handler 结束前 flush 一次,必须每条事件后都 flush。
-
ctx.Response().Flush()不会关闭连接,只是清空当前响应缓冲区 - 若忘记 flush,浏览器 EventSource 会一直 pending,直到超时或服务端超时关闭连接
- Fiber 的
ctx.Response()底层基于 fasthttp,不支持WriteHeaderNow()这类高级流控,flush 是唯一可靠方式
如何正确构造 SSE 格式并避免连接中断
SSE 协议对格式敏感:每条消息以换行分隔,字段必须是 data:、event:、id: 或 retry: 开头,末尾需有两个换行(\n\n)。少一个 \n,浏览器就解析失败;多写空格或错用冒号位置,整个流会被静默丢弃。
常见错误是用 fmt.Fprintf(ctx.Response().BodyWriter(), "data: %s\n\n", msg) —— 这不对,因为 BodyWriter() 在某些中间件(如压缩)下可能被封装,直接写会绕过 Fiber 的响应生命周期,导致 headers 写入失败或 panic。
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 始终用
ctx.SendString("data: hello\n\n")+ctx.Response().Flush()组合 - 每条消息末尾必须是
\n\n,不是\r\n\r\n,也不是单个\n - 设置
ctx.Set("Cache-Control", "no-cache")和ctx.Set("Connection", "keep-alive")防止代理缓存或提前断连 - 避免在 handler 中执行耗时同步操作(如数据库查询),否则会阻塞 flush,造成延迟或超时
怎么安全地管理长连接生命周期和客户端断开
Fiber 不提供原生的连接存活检测,ctx.Context().Done() 信号不可靠(fasthttp 的 context 不随 TCP 断开自动 cancel),所以不能只依赖 select { case 来退出循环。
真实场景中,客户端关页、网络中断、Nginx 代理超时都会让连接无声消失。你需要主动探测:要么定时写入空注释(:keepalive\n\n),要么用 ctx.Response().ConnectionState() == http.StateClosed —— 但 fasthttp 没暴露这个状态。更可行的是捕获底层 write error。
- 每次
ctx.SendString()后检查返回 error,write: broken pipe或use of closed network connection表示客户端已断开 - 用
time.AfterFunc()设置心跳(如每 15s 发一条:ping\n\n注释),避免代理超时切断 - 不要用全局 map 存连接,而应在 handler 内部用
sync.WaitGroup或context.WithTimeout()控制 goroutine 生命周期 - 如果推送逻辑依赖外部状态(如 channel 接收消息),确保 channel 是带缓冲的,否则 sender 可能因 receiver 慢而阻塞
为什么不能用 ctx.Next() 或中间件包装 SSE handler
所有中间件(包括 logger、recovery、cors)默认会在 handler 返回后才写响应体。如果你把 SSE handler 套进 app.Get("/stream", middleware, sseHandler),中间件会等 sseHandler 返回才开始处理响应——但 SSE handler 是永不返回的(它要一直跑着发事件),结果就是中间件永远卡住,headers 无法写出,连接直接失败。
同理,ctx.Next() 在 SSE handler 内调用毫无意义:它只是跳转到下一个 handler,而你根本没有下一个 handler,且流式响应不允许“中途交出控制权”。
- SSE endpoint 必须注册为最外层路由,不经过任何修改响应体或 header 的中间件
- CORS 需手动设置:
ctx.Set("Access-Control-Allow-Origin", "*"),不能依赖cors.New() - 日志要自己打(比如记录连接建立/断开时间),别指望
logger.New()能捕获流式请求的完整生命周期 - 若需鉴权,必须在 handler 开头完成(如解析 token、查 DB),不能放到中间件里异步做
真正难的不是写几行 send + flush,而是处理连接闪断、反向代理兼容、资源泄漏和并发推送时的 channel 阻塞。这些细节不爆错,但会让推送在压测或上线后间歇性失联。

















