use std::io::{Read as _, Write as _};
use std::net::{TcpListener, TcpStream};
use super::{handle_api_request_with_headers, ApiHttpResponse};
struct ParsedHttpRequest {
method: String,
path: String,
headers: Vec<(String, String)>,
body: String,
}
pub fn serve(address: &str) -> std::io::Result<()> {
crate::dreaming_runtime::start_core_dreaming();
eprintln!(
"formal-ai shared memory: {}",
crate::shared_memory::shared_memory_path().display()
);
let listener = TcpListener::bind(address)?;
eprintln!("formal-ai server listening on http://{address}");
for stream in listener.incoming() {
match stream {
Ok(mut stream) => {
if let Err(error) = handle_connection(&mut stream) {
eprintln!("request failed: {error}");
}
}
Err(error) => eprintln!("connection failed: {error}"),
}
}
Ok(())
}
fn handle_connection(stream: &mut TcpStream) -> std::io::Result<()> {
let Some(request) = read_request(stream)? else {
return Ok(());
};
let headers = request
.headers
.iter()
.map(|(name, value)| (name.as_str(), value.as_str()))
.collect::<Vec<_>>();
let response =
handle_api_request_with_headers(&request.method, &request.path, &headers, &request.body);
write_response(stream, &response)
}
fn read_request(stream: &mut TcpStream) -> std::io::Result<Option<ParsedHttpRequest>> {
let mut buffer = [0_u8; 8192];
let bytes_read = stream.read(&mut buffer)?;
if bytes_read == 0 {
return Ok(None);
}
let mut request_bytes = buffer[..bytes_read].to_vec();
let header_end = loop {
if let Some(position) = find_header_end(&request_bytes) {
break position;
}
let bytes_read = stream.read(&mut buffer)?;
if bytes_read == 0 {
return Ok(None);
}
request_bytes.extend_from_slice(&buffer[..bytes_read]);
};
let header_text = String::from_utf8_lossy(&request_bytes[..header_end]).to_string();
let content_length = content_length(&header_text);
let body_start = header_end + 4;
while request_bytes.len() < body_start.saturating_add(content_length) {
let bytes_read = stream.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
request_bytes.extend_from_slice(&buffer[..bytes_read]);
}
let request_line = header_text.lines().next().unwrap_or_default();
let mut request_parts = request_line.split_whitespace();
let method = request_parts.next().unwrap_or_default().to_owned();
let path = request_parts.next().unwrap_or_default().to_owned();
let headers = request_headers(&header_text);
let body_end = body_start
.saturating_add(content_length)
.min(request_bytes.len());
let body = String::from_utf8_lossy(&request_bytes[body_start..body_end]).to_string();
Ok(Some(ParsedHttpRequest {
method,
path,
headers,
body,
}))
}
fn write_response(stream: &mut TcpStream, response: &ApiHttpResponse) -> std::io::Result<()> {
let status_text = match response.status_code {
200 => "200 OK",
204 => "204 No Content",
400 => "400 Bad Request",
401 => "401 Unauthorized",
403 => "403 Forbidden",
404 => "404 Not Found",
405 => "405 Method Not Allowed",
_ => "500 Internal Server Error",
};
let deprecation_header = if response.deprecated {
"deprecation: true\r\nlink: </v1/network>; rel=\"successor-version\"\r\n"
} else {
""
};
write!(
stream,
"HTTP/1.1 {status_text}\r\n\
content-type: {}\r\n\
content-length: {}\r\n\
access-control-allow-origin: *\r\n\
access-control-allow-methods: GET,POST,OPTIONS\r\n\
access-control-allow-headers: content-type,authorization,x-api-key,x-goog-api-key,anthropic-api-key\r\n\
{deprecation_header}\
connection: close\r\n\
\r\n{}",
response.content_type,
response.body.len(),
response.body
)
}
fn find_header_end(bytes: &[u8]) -> Option<usize> {
bytes.windows(4).position(|window| window == b"\r\n\r\n")
}
fn request_headers(headers: &str) -> Vec<(String, String)> {
headers
.lines()
.skip(1)
.filter_map(|line| {
let (name, value) = line.split_once(':')?;
Some((name.trim().to_owned(), value.trim().to_owned()))
})
.collect()
}
fn content_length(headers: &str) -> usize {
headers
.lines()
.find_map(|line| {
let (name, value) = line.split_once(':')?;
if name.eq_ignore_ascii_case("content-length") {
value.trim().parse::<usize>().ok()
} else {
None
}
})
.unwrap_or(0)
}