tursodatabase/turso · warning · anyhow::Error

Invalid request line

Error message

Invalid request line

What it means

The first line of the header block must split into at least two whitespace-separated tokens, which become parts[0] (method) and parts[1] (path). Fewer than two tokens means a line like 'GET' with no path, or binary garbage that happens to contain \r\n\r\n. The parser refuses to guess, because indexing parts[1] would panic.

Source

Thrown at cli/sync_server.rs:1179

            let value = line.split(':').nth(1)?.trim();
            return value.parse().ok();
        }
    }
    None
}

fn parse_http_request(data: &[u8]) -> Result<(String, String, Vec<u8>)> {
    let header_end = find_header_end(data, 0).ok_or_else(|| anyhow!("Invalid HTTP request"))?;
    let headers = String::from_utf8_lossy(&data[..header_end]);

    let first_line = headers
        .lines()
        .next()
        .ok_or_else(|| anyhow!("Empty request"))?;
    let parts: Vec<&str> = first_line.split_whitespace().collect();

    if parts.len() < 2 {
        return Err(anyhow!("Invalid request line"));
    }

    let method = parts[0].to_string();
    let path = parts[1].to_string();
    let body = data[header_end + 4..].to_vec();

    Ok((method, path, body))
}

fn format_http_response(resp: &HttpResponse) -> Vec<u8> {
    let status_text = match resp.status {
        200 => "OK",
        204 => "No Content",
        404 => "Not Found",
        500 => "Internal Server Error",
        _ => "Unknown",
    };

View on GitHub (pinned to bad083fafb)

Solutions

  1. Send a well-formed request line: 'METHOD /path HTTP/1.1' — at least method and path tokens.
  2. If a TLS client is involved, point it at the TLS port or fix the scheme mismatch.
  3. Log the offending first line to identify which client is misbehaving.
  4. For health checks, use 'GET /health HTTP/1.1' rather than bare words.

Example fix

// before (client)
write!(stream, "{}\r\n\r\n", command)?; // single-token request line

// after (client)
write!(stream, "GET {} HTTP/1.1\r\nHost: sync\r\n\r\n", path)?;
Defensive patterns

Strategy: validation

Validate before calling

fn request_line_well_formed(data: &[u8]) -> bool {
    let Some(header_end) = (0..data.len().saturating_sub(3))
        .find(|&i| &data[i..i + 4] == b"\r\n\r\n")
    else { return false; };
    String::from_utf8_lossy(&data[..header_end])
        .lines()
        .next()
        .is_some_and(|line| line.split_whitespace().count() >= 2)
}
// before parsing:
anyhow::ensure!(request_line_well_formed(&request_data), "request line needs method and path");

Type guard

fn request_line_well_formed(data: &[u8]) -> bool {
    let Some(header_end) = (0..data.len().saturating_sub(3))
        .find(|&i| &data[i..i + 4] == b"\r\n\r\n")
    else { return false; };
    String::from_utf8_lossy(&data[..header_end])
        .lines()
        .next()
        .is_some_and(|line| line.split_whitespace().count() >= 2)
}

Try / catch

match parse_http_request(&request_data) {
    Ok((method, path, body)) => { /* dispatch */ }
    Err(err) if err.to_string() == "Invalid request line" => {
        // first line lacks method+path: reply 400 and close; log the line to find the client
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: parse_http_request sees a request line that splits into 0 or 1 tokens: a client writing only 'GET\r\n', a bare 'PING', or TLS ClientHello bytes sent to the plain HTTP port where binary leading data precedes the terminator.

Common situations: HTTPS requests pointed at the plain HTTP port (TLS handshake bytes as the first line); custom minimal clients omitting the path or HTTP version; scripts writing shorthand request lines; protocol-confused probes.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/6dc0a909ecd73b78. Report an issue: GitHub.