flipped-aurora/gin-vue-admin · error

当前响应不支持流式输出

Error message

当前响应不支持流式输出

What it means

proxyLLMStream relays the upstream LLM SSE stream to the gin response. gin's ResponseWriter only supports Flush() when it implements http.Flusher; on some writers (e.g. wrapped/hijacked writers, or when a middleware replaced the writer) the assertion fails and the proxy aborts with this error.

Solutions

  1. Audit middleware for custom ResponseWriter wrappers and ensure they embed http.Flusher and forward Flush()
  2. Remove/reorder middleware that replaces c.Writer on the LLM stream route
  3. Test with the vanilla gin engine route to confirm the writer supports flushing

Example fix

// before
type wrappedWriter struct { gin.ResponseWriter }
// after
type wrappedWriter struct {
  gin.ResponseWriter // already implements Flusher; do not shadow Flush
}
Defensive patterns

Strategy: validation

Validate before calling

flusher, ok := c.Writer.(http.Flusher)
if !ok {
  // fail fast with a clear middleware hint
  return fmt.Errorf("当前响应不支持流式输出: middleware %T replaced the writer", c.Writer)
}

Try / catch

if err := proxyLLMStream(c, upstream); err != nil {
  if strings.Contains(err.Error(), "不支持流式输出") {
    logger.Error("ResponseWriter cannot flush; check middleware wrapping c.Writer")
  }
  respondSSEError(c, err)
}

Prevention

When it happens

Trigger: c.Writer.(http.Flusher) type assertion fails while proxying a successful (2xx) upstream LLM stream response.

Common situations: A middleware wraps the gin Context.Writer with a custom ResponseWriter that doesn't embed http.Flusher; running behind a setup that hijacks or wraps the connection; unusual test harness writers.

Related errors


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

Appendix: source

Thrown at server/api/v1/system/sys_auto_code.go:181

func (autoApi *AutoCodeApi) proxyLLMStream(c *gin.Context, llm common.JSONMap) error {
	res, err := autoCodeService.LLMAutoStream(c.Request.Context(), llm)
	if err != nil {
		return err
	}
	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))
	}

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

	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 {

View on GitHub (pinned to 3136500ef3)