✍️ 适合人群:

  • 写过 REST API,但用户总问“好了没?”的你
  • 想告别“轮询 polling”,拥抱“推送 push”的你
  • 正在搞 AI 服务、视频转码、批量导入等长耗时任务的你

🤔 为什么选 SSE?而不是 WebSocket 或轮询?

方案 特点 适用场景
轮询(Polling) 前端每 2s 问一次“好了没?” → 浪费带宽 + 延迟高 简单、低频任务(如每分钟刷新状态)
WebSocket 双向通信,强大但重 聊天室、实时协作、游戏
SSE 服务端单向推流 + 自动重连 + 文本协议 + 原生浏览器支持 ✅ 任务进度、通知、日志流、AI 响应流

💡 关键优势:

  • 前端用 EventSource 一句代码接入
  • 自动重连(网络抖动不怕)
  • HTTP 协议,CDN/代理友好
  • 服务端就是普通 HTTP handler —— Go 写起来超简单!

🧪 小实战:AI 文本摘要服务(模拟)

🎯 需求:用户提交一段长文本,后端“调用 AI”处理(模拟耗时 5s),期间推送:

  1. {"status":"processing", "progress":20, "message":"正在理解语义..."}
  2. {"status":"processing", "progress":60, "message":"生成摘要中..."}
  3. {"status":"done", "result":"这是一段精炼的摘要。"}

✅ 1. 定义事件消息结构

// event.go
type SSEMessage struct {
	Status   string `json:"status"`   // "processing" / "done" / "error"
	Progress int    `json:"progress"` // 0-100
	Message  string `json:"message"`  // 人类可读提示
	Result   string `json:"result,omitempty"` // 最终结果(仅 done 时有)
}

📝 用 JSON 统一格式,方便前端统一处理 👍


✅ 2. SSE Handler 核心写法 —— 关键 4 步

// sse_handler.go
func AIStreamHandler(w http.ResponseWriter, r *http.Request) {
	// Step 1️⃣: 设置响应头 → 告诉浏览器:这是 SSE!
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("Access-Control-Allow-Origin", "*") // 可选:允许跨域

	// Step 2️⃣: 强制 flush,让 header 立刻发出去(否则浏览器等 body)
	if f, ok := w.(http.Flusher); ok {
		f.Flush()
	}

	// Step 3️⃣: 模拟 AI 处理流程(用 goroutine + channel 控制节奏)
	ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
	defer cancel()

	// 用一个 channel 模拟“AI 步骤事件流”
	events := make(chan SSEMessage, 10)
	go simulateAIWork(events)

	// Step 4️⃣: 主循环:监听事件 → 推送给前端
	for {
		select {
		case msg := <-events:
			data, _ := json.Marshal(msg)
			// 格式:`data: {...}\n\n`
			fmt.Fprintf(w, "data: %s\n\n", data)
			if f, ok := w.(http.Flusher); ok {
				f.Flush() // ⚠️ 每次写完必须 flush!否则缓冲区卡住
			}

			// 任务结束?跳出循环(否则 keep-alive 会一直挂)
			if msg.Status == "done" || msg.Status == "error" {
				return
			}

		case <-ctx.Done():
			// 超时或客户端断开
			fmt.Fprintf(w, "data: %s\n\n", `{"status":"error","message":"Client disconnected or timeout"}`)
			return
		}
	}
}

🔑 关键细节提醒:

  • text/event-stream 是 SSE 的“身份证”
  • Flush() 是灵魂!不 flush 就像快递打包了不发货 📦
  • ✅ 每条消息以 \n\n 结尾,这是 SSE 协议要求
  • ✅ 用 context 监听客户端断开(浏览器关 tab 时自动 cancel)

✅ 3. 模拟 AI 工作流(带进度)

// simulate.go
func simulateAIWork(out chan<- SSEMessage) {
	defer close(out)

	send := func(msg SSEMessage) {
		select {
		case out <- msg: // 防 channel 关闭 panic
		default:
		}
	}

	// Step 1: 预处理
	send(SSEMessage{Status: "processing", Progress: 10, Message: "接收请求..."})
	time.Sleep(500 * time.Millisecond)

	// Step 2: 理解语义
	send(SSEMessage{Status: "processing", Progress: 30, Message: "正在理解语义结构..."})
	time.Sleep(1200 * time.Millisecond)

	// Step 3: 生成摘要
	send(SSEMessage{Status: "processing", Progress: 70, Message: "生成摘要草稿..."})
	time.Sleep(1500 * time.Millisecond)

	// Step 4: 优化润色
	send(SSEMessage{Status: "processing", Progress: 95, Message: "润色语言,提升可读性..."})
	time.Sleep(800 * time.Millisecond)

	// Step 5: 完成!
	send(SSEMessage{
		Status:  "done",
		Progress: 100,
		Message:  "✅ 摘要生成成功!",
		Result:   "Go 的 SSE 实现简洁高效,特别适合单向实时推送场景,如 AI 处理进度、日志流、通知等。",
	})
}

✅ 4. 前端代码:3 行接入 SSE!

<!-- index.html -->
<script>
const evtSource = new EventSource("/ai/summarize");

evtSource.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log("🚀 收到进度:", msg);
  
  // 比如更新页面
  document.getElementById("status").innerText = msg.message;
  document.getElementById("progress").style.width = msg.progress + "%";
  
  if (msg.status === "done") {
    document.getElementById("result").innerText = msg.result;
    evtSource.close(); // 任务完成,关连接
  }
};

evtSource.onerror = (err) => {
  console.error("❌ SSE Error", err);
  evtSource.close();
};
</script>

<div id="status">等待 AI 响应...</div>
<div style="width:100%; background:#eee; height:20px; margin:10px 0;">
  <div id="progress" style="height:100%; background:#4CAF50; width:0%"></div>
</div>
<pre id="result"></pre>

✅ 效果:进度条实时前进 + 最终显示摘要 ✅


🛠 路由 & 启动(完整可运行)

// main.go
package main

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

func main() {
	http.HandleFunc("/ai/summarize", AIStreamHandler)
	fmt.Println("🚀 Server running on http://localhost:8080")
	http.ListenAndServe(":8080", nil)
}

✅ 运行:

go run .
# 访问 http://localhost:8080/index.html

🧩 进阶技巧(来自生产经验)

问题 解决方案
客户端断开后,Go 还在发? r.Context().Done() 监听,或 w.(http.CloseNotifier)(旧版)→ 推荐 context
想区分事件类型? event: xxx 字段:
fmt.Fprintf(w, "event: progress\ndata: %s\n\n", data)
前端:evtSource.addEventListener("progress", ...)
连接数太多? 加 middleware 限流:gorilla/handlers or 自定义计数器
想支持重连 token? 客户端传 ?lastEventId=123,服务端从断点恢复
调试看 raw stream? curl -N http://localhost:8080/ai/summarize-N 关闭缓冲)

💡 小知识:SSE 默认 3 秒无数据会触发浏览器重连(可配 retry: 5000 控制)


🌌 哲思:SSE 是“耐心”的技术

今天的世界崇尚“即时满足”——
但有些事,值得等待:一杯手冲咖啡、一段深刻思考、一次 AI 的深度推理。

SSE 不是炫技,而是对用户的尊重
“我知道你在等,所以我告诉你:我正在努力。”

这比一句冷冰冰的 202 Accepted,温暖得多。


Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐