tursodatabase/turso · error · anyhow::Error
Invalid HTTP request
Error message
Invalid HTTP request
What it means
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.
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
Example fix
// before stream.write_all(b"POST /pull-updates HTTP/1.1\nHost: localhost")?; // no blank line, bare \n // after stream.write_all(b"POST /pull-updates HTTP/1.1\r\nHost: localhost\r\nContent-Length: 0\r\n\r\n")?;
Defensive patterns
Strategy: validation
Validate before calling
// Client-side: verify framing before sending.
fn request_is_complete(raw: &[u8]) -> bool {
raw.windows(4).any(|w| w == b"\r\n\r\n")
} Type guard
fn looks_like_http_request(data: &[u8]) -> bool {
data.starts_with(b"GET ")
|| data.starts_with(b"POST ")
|| data.starts_with(b"PUT ")
|| data.starts_with(b"DELETE ")
|| data.starts_with(b"OPTIONS ")
} Try / catch
// Server-side: map parse failures to 400 instead of dropping the connection.
let (method, path, body) = match parse_http_request(&request_data) {
Ok(parsed) => parsed,
Err(_) => {
respond_400(&mut stream)?;
return Ok(());
}
}; Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Empty request
- Invalid request line
- HTTP request length overflows
- buffer too short for u32 at offset
- buffer too short for u64 at offset
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/57af175fc74ae5d4.
Report an issue: GitHub.
Appendix: source
Thrown at cli/sync_server.rs:1284
}
fn find_header_end(data: &[u8], start: usize) -> Option<usize> {
(start..data.len().saturating_sub(3)).find(|&i| &data[i..i + 4] == b"\r\n\r\n")
}
fn parse_content_length(headers: &str) -> Option<usize> {
for line in headers.lines() {
let lower = line.to_lowercase();
if lower.starts_with("content-length:") {
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))
}View on GitHub (pinned to 492c4a71cd)