{"record":{"id":"4a647dc871b0596f","repo":"siyuan-note/siyuan","slug":"request-failed","errorCode":null,"errorMessage":"request failed: ","messagePattern":"request failed: ","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/httprequest.go","lineNumber":335,"sourceCode":"\tif method == \"\" {\n\t\tmethod = \"GET\"\n\t}\n\n\tvar reqBody io.Reader\n\tif body != \"\" && method != \"GET\" && method != \"HEAD\" {\n\t\treqBody = strings.NewReader(body)\n\t}\n\treq, err := http.NewRequest(method, rawURL, reqBody)\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"invalid request: \" + err.Error())\n\t}\n\tfor k, v := range headers {\n\t\treq.Header.Set(k, v)\n\t}\n\n\tresp, err := ssrfSafeClient.Do(req)\n\tif err != nil {\n\t\treturn 0, \"\", \"\", errors.New(\"request failed: \" + err.Error())\n\t}\n\tif resp == nil {\n\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","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/httprequest.go#L317-L353","documentation":"ssrfSafeClient.Do executes the request; any transport-level failure (DNS, TCP connect, TLS, timeout, proxy failure, context cancel) is wrapped as 'request failed: <cause>'. The inner error text names the actual cause, so it should be read, not discarded.","triggerScenarios":"HTTPRequest called against a host that is unreachable, refuses connections, has bad TLS, or where the context deadline expires during Do; also failures inside the SSRF-safe transport's proxy tunnel.","commonSituations":"Target server down or wrong port; TLS certificate invalid/expired/self-signed; firewall dropping outbound traffic; request timeout too small; proxy misconfiguration surfacing here after passing the earlier checks.","solutions":["Read the wrapped cause: 'connection refused' → service/port wrong; 'no such host' → DNS; 'certificate is valid for' → TLS/SNI problem; 'context deadline exceeded' → raise timeout","Verify the service is up and reachable with `curl -v <url>` from the same machine","Fix TLS issues (valid cert) rather than disabling verification","Add retry with backoff for transient network errors and a sane timeout on the context"],"exampleFix":"// before\nHTTPRequest(\"GET\", \"https://api.example.com:9999/v1\", nil, \"\") // wrong port, connection refused\n// after\nHTTPRequest(\"GET\", \"https://api.example.com:443/v1\", nil, \"\")","handlingStrategy":"try-catch","validationCode":"if u, err := url.Parse(rawURL); err != nil || u.Host == \"\" || (u.Scheme != \"http\" && u.Scheme != \"https\") {\n    return errors.New(\"URL must be an absolute http(s) URL\")\n}","typeGuard":null,"tryCatchPattern":"status, _, _, err := HTTPRequest(\"GET\", url, nil, \"\")\nif err != nil {\n    if strings.HasPrefix(err.Error(), \"request failed: \") && isTransient(err) {\n        time.Sleep(backoff)\n        return HTTPRequest(\"GET\", url, nil, \"\") // retry once\n    }\n    return err\n}","preventionTips":["Set a realistic context timeout before issuing requests","Classify the wrapped cause (DNS vs connect vs TLS vs timeout) before reacting","Retry with backoff only for transient causes (timeouts, connection reset)","Validate target reachability with curl -v when debugging persistent failures"],"tags":["network","http","ssrf"],"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"}