affaan-m/ECC · warning · anyhow::Error

Unexpected EOF while reading HTTP request

Error message

Unexpected EOF while reading HTTP request

What it means

The built-in HTTP server's request reader loops reading from the client socket until it finds the `\r\n\r\n` header terminator. If `stream.read` returns 0 — the peer closed the connection before sending complete headers — it bails with this error. It is a guard against truncated/malformed requests from misbehaving clients.

Solutions

  1. Retry the request from the client once the connection is stable.
  2. Identify clients that probe with connect-then-close and configure them to send a real HTTP request (e.g. `GET /health`).
  3. If it occurs from your own code, ensure the client writes and flushes the full request before closing the socket.

Example fix

// before (client closes early)
let mut s = TcpStream::connect(addr)?;
drop(s); // EOF on server

// after
let mut s = TcpStream::connect(addr)?;
write!(s, "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Client: ensure the request is fully written before any early return/close
assert!(request.contains("\r\n\r\n"), "request missing header terminator");

Try / catch

// Server caller side
match read_http_request(&mut stream) {
    Ok(req) => handle(req),
    Err(e) if e.to_string().contains("Unexpected EOF") => {
        log::debug!("client closed connection early: {e}"); // non-fatal, drop connection
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A client opens a TCP connection to the local HTTP endpoint and closes it (or the connection drops) before sending the full request headers; a health-check probe that connects and disconnects; a port scanner or half-open connection timing out.

Common situations: Load balancer or monitoring health checks that only test TCP connect; curl aborted with Ctrl-C mid-request; network interruption between the client and the locally spawned server; a client sending a request smaller than the buffer read granularity that stalls and gets reset.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/e17c1f0affd392ae. Report an issue: GitHub.

Appendix: source

Thrown at ecc2/src/main.rs:4198

        }
        _ => write_http_response(
            stream,
            404,
            "application/json",
            &serde_json::json!({"error": "not found"}).to_string(),
        ),
    }
}

fn read_http_request(
    stream: &mut TcpStream,
) -> Result<(String, String, BTreeMap<String, String>, Vec<u8>)> {
    let mut buffer = Vec::new();
    let mut temp = [0_u8; 1024];
    let header_end = loop {
        let read = stream.read(&mut temp)?;
        if read == 0 {
            anyhow::bail!("Unexpected EOF while reading HTTP request");
        }
        buffer.extend_from_slice(&temp[..read]);
        if let Some(index) = buffer.windows(4).position(|window| window == b"\r\n\r\n") {
            break index + 4;
        }
        if buffer.len() > 64 * 1024 {
            anyhow::bail!("HTTP request headers too large");
        }
    };

    let header_text = String::from_utf8(buffer[..header_end].to_vec())
        .context("HTTP request headers were not valid UTF-8")?;
    let mut lines = header_text.split("\r\n");
    let request_line = lines
        .next()
        .filter(|line| !line.trim().is_empty())
        .ok_or_else(|| anyhow::anyhow!("Missing HTTP request line"))?;
    let mut request_parts = request_line.split_whitespace();

View on GitHub (pinned to 8321021c54)