Set timeouts and handle errors

For Rust developers calling servers that can be slow or answer with an error

You give a fibre_http client a timeout, turn error statuses like 404 into errors you can match on and watch a request to a server that never answers give up on time.

Before you start

You need Rust with Cargo. fibre_http comes from crates.io:

fibre_http = "0"

Set up the project#

Start a new binary crate.

cargo new timeouts
cd timeouts

Put this in the [dependencies] section of Cargo.toml:

Cargo.toml
[dependencies]
fibre_http = "0"

This tutorial uses the blocking client, so it needs no async runtime.

The program#

Replace src/main.rs with this:

src/main.rs
use std::net::TcpListener;
use std::time::{Duration, Instant};

use fibre_http::{Error, SyncClient};

fn fetch(client: &SyncClient, url: &str) -> Result<String, Error> {
    let response = client.get(url).send()?.error_for_status()?;
    response.text()
}

fn main() -> Result<(), Error> {
    let client = SyncClient::builder().timeout(Duration::from_secs(10)).build_sync()?;

    match fetch(&client, "https://example.com/") {
        Ok(page) => println!("example.com: {} bytes", page.len()),
        Err(e) => println!("example.com failed: {e}"),
    }

    match fetch(&client, "https://example.com/no-such-page") {
        Ok(page) => println!("no-such-page: {} bytes", page.len()),
        Err(Error::Status(code)) => println!("no-such-page: the server answered {code}"),
        Err(e) => println!("no-such-page failed: {e}"),
    }

    let silent = TcpListener::bind("127.0.0.1:0").expect("bind a local port");
    let url = format!("http://{}/", silent.local_addr().expect("local address"));

    let started = Instant::now();
    let result = client.get(&url).timeout(Duration::from_millis(500)).send();
    match result {
        Ok(response) => println!("silent server: {}", response.status()),
        Err(Error::Timeout) => println!("silent server: gave up after {} ms", started.elapsed().as_millis()),
        Err(e) => println!("silent server failed: {e}"),
    }
    Ok(())
}

Run it:

$ cargo run
example.com: 577 bytes
no-such-page: the server answered 404
silent server: gave up after 500 ms

What each part does#

timeout(Duration::from_secs(10)) on the builder gives every request from this client a 10 second limit. The limit covers the whole request: getting a connection, sending, waiting for the answer and reading the body.

A 404 is still a response, so send succeeds. error_for_status() turns any 4xx or 5xx status into Error::Status, which carries the code. That lets one match handle network failures and error statuses together.

The third request goes to a local port that accepts connections and never replies. timeout on the request replaces the client's 10 seconds with 500 ms for this one call. When the time runs out, send returns Error::Timeout.

Error has other cases worth matching on, such as Error::Tls for a certificate problem and Error::Refused when a server turns a request away before handling it. The usage guide lists them.

Where to go next#

The usage guide covers redirects, retries, proxies and the rest of the error cases: https://github.com/excsn/fibre/blob/main/http/README.USAGE.md

The full API is on docs.rs at https://docs.rs/fibre_http.