flipped-aurora/gin-vue-admin · error
读取响应失败
Error message
读取响应失败: %w
What it means
fetchPublicUpstream reads the entire upstream HTTP response body with io.ReadAll; when that read fails (connection reset mid-body, timeouts, aborted streams) it wraps the underlying error with the message '读取响应失败'. It indicates the response started but could not be fully received. The wrapped error carries the actual cause.
Solutions
- Inspect the wrapped error: if it is 'context deadline exceeded', increase requestTimeout() or speed up the upstream endpoint
- Verify network connectivity between the MCP process and upstreamBaseURL (curl the same URL)
- Check upstream server logs for crashes or connection resets during the request
- Add idempotent retry logic in the MCP tool layer for GET requests
Example fix
// before
rawBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %w", err)
}
// after
rawBody, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20))
if err != nil {
return nil, fmt.Errorf("读取响应失败: %w", err)
} Defensive patterns
Strategy: retry
Validate before calling
func upstreamReachable(ctx context.Context, base string) error {
c, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(c, http.MethodGet, base+"/health", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
} Try / catch
result, err := fetchPublicUpstream[T](ctx, endpoint)
if err != nil {
if strings.Contains(err.Error(), "读取响应失败") {
// retry once with backoff; surface friendly message otherwise
}
return err
} Prevention
- Use io.LimitReader to bound body size
- Set a realistic requestTimeout for slow endpoints
- Monitor upstream server stability and restarts
- Retry idempotent GET calls on transient read failures
When it happens
Trigger: The upstream GVA server closes the connection while the body is being read; network interruption mid-transfer; resp.Body read deadline exceeded due to the request timeout context firing during body read.
Common situations: Upstream server restarting under load, flaky internal network/DNS between MCP process and GVA main service, proxy terminating long responses, requestTimeout() too short for slow endpoints.
Related errors
AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31).
Data as JSON: /api/errors/3b37a494342c0c52.
Report an issue: GitHub.
Appendix: source
Thrown at server/mcp/http_client.go:126
func fetchPublicUpstream[T any](path string) (*upstreamEnvelope[T], error) {
timeoutCtx, cancel := context.WithTimeout(context.Background(), defaultUpstreamTimeout)
defer cancel()
req, err := http.NewRequestWithContext(timeoutCtx, http.MethodGet, upstreamURL(path), nil)
if err != nil {
return nil, fmt.Errorf("构建请求失败: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("请求上游接口失败: %w", err)
}
defer resp.Body.Close()
rawBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败: %w", err)
}
if resp.StatusCode >= http.StatusBadRequest {
return nil, fmt.Errorf("上游接口返回状态码 %d: %s", resp.StatusCode, string(rawBody))
}
var result upstreamEnvelope[T]
if err := json.Unmarshal(rawBody, &result); err != nil {
return nil, fmt.Errorf("解析响应失败: %w", err)
}
if result.Code != 0 {
return nil, fmt.Errorf("上游接口业务错误: %s", result.Msg)
}
return &result, nil
}
func getUpstream[T any](ctx context.Context, endpoint string, query url.Values) (*upstreamEnvelope[T], error) {
return doUpstream[T](ctx, http.MethodGet, endpoint, query, nil)
}View on GitHub (pinned to 3136500ef3)