flipped-aurora/gin-vue-admin · error
向客户端写入流式响应失败: %w
Error message
向客户端写入流式响应失败: %w
What it means
proxyLLMStream copies the upstream stream to the client in 32KB chunks: it reads from res.Body and writes to c.Writer via c.Writer.Write. If any client-facing write fails, it returns fmt.Errorf("向客户端写入流式响应失败: %w", writeErr) — 'failed to write streaming response to client' — wrapping the underlying write error. This usually means the client connection is gone.
Source
Thrown at server/api/v1/system/sys_auto_code.go:201
copyLLMStreamHeaders(c.Writer.Header(), res.Header)
if c.Writer.Header().Get("Content-Type") == "" {
c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
}
if c.Writer.Header().Get("Cache-Control") == "" {
c.Writer.Header().Set("Cache-Control", "no-cache")
}
c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("X-Accel-Buffering", "no")
c.Status(res.StatusCode)
flusher.Flush()
buf := make([]byte, 32*1024)
for {
n, readErr := res.Body.Read(buf)
if n > 0 {
if _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {
return fmt.Errorf("向客户端写入流式响应失败: %w", writeErr)
}
flusher.Flush()
}
if readErr != nil {
if errors.Is(readErr, io.EOF) {
return nil
}
return fmt.Errorf("读取上游流式响应失败: %w", readErr)
}
}
}
func copyLLMStreamHeaders(dst, src http.Header) {
for _, key := range []string{
"Content-Type",
"Cache-Control",
"Content-Encoding",
"Content-Language",View on GitHub (pinned to 3136500ef3)
Solutions
- Check the wrapped %w error: context canceled / broken pipe means the client left — handle as a normal cancellation, not a server bug.
- Raise proxy timeouts (nginx proxy_read_timeout, LB idle timeout) to exceed the expected LLM generation duration.
- Ensure the frontend keeps the request alive and does not abort early; surface cancel handling in the fetch layer.
- In the handler, detect ctx.Err() (c.Request.Context().Err()) after write failure and return a sentinel so the server logs it as cancellation instead of an error.
Example fix
// before
if _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {
return fmt.Errorf("向客户端写入流式响应失败: %w", writeErr)
}
// after
if _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {
if c.Request.Context().Err() != nil {
return nil // client canceled
}
return fmt.Errorf("向客户端写入流式响应失败: %w", writeErr)
} Defensive patterns
Strategy: try-catch
Try / catch
if _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {
if errors.Is(writeErr, context.Canceled) || c.Request.Context().Err() != nil {
return nil // client canceled: not an error
}
return fmt.Errorf("向客户端写入流式响应失败: %w", writeErr)
} Prevention
- Set proxy_read_timeout / LB idle timeouts above worst-case generation time
- Handle fetch AbortController cancels cleanly in the frontend
- Check c.Request.Context().Err() before logging write failures as errors
- Keep SSE connections warm to avoid idle-timeout kills
When it happens
Trigger: While LLMAuto is proxying tokens, the browser/tab is closed, the user navigates away or cancels the fetch, a proxy/load-balancer between client and Gin times out the idle/exceeding-duration connection, or the client socket errors mid-stream.
Common situations: Long LLM generations exceed an nginx/envoy proxy_read_timeout; frontend AbortController cancels the request; corporate proxies kill long-lived SSE connections; mobile clients drop networks mid-answer.
Related errors
- LLM stream request timed out
- 上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w
- 读取上游流式响应失败: %w
- 上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w
- 读取上游流式响应失败: %w
AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31).
Data as JSON: /api/errors/ad6f56a6476bb784.
Report an issue: GitHub.