http-client icon indicating copy to clipboard operation
http-client copied to clipboard

IsahcClient created without Config ignores the timeout setting.

Open trrk opened this issue 2 years ago • 0 comments

IsahcClient::new() creates a client with a timeout set to 60 seconds. However, this client does not actually return a timeout error after 60 seconds. IsahcClient created with Config and try_into does not have this problem and returns a timeout error after 60 seconds.


Here are some additional explanations.

  • There is a concern related to this IsahcClient timeout issue. It is possible that settings other than timeout are also being ignored. Other clients may also have this issue.
  • I noticed this problem while using surf. The timeout behavior was different for the two below.
let client: Client = Config::new()
            .try_into()
            .unwrap();
client.get("...").send().await;

surf::get("...").send().await;
  • Here is the code I used to check the behavior. It takes more than 60 seconds to run. Depending on the environment, it may not work as expected.
use async_std::task;
use futures::prelude::*;
use http_client::{isahc, Config, HttpClient};
use std::time::Duration;

#[async_std::main]
async fn main() {
    let mut tasks = vec![];

    tasks.push(task::spawn(async {
        let client: isahc::IsahcClient = Config::new()
            .try_into()
            .unwrap();
        assert_eq!(client.config().timeout, Some(Duration::from_secs(60)));

        run_test(client, "isahc: created with config").await;
    }));
    tasks.push(task::spawn(async {
        let client = isahc::IsahcClient::new();
        assert_eq!(client.config().timeout, Some(Duration::from_secs(60)));

        run_test(client, "isahc: created by new()").await;
    }));

    println!("start tasks");
    future::join_all(tasks).await;
}

async fn run_test(client: impl HttpClient, process_name: &str) {
    let timeout = client.config().timeout.unwrap();
    let sleep_duration = timeout + Duration::from_secs(1);

    let result = communicate(client, sleep_duration).await;

    let timeout_secs = timeout.as_secs();
    let sleep_secs = sleep_duration.as_secs();
    println!(
        "{process_name}: config.timeout={timeout_secs} sleep={sleep_secs} result={:?}",
        result
    );
}

// Make a request to the server. Then read the body after the specified time has elapsed. Created to cause a timeout.
// If communication is successful, return Ok. Otherwise (including a timeout), return Err.
async fn communicate(client: impl HttpClient, sleep_duration: Duration) -> Result<(), std::io::Error> {
    // If the body is to be read internally to the end, the size must be increased. Otherwise, it will not time out.
    const BODY_BYTES: usize = 80 * 1024;

    let url = format!("http://httpbin.org/bytes/{BODY_BYTES}");
    let url = http_types::Url::parse(&url).unwrap();

    let req = http_types::Request::get(url);
    let mut res = client.send(req).await.unwrap();

    let mut body = res.take_body().into_reader();

    task::sleep(sleep_duration).await;

    let mut total = vec![];
    let read_bytes = body.read_to_end(&mut total).await?;

    assert_eq!(read_bytes, BODY_BYTES);
    Ok(())
}
isahc: created with config: config.timeout=60 sleep=61 result=Err(Kind(TimedOut))
isahc: created by new(): config.timeout=60 sleep=61 result=Ok(())

trrk avatar Apr 10 '22 15:04 trrk