{"record":{"id":"0458f17619cf117d","repo":"flipped-aurora/gin-vue-admin","slug":"w-0458f1","errorCode":null,"errorMessage":"读取上游非流式响应失败: %w","messagePattern":"读取上游非流式响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/api/v1/system/sys_auto_code_sse.go","lineNumber":73,"sourceCode":"\t}\n\tdefer res.Body.Close()\n\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\tbody, readErr := io.ReadAll(res.Body)\n\t\tif readErr != nil {\n\t\t\treturn fmt.Errorf(\"上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w\", res.StatusCode, res.Header.Get(\"Content-Type\"), readErr)\n\t\t}\n\t\treturn fmt.Errorf(\"上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s\", res.StatusCode, res.Header.Get(\"Content-Type\"), previewResponseBody(body))\n\t}\n\n\tct := res.Header.Get(\"Content-Type\")\n\tlogger.WithCtx(c.Request.Context()).Mod(\"biz\").Field(\"status\", res.StatusCode).Field(\"content-type\", ct).Info(\"LLMAutoSSE 上游返回成功，开始 SSE 流式转发\")\n\n\t// 如果上游返回的不是 SSE 流（可能是 blocking 模式返回的 JSON），直接读取并转发\n\tif !strings.Contains(ct, \"text/event-stream\") && !strings.Contains(ct, \"text/plain\") {\n\t\tbody, readErr := io.ReadAll(res.Body)\n\t\tif readErr != nil {\n\t\t\treturn fmt.Errorf(\"读取上游非流式响应失败: %w\", readErr)\n\t\t}\n\t\tlogger.WithCtx(c.Request.Context()).Mod(\"biz\").Field(\"body_preview\", previewResponseBody(body)).Warn(\"LLMAutoSSE 上游返回非 SSE 流，Content-Type: \"+ct+\", 将以单次事件转发\")\n\n\t\tflusher, ok := c.Writer.(http.Flusher)\n\t\tif !ok {\n\t\t\treturn errors.New(\"当前响应不支持流式输出\")\n\t\t}\n\t\tprepareSSEHeaders(c)\n\t\tc.Status(http.StatusOK)\n\n\t\tvar payload any\n\t\tif err := json.Unmarshal(body, &payload); err != nil {\n\t\t\tpayload = string(body)\n\t\t}\n\t\tif err := renderSSE(c, sse.Event{Event: \"message\", Data: payload}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := renderSSE(c, sse.Event{Event: \"done\", Data: gin.H{\"done\": true}}); err != nil {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/api/v1/system/sys_auto_code_sse.go#L55-L91","documentation":"If the upstream response Content-Type is neither text/event-stream nor text/plain, the handler switches to blocking mode and reads the entire body with io.ReadAll. This error wraps a failure of that full-body read (connection reset, truncated response, timeout mid-read).","triggerScenarios":"Upstream returns 2xx with a non-streaming Content-Type (e.g. application/json in blocking mode) and io.ReadAll(res.Body) fails because the connection is closed or reset before the body completes.","commonSituations":"Upstream closes the connection mid-response; network interruption between proxy and LLM service; upstream returns JSON but dies before finishing the write.","solutions":["Retry the request; transient connection resets are the usual cause","Check upstream service logs/health for premature connection closure","Verify no proxy/load-balancer between services is cutting long responses (timeouts, buffer limits)","Consider setting a client timeout large enough for blocking mode responses"],"exampleFix":"// before\nbody, readErr := io.ReadAll(res.Body)\nif readErr != nil { return fmt.Errorf(\"读取上游非流式响应失败: %w\", readErr) }\n// after\nbody, readErr := io.ReadAll(io.LimitReader(res.Body, maxBodySize))\nif readErr != nil {\n    return fmt.Errorf(\"读取上游非流式响应失败: %w\", readErr) // inspect wrapped net/http errors for reset/timeout\n}","handlingStrategy":"retry","validationCode":"ct := res.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"text/event-stream\") && !strings.Contains(ct, \"text/plain\") {\n    // expect blocking JSON; read with limit and timeout\n}","typeGuard":null,"tryCatchPattern":"body, err := io.ReadAll(res.Body)\nif err != nil {\n    var ne net.Error\n    if errors.As(err, &ne) && ne.Timeout() { /* retry with backoff */ }\n    return err\n}","preventionTips":["Set http.Client timeouts longer than the longest blocking response","Use io.LimitReader to bound body size","Monitor for connection resets between proxy and upstream"],"tags":["http","sse","io","upstream"],"backgroundTag":"response-body-read-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}