OpenNHP/opennhp · error

could not read response body

Error message

could not read response body: %v

What it means

After a 200 response, the function reads the full response body with io.ReadAll(resp.Body). If the read fails mid-stream — connection dropped or reset before the body finished arriving, chunked-encoding error, or an idle/keep-alive read timeout — it wraps the error as "could not read response body: %v". The upload itself likely succeeded server-side but the client cannot confirm it.

Solutions

  1. Retry the whole upload when the error is io.ErrUnexpectedEOF or a connection-reset, since the server state is uncertain (idempotency permitting)
  2. Check network path stability (VPN/proxy idle timeouts) and raise proxy keep-alive/idle timeouts above the upload duration
  3. Check server logs to see whether the upload was persisted despite the truncated response
  4. Verify the server writes and flushes its response promptly after processing instead of holding the connection
  5. If using a custom Transport, ensure ResponseHeaderTimeout/IdleConnTimeout are generous for long uploads

Example fix

// before
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
	return "", fmt.Errorf("could not read response body: %v", err)
}
// after
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
	if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {
		return "", fmt.Errorf("response truncated, upload result unknown, retry: %v", err)
	}
	return "", fmt.Errorf("could not read response body: %v", err)
}
Defensive patterns

Strategy: retry

Validate before calling

client := &http.Client{
	Timeout: 120 * time.Minute,
	Transport: &http.Transport{
		IdleConnTimeout: 300 * time.Second,
		ResponseHeaderTimeout: 120 * time.Second,
	},
}
_ = client // use generous timeouts so keep-alive reads are not cut short

Try / catch

result, err := device.UploadFileToNHPServer(filePath)
if err != nil && strings.Contains(err.Error(), "could not read response body") {
	if strings.Contains(err.Error(), "unexpected EOF") || strings.Contains(err.Error(), "connection reset") {
		// server may have persisted the file; verify existence before blind re-upload
		return retryUploadWithBackoff(filePath)
	}
	return fmt.Errorf("failed reading upload confirmation: %w", err)
}

Prevention

When it happens

Trigger: io.ReadAll(resp.Body) returns a non-EOF error in UploadFileToNHPServer (endpoints/db/utils.go:271-274) on a 200 response: unexpected EOF from a connection reset after headers, server closing the connection while streaming the response, intermediate proxy truncating the response, or read timeout on a stalled keep-alive connection.

Common situations: Flaky network or NAT dropping the connection right after the upload completes; proxy/load balancer with a short idle timeout cutting the response; server crashing mid-response; very large 200 responses interrupted on slow links.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/661a320bf4ccd9fa. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/db/utils.go:273

		Timeout: 120 * time.Minute,
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("could not send https request: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		bodyBytes, _ := io.ReadAll(resp.Body)

		return "", fmt.Errorf("unexpected status code: %d, content: %s", resp.StatusCode, string(bodyBytes))
	}

	// read response body
	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("could not read response body: %v", err)
	}

	// parse response body
	var respBody ServerResponse

	err = json.Unmarshal(bodyBytes, &respBody)
	if err != nil {
		return "", fmt.Errorf("could not parse response body: %v", err)
	}

	duration := time.Since(startTime)
	speed := float64(progress.TotalSize) / duration.Seconds() / (1024 * 1024)

	// change the chinese to english
	fmt.Printf("\nUpload %s to %s success! (time: %.2fs, speed: %.2fMB/s)\n",
		filePath, httpHost+respBody.FileURI, duration.Seconds(), speed)

	return httpHost + respBody.FileURI, nil

View on GitHub (pinned to 6e04ca5ff0)