Reputation: 1260
I'm creating an Open Metrics HTTP source for a Prometheus server in Rust. I'd rather avoid pulling in a whole web server library (crate) because I control the infrastructure and pretending to answer HTTP/1.1 is easy enough.
What's a bit surprising is that I'm getting failures from various clients:
I've tried both adding the "Connection" and "Content-Length" headers to try and get around this. Right now this is what's coming out of netcat:
$ nc localhost 3003
HTTP/1.1 200 OK
Content-Type: text/plain; version=0.0.4; charset=utf-8; escaping=underscores
Connection: close
Content-Length: 111
# HELP requests Number of requests received.
# TYPE requests counter
requests_total{method="Get"} 8
# EOF
I'm letting the socket go out of scope. This is where the Rust stuff starts:
let method = Method::Get;
for stream in listener.incoming() {
let mut stream = stream.unwrap();
metrics.inc_requests(method.clone());
println!("Connection established!");
let mut response_buffer = HTTP_HEADERS.to_string();
response_buffer.push_str(&format!("Content-Length: {}", response_buffer.len()));
response_buffer.push_str("\n\n");
encode(&mut response_buffer, ®istry).unwrap();
stream.write(response_buffer.as_bytes()).unwrap();
}
Upvotes: 0
Views: 58
Reputation: 1260
Ah, it turns out there's a shutdown
function that can be used. I was relying on the socket going out of scope. This also removes the content-length requirement. I'm sure this isn't Rust-specific. Skip to the last update for a solution.
let method = Method::Get;
for stream in listener.incoming() {
let mut stream = stream.unwrap();
metrics.inc_requests(method.clone());
println!("Connection established!");
let mut response_buffer = HTTP_HEADERS.to_string();
encode(&mut response_buffer, ®istry).unwrap();
stream.write(response_buffer.as_bytes()).unwrap();
stream.shutdown(std::net::Shutdown::Both).unwrap();
}
This doesn't resolve the problem 100% in Safari (some requests succeed, some don't). But really it just needs to work with Prometheus for now.
Update 1: Reading from the socket first didn't make Safari happy (note the code has been refactored a bit)
fn send_response(mut stream: TcpStream, registry: &Arc<Registry>) -> Result<(), MetricsError> {
let mut response_buffer = HTTP_HEADERS.to_string();
let mut request_buffer = Vec::new();
encode(&mut response_buffer, registry)?;
stream.read(&mut request_buffer)?;
stream.write(response_buffer.as_bytes())?;
stream.flush()?;
stream.shutdown(Shutdown::Both)?;
Ok(())
}
Update 2: Reliably solved! The problem was specifically not reading a sized buffer. Thanks for everyone's comments:
fn send_response(mut stream: TcpStream, registry: &Arc<Registry>) -> Result<(), MetricsError> {
let mut response_buffer = HTTP_HEADERS.to_string();
let mut request_buffer = [0u8; HTTP_REQUEST_BUFFER_SIZE];
encode(&mut response_buffer, registry)?;
stream.read(&mut request_buffer)?;
stream.write_all(response_buffer.as_bytes())?;
stream.flush()?;
stream.shutdown(Shutdown::Both)?;
Ok(())
}
Upvotes: 0