Pulling HTTP data requires sleep -- code snippet from Programming Rust 2nd Edition

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)
}
1 Like

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)

cargo 1.63.0 (fd9c4297c 2022-07-01)
rustc 1.63.0 (4b91a6ea7 2022-08-08)

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

STDOUT
worldtimeapi.org - Output is ``

use std::io::{Read, Write};
use std::net::{Shutdown, TcpStream};
use std::{io};

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)
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.