OpenNHP/opennhp · error
failed to read body
Error message
failed to read body
What it means
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.
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
Example fix
// before: client aborts mid-send
req, _ := http.NewRequest("POST", url, brokenReader)
// after: buffer the packet fully client-side so the body read cannot fail
req, _ := http.NewRequest("POST", url, bytes.NewReader(packetBytes))
req.ContentLength = int64(len(packetBytes)) Defensive patterns
Strategy: retry
Validate before calling
// client: send a fully buffered body and keep the connection open
if len(packet) == 0 { return errors.New("empty packet") }
req, _ := http.NewRequest("POST", relayURL, bytes.NewReader(packet))
req.ContentLength = int64(len(packet)) Try / catch
resp, err := http.DefaultClient.Do(req)
if err != nil { return retryWithBackoff(req) }
if resp.StatusCode == 400 { body, _ := io.ReadAll(resp.Body); log.Printf("relay rejected body: %s", body) } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- failed to download ztdo
- could not send https request
- could not read response body
- missing source address
- failed to download HRK
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/3b3669bdd3b844f9.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/relay/relay.go:961
func (rs *RelayServer) handleRelay(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
cr, status, errMsg := rs.resolveServer(r)
if cr == nil {
http.Error(w, errMsg, status)
return
}
// Read inner NHP packet from request body. Cap at maxPacketSize+1 so we
// can reject oversize bodies without pulling an unbounded amount into
// memory. A single r.Body.Read() is not guaranteed to return the full
// payload; io.ReadAll drains until EOF.
innerPacket, err := io.ReadAll(io.LimitReader(r.Body, int64(maxPacketSize)+1))
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
return
}
if len(innerPacket) == 0 {
http.Error(w, "empty packet", http.StatusBadRequest)
return
}
if len(innerPacket) > maxPacketSize {
http.Error(w, "packet too large", http.StatusBadRequest)
return
}
n := len(innerPacket)
// Extract the counter from the inner packet header (bytes [16:24], big-endian uint64).
// The NHP server echoes this counter in its ACK/COK response, so we use it
// to match the response back to this HTTP request.
if n < 24 {
http.Error(w, "inner packet too short", http.StatusBadRequest)
returnView on GitHub (pinned to 6e04ca5ff0)