Skip to content

reqwest

Web & HTTPNetworking/HTTPRust

What it is

reqwest is the standard high-level HTTP client for Rust, with async and blocking APIs, JSON support, connection pooling, cookies and proxies.

Build a Client once and reuse it. Requests are built fluently and awaited; the json feature integrates with Serde for both request and response bodies.

Installation

cargo add reqwest --features json

Getting started

The smallest useful thing you can do with it, and what each part means.

Typed GET and POST
let client = reqwest::Client::builder()
    .timeout(Duration::from_secs(10))
    .build()?;

// Deserialises straight into your type.
let book: Book = client
    .get("https://api.example.com/books/42")
    .header("Authorization", format!("Bearer {token}"))
    .send()
    .await?
    .error_for_status()?   // turn 4xx/5xx into an Err
    .json()
    .await?;

let created: Book = client
    .post("https://api.example.com/books")
    .json(&new_book)
    .send().await?.error_for_status()?.json().await?;
error_for_status is the line people forget: without it a 500 response is a successful request, and the following json() fails with a confusing parse error.

Advanced usage

Where the library earns its place over a simpler alternative.

Concurrency and retries
use futures::stream::{self, StreamExt};

// Bounded concurrency — do not open 10,000 sockets at once.
let results: Vec<_> = stream::iter(urls)
    .map(|url| {
        let client = client.clone();   // cheap: an Arc internally
        async move { client.get(url).send().await?.text().await }
    })
    .buffer_unordered(16)
    .collect()
    .await;

// Simple backoff for idempotent requests.
let mut delay = Duration::from_millis(200);
for attempt in 0..3 {
    match client.get(&url).send().await {
        Ok(r) if r.status().is_success() => break,
        _ if attempt < 2 => { tokio::time::sleep(delay).await; delay *= 2; }
        other => return other.map(|_| ()).map_err(Into::into),
    }
}
Cloning a Client is cheap and shares the connection pool. buffer_unordered caps in-flight requests, which is what stops a scraper exhausting file descriptors.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

error decoding response body
The response is not the JSON you expected, often an HTML error page. Call error_for_status first and log the body when parsing fails.
Too many open files
Unbounded concurrent requests. Cap them with buffer_unordered or a semaphore.

Best practices

  • Create one Client and clone it; a new Client per request discards the connection pool.
  • Always set a timeout — the default is unbounded.
  • Call error_for_status so HTTP error codes become Rust errors.
  • Use buffer_unordered to bound concurrency rather than spawning unlimited tasks.

Background

Why it exists, and what it was reacting to.

Built by Sean McArthur on top of his Hyper library, reqwest provides the ergonomic layer Hyper deliberately omits, and is the default choice for making HTTP requests from Rust.