SSE 代理模式

业务后端作为 SSE 代理的设计:透传 AI 服务的流式响应、禁用缓冲、客户端断开检测、超时控制。

#type / concept #status / growing #tech / dev / backend

[!info] related notes

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 服务的响应,浪费资源。

创建于 2026/6/25 更新于 2026/6/25