SSE 代理模式
业务后端作为 SSE 代理的设计:透传 AI 服务的流式响应、禁用缓冲、客户端断开检测、超时控制。
#type / concept
#status / growing
#tech / dev / backend
[!info] related notes
- 前置: SSE
- 设计模式: 透传模式
- 完整链路: 多服务 SSE 管道
- 前端消费: 前端 SSE 消费
SSE 代理模式
核心问题
前端不直连 AI 服务(安全、认证、上下文注入),需要业务后端做 SSE 代理。但代理 SSE 和代理普通 HTTP 不同——不能缓冲,必须逐行实时转发。
设计要点
1. 禁用缓冲
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
c.Header("X-Accel-Buffering", "no") // 禁用 nginx 缓冲
nginx 默认会缓冲响应。SSE 需要实时推送,必须禁用。
2. 逐行转发
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
line := scanner.Text()
fmt.Fprintf(c.Writer, "%s\n", line)
c.Writer.Flush() // 每行都要 flush
}
3. 客户端断开检测
if c.Request.Context().Err() != nil {
break // 客户端断开,停止读取 AI 服务
}
4. 超时控制
client := &http.Client{Timeout: 5 * time.Minute}
resp, err := client.Do(req)
错误处理
resp, err := client.Do(req)
if err != nil {
c.JSON(502, gin.H{"error": "AI service unavailable"})
return
}
if resp.StatusCode != 200 {
body, _ := io.ReadAll(resp.Body)
c.JSON(resp.StatusCode, gin.H{"error": string(body)})
return
}
常见错误
缓冲导致前端看不到实时输出
// ❌ 读完所有数据再返回
body, _ := io.ReadAll(resp.Body)
c.Data(200, "text/event-stream", body)
// ✅ 逐行转发 + flush
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
fmt.Fprintf(c.Writer, "%s\n", scanner.Text())
c.Writer.Flush()
}
忘记处理客户端断开
不检测 c.Request.Context().Err() 会导致:前端关闭连接后,Go 继续读 AI 服务的响应,浪费资源。