{"record":{"id":"3b37a494342c0c52","repo":"flipped-aurora/gin-vue-admin","slug":"w-3b37a4","errorCode":null,"errorMessage":"读取响应失败: %w","messagePattern":"读取响应失败: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/mcp/http_client.go","lineNumber":126,"sourceCode":"func fetchPublicUpstream[T any](path string) (*upstreamEnvelope[T], error) {\n\ttimeoutCtx, cancel := context.WithTimeout(context.Background(), defaultUpstreamTimeout)\n\tdefer cancel()\n\n\treq, err := http.NewRequestWithContext(timeoutCtx, http.MethodGet, upstreamURL(path), nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"构建请求失败: %w\", err)\n\t}\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"请求上游接口失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\trawBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\tif resp.StatusCode >= http.StatusBadRequest {\n\t\treturn nil, fmt.Errorf(\"上游接口返回状态码 %d: %s\", resp.StatusCode, string(rawBody))\n\t}\n\n\tvar result upstreamEnvelope[T]\n\tif err := json.Unmarshal(rawBody, &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\tif result.Code != 0 {\n\t\treturn nil, fmt.Errorf(\"上游接口业务错误: %s\", result.Msg)\n\t}\n\treturn &result, nil\n}\n\nfunc getUpstream[T any](ctx context.Context, endpoint string, query url.Values) (*upstreamEnvelope[T], error) {\n\treturn doUpstream[T](ctx, http.MethodGet, endpoint, query, nil)\n}","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/mcp/http_client.go#L108-L144","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nrawBody, err := io.ReadAll(resp.Body)\nif err != nil {\n\treturn nil, fmt.Errorf(\"读取响应失败: %w\", err)\n}\n// after\nrawBody, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20))\nif err != nil {\n\treturn nil, fmt.Errorf(\"读取响应失败: %w\", err)\n}","handlingStrategy":"retry","validationCode":"func upstreamReachable(ctx context.Context, base string) error {\n\tc, cancel := context.WithTimeout(ctx, 3*time.Second)\n\tdefer cancel()\n\treq, _ := http.NewRequestWithContext(c, http.MethodGet, base+\"/health\", nil)\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"result, err := fetchPublicUpstream[T](ctx, endpoint)\nif err != nil {\n\tif strings.Contains(err.Error(), \"读取响应失败\") {\n\t\t// retry once with backoff; surface friendly message otherwise\n\t}\n\treturn err\n}","preventionTips":["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"],"tags":["http-client","network","go"],"backgroundTag":"response-body-read-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}