Hi folks, I'm currently reading "Programming Rust 2nd edition" and trying to run the blocking HTTP client code snippet. But I'm unable to read data from the server without adding a "sleep" call after writing my data. I know I should be using something like reqwest but trying to figure out what I'm doing wrong here.
fn main() {
let host = "worldtimeapi.org";
let path = "/api/timezone/America/Argentina/Salta";
let response = send_request(host, 80, path);
println!("Output is `{}`", host, response.unwrap());
}
fn send_request(host: &str, port: u16, path: &str) -> io::Result<String> {
let mut socket = TcpStream::connect((host, port))?;
let request = format!("GET {} HTTP/1.1\r\nHost: {}\r\n\r\n", path, host);
socket.write_all(request.as_bytes())?;
socket.set_nodelay(true)?;
socket.flush()?;
// Why do I need sleep here? Removing this shows me the following output on STDOUT
// Output is ``
thread::sleep(Duration::from_secs(1));
socket.shutdown(Shutdown::Write)?;
let mut response = String::new();
socket.read_to_string(&mut response)?;
Ok(response)
}
It's nagle's algorithm. Since the .set_nodelay(true) applied after the .write_all(...), this write is not sent instantly. With thread::sleep(1s) it will be sent anyway, but without it you're shutting down the TCP write half before the send buffer is actually send.
If you move the socket.set_nodelay(true) one line above, it works.
Side note: socket.flush() is no-op. It can only flush things managed by userland like BufWriter<T>. But the in-kernel TCP buffer is not flushed with it.
Fulll code that works:
use std::io::{self, Read, Write};
use std::net::{TcpStream, Shutdown};
fn main() {
let host = "worldtimeapi.org";
let path = "/api/timezone/America/Argentina/Salta";
let response = send_request(host, 80, path);
println!("{} - Output is `{}`", host, response.unwrap());
}
fn send_request(host: &str, port: u16, path: &str) -> io::Result<String> {
let mut socket = TcpStream::connect((host, port))?;
let request = format!("GET {} HTTP/1.1\r\nHost: {}\r\n\r\n", path, host);
socket.set_nodelay(true)?;
socket.write_all(request.as_bytes())?;
socket.shutdown(Shutdown::Write)?;
let mut response = String::new();
socket.read_to_string(&mut response)?;
Ok(response)
}
Thanks @Hyeonu -- it's interesting to note that moving the set_nodelay call by copy-pasting your code still doesn't work for me. Would you know why? I'm using the following rustc and cargo versions on my M1 Macbook Air (in case it makes a difference)
Hi folks, any help on this please? It seems strange that a simple HTTP client snippet doesn't work without a sleep even after enabling nodelay. This is my latest snippet