当前位置:首页 > 文章列表 > Golang > Go问答 > Go 写 SSE 接口为什么浏览器收不到消息:EventSource、Flush 和超时怎么排查

Go 写 SSE 接口为什么浏览器收不到消息:EventSource、Flush 和超时怎么排查

来源:17golang原创 2026-07-07 16:05:26 0浏览 收藏

Go 写 SSE 接口时,浏览器 EventSource 能连上但迟迟收不到消息,最常见的原因不是浏览器“不支持”,而是服务端把数据写进响应后没有及时刷出去,或者中间的 Nginx、网关、压缩层把流式响应缓冲住了。排查这类问题,先看响应头是不是 text/event-stream,再看每条消息是不是以空行结尾,最后确认 Go 代码里拿到了 http.Flusher 并在每次写入后调用 Flush()

要点速览

  • SSE 是浏览器原生的单向事件流,适合通知、进度、状态变更场景,不适合做双向实时聊天协议。
  • Go handler 写入 data: ...\n\n 后要调用 Flush(),否则浏览器可能一直处于 pending 收不到内容的状态。
  • 代理层要关闭响应缓冲,Nginx 常见检查点包括 X-Accel-Bufferingproxy_buffering
  • 生产环境还要处理客户端断开、心跳保活、超时和自动重连逻辑,不能只写一个无限循环就完事。

EventSource 能解决什么,不解决什么

SSE 的全名是 Server-Sent Events,浏览器通过 new EventSource("/events") 建立一条普通 HTTP 长连接,服务端持续往这条连接里推送文本格式的事件。它的优势是足够轻量:不用做 WebSocket 升级,不用自己处理复杂握手逻辑,浏览器原生还自带断线自动重连能力。

但它只支持服务端到浏览器的单向推送。像订单状态同步、后台任务进度展示、系统告警、日志尾部实时更新这类场景都非常适配。如果页面还需要频繁往服务端回传数据,用 WebSocket 或者普通 HTTP 接口会更顺手。

能力点 SSE 的表现 排查时看哪里
连接方式 普通 HTTP 长连接 Network 面板里对应请求是否处于 pending 状态
消息方向 仅服务端推送给浏览器 EventStream 面板是否能抓到新的 data 内容
消息格式 data: 内容\n\n 是否漏了结尾空行或者正确的 Content-Type 头
实时性 强依赖 Flush 操作和各层代理的缓冲策略 Go、Nginx、网关是否开启了小块内容缓冲

浏览器 pending,不代表服务端已经推送到了页面

很多人第一次调试 SSE,在 DevTools 里看到请求状态是 200,类型标记也显示为 eventsource,就默认服务端逻辑肯定没问题了。这时候先别急着关调试面板。SSE 请求本来就会长时间维持 pending 状态,真正需要确认的是 EventStream 标签页里有没有新的事件输出,以及请求的 Waterfall 视图里有没有持续收到新的响应片段。

Go SSE 接口写入后未 Flush 导致浏览器 EventSource pending 等待

如果 Go 服务端日志已经打印了“成功写入消息”的记录,但浏览器侧完全没收到对应事件,问题基本都落在三个地方:第一是消息格式不符合规范;第二是内容写进缓冲区之后没有手动刷出;第三是中间链路的代理组件把小块响应攒起来做批量发送了。不要只盯着前端 JavaScript 查问题,整条服务端响应链路都要同步排查。

最小 Go 示例:写完一条就刷一次

下面这个 handler 是用来排查 SSE 问题的最简版本,刻意精简了多余逻辑,方便把核心关键点拎出来看清楚。

package main

import (
    "fmt"
    "net/http"
    "time"
)

func events(w http.ResponseWriter, r *http.Request) {
    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "streaming unsupported", http.StatusInternalServerError)
        return
    }

    h := w.Header()
    h.Set("Content-Type", "text/event-stream")
    h.Set("Cache-Control", "no-cache")
    h.Set("Connection", "keep-alive")
    h.Set("X-Accel-Buffering", "no")

    ticker := time.NewTicker(2 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case 

这里有三个细节最容易被忽略。第一,Content-Type 要设置成 text/event-stream。第二,每条消息末尾必须带一个空行,也就是 \n\n。第三,每次写入内容之后都要调用 Flush(),让当前的响应片段立刻从 Go 进程送到 TCP 连接上。

Go SSE handler 调用 http.Flusher 后浏览器收到 EventSource 消息

兼容处理:代理缓冲和超时更容易藏问题

本地直连 Go 服务能正常收到消息,上线经过 Nginx 或网关之后推送就卡住不动了,这种情况非常常见。代理默认一般会为了整体吞吐量做缓冲,把零散的小块响应攒到一定大小再统一发送,浏览器端就会表现出长时间收不到内容的状态。

如果你用 Nginx 做反向代理,可以先对照这些配置思路调整:

location /events {
    proxy_pass http://go_backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_read_timeout 1h;
}

Go 侧加上 X-Accel-Buffering: no 也能起到辅助作用,但不要只靠响应头来解决缓冲问题。真实生产环境里还有负载均衡、CDN、API 网关、压缩中间件等多层组件,它们都可能修改流式响应的默认行为。排查的时候建议按“浏览器直连 Go 服务、经过 Nginx 转发、走完整网关链路”三步拆分验证,不要把所有层级混在一起盲目猜问题。

生产环境别忘了心跳和断开处理

SSE 不是随便写个死循环就能跑稳的。浏览器切换网络、用户关闭页面、代理触发空闲超时,都会悄无声息地断开连接。Go 代码里要监听 r.Context().Done() 事件,及时终止处理循环,避免无用的 goroutine 和 ticker 留在后台空转。

如果业务本身的事件推送频次很低,可以定期发一条注释型的心跳消息:

fmt.Fprint(w, ": ping\n\n")
flusher.Flush()

这行内容不会触发浏览器的 message 回调,但能维持连接活跃,也能帮你快速验证整条推送链路是否还能正常传输小块内容。心跳间隔不要随便设置,最好结合代理空闲超时阈值、服务端连接承载量和页面实际业务需求共同确定。

相关问题

SSE 和 WebSocket 应该怎么选?

页面只需要接收服务端下发通知的场景,优先考虑 SSE;需要双向高频通信的场景,再考虑 WebSocket。SSE 的优势是接入成本低、浏览器自带自动重连、普通 HTTP 链路调试起来更简单。

EventSource 收到 200 但没有 message,是前端问题吗?

不一定。200 状态只能说明连接成功建立,不能代表消息已经传输到页面。要继续排查响应格式、Flush()、代理缓冲配置和 EventStream 面板的实际输出内容。

SSE 可以传 JSON 吗?

可以。把 JSON 字符串放在 data: 后面即可,前端在 onmessage 回调里用 JSON.parse(event.data) 解析。注意每条事件末尾仍然需要用空行做结束标记。

Go 里不调用 Flush 会怎样?

写入的数据可能暂时留在进程的输出缓冲区里,浏览器看起来就会一直等待。某些情况下缓冲区写满之后内容也会被自动送出,但这不是实时推送场景应该依赖的行为。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go map 并发读写为什么会 panic:从 fatal error 到三种修法Go map 并发读写为什么会 panic:从 fatal error 到三种修法
上一篇
Go map 并发读写为什么会 panic:从 fatal error 到三种修法
PHP 文件上传怎么做才安全:从 $_FILES 校验到落盘和清理
下一篇
PHP 文件上传怎么做才安全:从 $_FILES 校验到落盘和清理
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    4317次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    4001次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    3990次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    4171次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    4138次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码