flipped-aurora/gin-vue-admin · error

读取上游非流式响应失败: %w

Error message

读取上游非流式响应失败: %w

What it means

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).

Source

Thrown at server/api/v1/system/sys_auto_code_sse.go:73

	}
	defer res.Body.Close()

	if res.StatusCode < 200 || res.StatusCode >= 300 {
		body, readErr := io.ReadAll(res.Body)
		if readErr != nil {
			return fmt.Errorf("上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w", res.StatusCode, res.Header.Get("Content-Type"), readErr)
		}
		return fmt.Errorf("上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s", res.StatusCode, res.Header.Get("Content-Type"), previewResponseBody(body))
	}

	ct := res.Header.Get("Content-Type")
	logger.WithCtx(c.Request.Context()).Mod("biz").Field("status", res.StatusCode).Field("content-type", ct).Info("LLMAutoSSE 上游返回成功,开始 SSE 流式转发")

	// 如果上游返回的不是 SSE 流(可能是 blocking 模式返回的 JSON),直接读取并转发
	if !strings.Contains(ct, "text/event-stream") && !strings.Contains(ct, "text/plain") {
		body, readErr := io.ReadAll(res.Body)
		if readErr != nil {
			return fmt.Errorf("读取上游非流式响应失败: %w", readErr)
		}
		logger.WithCtx(c.Request.Context()).Mod("biz").Field("body_preview", previewResponseBody(body)).Warn("LLMAutoSSE 上游返回非 SSE 流,Content-Type: "+ct+", 将以单次事件转发")

		flusher, ok := c.Writer.(http.Flusher)
		if !ok {
			return errors.New("当前响应不支持流式输出")
		}
		prepareSSEHeaders(c)
		c.Status(http.StatusOK)

		var payload any
		if err := json.Unmarshal(body, &payload); err != nil {
			payload = string(body)
		}
		if err := renderSSE(c, sse.Event{Event: "message", Data: payload}); err != nil {
			return err
		}
		if err := renderSSE(c, sse.Event{Event: "done", Data: gin.H{"done": true}}); err != nil {

View on GitHub (pinned to 3136500ef3)

Solutions

  1. Retry the request; transient connection resets are the usual cause
  2. Check upstream service logs/health for premature connection closure
  3. Verify no proxy/load-balancer between services is cutting long responses (timeouts, buffer limits)
  4. Consider setting a client timeout large enough for blocking mode responses

Example fix

// before
body, readErr := io.ReadAll(res.Body)
if readErr != nil { return fmt.Errorf("读取上游非流式响应失败: %w", readErr) }
// after
body, readErr := io.ReadAll(io.LimitReader(res.Body, maxBodySize))
if readErr != nil {
    return fmt.Errorf("读取上游非流式响应失败: %w", readErr) // inspect wrapped net/http errors for reset/timeout
}
Defensive patterns

Strategy: retry

Validate before calling

ct := res.Header.Get("Content-Type")
if !strings.Contains(ct, "text/event-stream") && !strings.Contains(ct, "text/plain") {
    // expect blocking JSON; read with limit and timeout
}

Try / catch

body, err := io.ReadAll(res.Body)
if err != nil {
    var ne net.Error
    if errors.As(err, &ne) && ne.Timeout() { /* retry with backoff */ }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: Upstream closes the connection mid-response; network interruption between proxy and LLM service; upstream returns JSON but dies before finishing the write.

Related errors


AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31). Data as JSON: /api/errors/0458f17619cf117d. Report an issue: GitHub.