{"record":{"id":"d9f19b203074fec4","repo":"micro/go-micro","slug":"rsp-status-body-dynamic-http-status-and-bo","errorCode":null,"errorMessage":"${rsp.Status}: ${body} (dynamic HTTP status and body)","messagePattern":"(.+?): (.+?) \\(dynamic HTTP status and body\\)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"transport/http_client.go","lineNumber":169,"sourceCode":"\n\trsp, err := http.ReadResponse(h.buff, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif err2 := rsp.Body.Close(); err2 != nil {\n\t\t\terr = errors.Wrap(err2, \"failed to close body\")\n\t\t}\n\t}()\n\n\tb, err := io.ReadAll(rsp.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn errors.New(rsp.Status + \": \" + string(b))\n\t}\n\n\tmsg.Body = b\n\n\tif msg.Header == nil {\n\t\tmsg.Header = make(map[string]string, len(rsp.Header))\n\t}\n\n\tfor k, v := range rsp.Header {\n\t\tif len(v) > 0 {\n\t\t\tmsg.Header[k] = v[0]\n\t\t} else {\n\t\t\tmsg.Header[k] = \"\"\n\t\t}\n\t}\n\n\treturn nil\n}","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/transport/http_client.go#L151-L187","documentation":"When an HTTP response's status code is anything other than 200 OK, Recv reads the whole body and returns an error containing '<status string>: <body>'. The error message is dynamic: the transport surfaces the upstream HTTP status and response body directly as the error text, so non-OK responses never reach your Message.","triggerScenarios":"Any Recv over a plain (non-stream) request where the server replies 4xx/5xx — auth failures (401/403), not found (404), validation errors (400), or backend crashes (500).","commonSituations":"Calling a service whose endpoint moved (404), expired credentials (401), or a downstream service returning JSON error bodies that end up concatenated into the Go error string; parsing code that expects msg.Body gets nothing because the error path fired first.","solutions":["Log err.Error() — it embeds the HTTP status and the server's error body; fix the root cause the status indicates (auth, path, payload)","On the client side, match the status prefix (e.g. strings.HasPrefix(err.Error(), \"401\")) if you need to branch, or prefer the socket/stream API that exposes headers","Fix the server side so healthy requests return 200; add health checks/retries for transient 5xx"],"exampleFix":"// before\nif err := client.Recv(msg); err != nil { return err } // \"401 Unauthorized: {\\\"error\\\":...}\" opaque\n// after\nif err := client.Recv(msg); err != nil {\n\tif strings.HasPrefix(err.Error(), \"401\") { return ErrUnauthorized }\n\treturn err\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := client.Recv(msg); err != nil {\n\tfor _, code := range []string{\"401\",\"403\",\"404\",\"500\"} {\n\t\tif strings.HasPrefix(err.Error(), code) { /* handle status */ }\n\t}\n\treturn err\n}","preventionTips":["Log the full error text — it contains status and server body","Prefer checking endpoint health before calls in tests","Retry only 5xx; surface 4xx to the caller"],"tags":["transport","http","status-code","api"],"backgroundTag":"http-non-200-response","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}