{"record":{"id":"57af175fc74ae5d4","repo":"tursodatabase/turso","slug":"invalid-http-request","errorCode":null,"errorMessage":"Invalid HTTP request","messagePattern":"Invalid HTTP request","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"cli/sync_server.rs","lineNumber":1169,"sourceCode":"}\n\nfn find_header_end(data: &[u8], start: usize) -> Option<usize> {\n    (start..data.len().saturating_sub(3)).find(|&i| &data[i..i + 4] == b\"\\r\\n\\r\\n\")\n}\n\nfn parse_content_length(headers: &str) -> Option<usize> {\n    for line in headers.lines() {\n        let lower = line.to_lowercase();\n        if lower.starts_with(\"content-length:\") {\n            let value = line.split(':').nth(1)?.trim();\n            return value.parse().ok();\n        }\n    }\n    None\n}\n\nfn parse_http_request(data: &[u8]) -> Result<(String, String, Vec<u8>)> {\n    let header_end = find_header_end(data, 0).ok_or_else(|| anyhow!(\"Invalid HTTP request\"))?;\n    let headers = String::from_utf8_lossy(&data[..header_end]);\n\n    let first_line = headers\n        .lines()\n        .next()\n        .ok_or_else(|| anyhow!(\"Empty request\"))?;\n    let parts: Vec<&str> = first_line.split_whitespace().collect();\n\n    if parts.len() < 2 {\n        return Err(anyhow!(\"Invalid request line\"));\n    }\n\n    let method = parts[0].to_string();\n    let path = parts[1].to_string();\n    let body = data[header_end + 4..].to_vec();\n\n    Ok((method, path, body))\n}","sourceCodeStart":1151,"sourceCodeEnd":1187,"githubUrl":"https://github.com/tursodatabase/turso/blob/bad083fafbefdeae9a42ec19bdaaad8918dcf411/cli/sync_server.rs#L1151-L1187","documentation":"parse_http_request first searches the accumulated bytes for the \\r\\n\\r\\n header terminator; none found yields this generic rejection. In the connection loop it typically surfaces when the peer closes the connection (read returns 0) before sending a complete header block, because oversized-but-terminator-less headers are caught earlier by the MAX_HEADER_BYTES check. It means: these bytes are not a complete HTTP request.","triggerScenarios":"A peer sends bytes without \\r\\n\\r\\n and closes the connection, so handle_connection exits its read loop and parse_http_request fails; also direct calls to parse_http_request on non-HTTP bytes (raw TCP probes, TLS handshakes sent to the plain port).","commonSituations":"Health checks or monitoring probing the sync port with raw TCP instead of HTTP; clients timing out and closing mid-header; reverse proxies forwarding partial requests; test scripts sending hand-typed requests without the blank line.","solutions":["From the client, send complete HTTP headers terminated by \\r\\n\\r\\n before the body.","Use a real HTTP client (curl, reqwest) for health checks instead of raw TCP connects.","If headers legitimately exceed the limit, raise MAX_HEADER_BYTES or split them.","Retry the request in full; the server discards a request whose headers never completed."],"exampleFix":"// before (client)\nstream.write_all(body)?; // raw bytes, no header block\n\n// after (client)\nstream.write_all(b\"POST /sync HTTP/1.1\\r\\nHost: tursodb\\r\\nContent-Length: 4\\r\\n\\r\\n\")?;\nstream.write_all(body)?;","handlingStrategy":"validation","validationCode":"fn is_complete_http_request(data: &[u8]) -> bool {\n    find_header_end(data, 0).is_some()\n}\n// before parsing:\nif !is_complete_http_request(&request_data) {\n    // peer closed before finishing headers: drop the connection\n    return Ok(());\n}\nlet (method, path, body) = parse_http_request(&request_data)?;","typeGuard":"fn is_complete_http_request(data: &[u8]) -> bool {\n    (0..data.len().saturating_sub(3)).any(|i| &data[i..i + 4] == b\"\\r\\n\\r\\n\")\n}","tryCatchPattern":"match parse_http_request(&request_data) {\n    Ok((method, path, body)) => { /* dispatch */ }\n    Err(err) if err.to_string() == \"Invalid HTTP request\" => {\n        // incomplete or non-HTTP bytes: respond 400 and close; do not retry reads\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Send complete requests: headers terminated by \\r\\n\\r\\n, then the body.","Use real HTTP clients for monitoring and health checks, not raw TCP connects.","Handle peer close (read returns 0) as end-of-request, not as a parse attempt.","Prefer a production HTTP server framework for anything beyond test tooling."],"tags":["http","request-parsing","sync-server","protocol"],"backgroundTag":"malformed-http-request","analyzedSha":"bad083fafbefdeae9a42ec19bdaaad8918dcf411","analyzedAt":"2026-08-16T23:12:11.798Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}