{"record":{"id":"b589bd89da2342f0","repo":"siyuan-note/siyuan","slug":"read-body-failed","errorCode":null,"errorMessage":"read body failed: ","messagePattern":"read body failed: ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/httprequest.go","lineNumber":356,"sourceCode":"\t\treturn 0, \"\", \"\", errors.New(\"nil response\")\n\t}\n\tdefer resp.Body.Close()\n\n\tstatusCode = resp.StatusCode\n\tcontentType = resp.Header.Get(\"Content-Type\")\n\n\tmaxReadBytes := int64(maxHTTPRequestBytes)\n\tif !isTextContentType(contentType) {\n\t\tmaxReadBytes = maxHTTPRequestFileBytes\n\t}\n\t// ContentLength 为 -1（chunked）时跳过大小预检，交由 LimitReader 兜底截断。\n\tif resp.ContentLength > maxReadBytes {\n\t\treturn statusCode, contentType, \"\", errors.New(\"response too large\")\n\t}\n\n\trespBody, rerr := io.ReadAll(io.LimitReader(resp.Body, maxReadBytes))\n\tif rerr != nil {\n\t\treturn statusCode, contentType, \"\", errors.New(\"read body failed: \" + rerr.Error())\n\t}\n\n\t// 二进制响应落盘，返回文件路径，供智能体按需进一步处理。\n\tif !isTextContentType(contentType) {\n\t\timportDir := filepath.Join(TempDir, \"import\")\n\t\tif merr := os.MkdirAll(importDir, 0755); merr != nil {\n\t\t\treturn statusCode, contentType, \"\", errors.New(\"create import dir failed: \" + merr.Error())\n\t\t}\n\t\tfilename := extractFilename(rawURL, contentType)\n\t\tfilePath := filepath.Join(importDir, filename)\n\t\tif werr := os.WriteFile(filePath, respBody, 0644); werr != nil {\n\t\t\treturn statusCode, contentType, \"\", errors.New(\"write file failed: \" + werr.Error())\n\t\t}\n\t\treturn statusCode, contentType, fmt.Sprintf(\"Saved to: %s (%d bytes)\", filePath, len(respBody)), nil\n\t}\n\n\treturn statusCode, contentType, truncateRunes(string(respBody), maxHTTPRequestChars), nil\n}","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/httprequest.go#L338-L374","documentation":"HTTPRequest reads the response body with io.ReadAll(io.LimitReader(resp.Body, maxReadBytes)). If that read fails — typically a network interruption mid-body, a TLS/connection reset, or the read hitting the 30-second client timeout — the error is wrapped as \"read body failed: <cause>\". This happens after a successful response header, so statusCode and contentType may already be set.","triggerScenarios":"HTTPRequest() where the connection drops or times out while streaming the response body, or the server closes the connection prematurely (chunked encoding aborted), or a proxy tunnel is severed mid-transfer.","commonSituations":"Flaky Wi-Fi/mobile networks, proxies killing long transfers, servers with keep-alive races, or responses taking longer than the 30-second ssrfSafeClient timeout on slow links.","solutions":["Retry the request — transient network failures are the most common cause","Check the wrapped cause after \"read body failed: \" (context deadline exceeded, connection reset, unexpected EOF) and address it specifically","Reduce response size (pagination) so the body transfer completes within the 30-second client timeout","Verify proxy stability if HTTP_PROXY/SOCKS5 proxies are configured, since tunnel drops surface here","If it consistently fails for one host, test the URL with curl to determine whether the server itself aborts transfers"],"exampleFix":"// before: single attempt\nstatus, ct, text, err := util.HTTPRequest(\"GET\", url, nil, \"\")\n// after: retry transient failures\nvar status int; var ct, text string; var err error\nfor i := 0; i < 3; i++ {\n    status, ct, text, err = util.HTTPRequest(\"GET\", url, nil, \"\")\n    if err == nil || !strings.Contains(err.Error(), \"read body failed\") { break }\n    time.Sleep(time.Second)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"var status int; var ct, text string; var err error\nfor attempt := 0; attempt < 3; attempt++ {\n    status, ct, text, err = util.HTTPRequest(\"GET\", url, nil, \"\")\n    if err == nil || !strings.Contains(err.Error(), \"read body failed\") {\n        break\n    }\n    time.Sleep(time.Duration(attempt+1) * time.Second)\n}","preventionTips":["Retry transient body-read failures with exponential backoff","Keep responses small (pagination) so transfers complete well within the 30-second client timeout","Check proxy stability if HTTP_PROXY/HTTPS_PROXY/SOCKS5 is configured — tunnel drops surface as body read errors","Parse the wrapped cause (context deadline exceeded, connection reset, unexpected EOF) to choose between retry and config changes"],"tags":["http","network","io","timeout","go"],"backgroundTag":"http-request-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}