{"record":{"id":"3b3669bdd3b844f9","repo":"OpenNHP/opennhp","slug":"failed-to-read-body","errorCode":null,"errorMessage":"failed to read body","messagePattern":"failed to read body","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"endpoints/relay/relay.go","lineNumber":961,"sourceCode":"func (rs *RelayServer) handleRelay(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\thttp.Error(w, \"method not allowed\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\tcr, status, errMsg := rs.resolveServer(r)\n\tif cr == nil {\n\t\thttp.Error(w, errMsg, status)\n\t\treturn\n\t}\n\n\t// Read inner NHP packet from request body. Cap at maxPacketSize+1 so we\n\t// can reject oversize bodies without pulling an unbounded amount into\n\t// memory. A single r.Body.Read() is not guaranteed to return the full\n\t// payload; io.ReadAll drains until EOF.\n\tinnerPacket, err := io.ReadAll(io.LimitReader(r.Body, int64(maxPacketSize)+1))\n\tif err != nil {\n\t\thttp.Error(w, \"failed to read body\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif len(innerPacket) == 0 {\n\t\thttp.Error(w, \"empty packet\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif len(innerPacket) > maxPacketSize {\n\t\thttp.Error(w, \"packet too large\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tn := len(innerPacket)\n\n\t// Extract the counter from the inner packet header (bytes [16:24], big-endian uint64).\n\t// The NHP server echoes this counter in its ACK/COK response, so we use it\n\t// to match the response back to this HTTP request.\n\tif n < 24 {\n\t\thttp.Error(w, \"inner packet too short\", http.StatusBadRequest)\n\t\treturn","sourceCodeStart":943,"sourceCodeEnd":979,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/relay.go#L943-L979","documentation":"handleRelay responds with HTTP 400 'failed to read body' when io.ReadAll fails while draining the request body (capped at maxPacketSize+1 via io.LimitReader). This means the underlying body reader returned an error before EOF, so the inner NHP packet could not be obtained. It guards the relay from forwarding corrupt or truncated payloads to an NHP server instance.","triggerScenarios":"POSTing to the relay endpoint with a request body whose read fails mid-stream: client disconnects before the body finishes transferring, an upstream reverse proxy aborts the connection, or chunked transfer-encoding is cut off before the terminating chunk.","commonSituations":"Flaky mobile/browser clients dropping the connection mid-upload; proxies (nginx, Cloudflare) timing out and closing the upstream body; tests that close the request body or hand the handler a failing reader.","solutions":["Ensure the client sends the complete body and keeps the connection open until the relay responds","Check intermediate reverse proxies for body-read/timeout limits (e.g. nginx proxy_read_timeout, client_body_timeout) and raise them","Retry the request from the client; this is typically a transient transport failure","If testing, verify the test harness provides a valid non-failing io.Reader for r.Body"],"exampleFix":"// before: client aborts mid-send\nreq, _ := http.NewRequest(\"POST\", url, brokenReader)\n// after: buffer the packet fully client-side so the body read cannot fail\nreq, _ := http.NewRequest(\"POST\", url, bytes.NewReader(packetBytes))\nreq.ContentLength = int64(len(packetBytes))","handlingStrategy":"retry","validationCode":"// client: send a fully buffered body and keep the connection open\nif len(packet) == 0 { return errors.New(\"empty packet\") }\nreq, _ := http.NewRequest(\"POST\", relayURL, bytes.NewReader(packet))\nreq.ContentLength = int64(len(packet))","typeGuard":null,"tryCatchPattern":"resp, err := http.DefaultClient.Do(req)\nif err != nil { return retryWithBackoff(req) }\nif resp.StatusCode == 400 { body, _ := io.ReadAll(resp.Body); log.Printf(\"relay rejected body: %s\", body) }","preventionTips":["Buffer the packet fully before sending so the body cannot fail mid-read","Check proxy timeout settings between client and relay","Treat 400 'failed to read body' as a transport issue and retry with backoff"],"tags":["http","network","relay"],"backgroundTag":"http-request-failed","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}