{"record":{"id":"57af175fc74ae5d4","repo":"tursodatabase/turso","slug":"invalid-http-request","errorCode":null,"errorMessage":"Invalid HTTP request","messagePattern":"Invalid HTTP request","errorType":"console","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"cli/sync_server.rs","lineNumber":1284,"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":1266,"sourceCodeEnd":1302,"githubUrl":"https://github.com/tursodatabase/turso/blob/492c4a71cd7c2649e7df83da1471b74f4b1c7aa9/cli/sync_server.rs#L1266-L1302","documentation":"parse_http_request requires the accumulated request bytes to contain a \\r\\n\\r\\n header terminator (find_header_end). When none is present the request is unparseable and this error is returned. In the live server the read loop only breaks after a terminator is found, the header cap is hit, or the peer closed the connection - so in practice this means the client closed or sent garbage without ever completing a header block.","triggerScenarios":"A TCP client connects to the sync port, sends bytes without \\r\\n\\r\\n (e.g. only 'GET /pull-updates HTTP/1.1' then closes, or uses bare \\n line endings), and the connection ends; parse_http_request then finds no terminator.","commonSituations":"Health checks or port scanners opening raw TCP connections; telnet/manual debugging sessions; clients written with \\n line endings; proxies forwarding half a request before timing out.","solutions":["Send complete requests: full header block terminated by a blank \\r\\n line, i.e. the literal bytes \\r\\n\\r\\n","Use an HTTP client library (reqwest, curl) instead of hand-written sockets so framing is correct","If embedding the server, treat this error as a 400 response rather than a connection-level failure"],"exampleFix":"// before\nstream.write_all(b\"POST /pull-updates HTTP/1.1\\nHost: localhost\")?; // no blank line, bare \\n\n\n// after\nstream.write_all(b\"POST /pull-updates HTTP/1.1\\r\\nHost: localhost\\r\\nContent-Length: 0\\r\\n\\r\\n\")?;","handlingStrategy":"validation","validationCode":"// Client-side: verify framing before sending.\nfn request_is_complete(raw: &[u8]) -> bool {\n    raw.windows(4).any(|w| w == b\"\\r\\n\\r\\n\")\n}","typeGuard":"fn looks_like_http_request(data: &[u8]) -> bool {\n    data.starts_with(b\"GET \")\n        || data.starts_with(b\"POST \")\n        || data.starts_with(b\"PUT \")\n        || data.starts_with(b\"DELETE \")\n        || data.starts_with(b\"OPTIONS \")\n}","tryCatchPattern":"// Server-side: map parse failures to 400 instead of dropping the connection.\nlet (method, path, body) = match parse_http_request(&request_data) {\n    Ok(parsed) => parsed,\n    Err(_) => {\n        respond_400(&mut stream)?;\n        return Ok(());\n    }\n};","preventionTips":["Terminate every HTTP request with the literal bytes \\r\\n\\r\\n","Use established HTTP client libraries instead of raw sockets","Expect bare TCP probes (scanners, health checks) to fail parsing; answer 400, do not crash"],"tags":["http","sync-server","request-parsing","protocol-error"],"backgroundTag":"malformed-http-request","analyzedSha":"492c4a71cd7c2649e7df83da1471b74f4b1c7aa9","analyzedAt":"2026-08-20T07:02:18.389Z","contentChangedAt":"2026-08-20T07:02:18.389Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}