How do I work with Reqwest for HTTP Client Operations in Rust?

Walkthrough

Reqwest is a simple, higher-level HTTP client for Rust. It provides a convenient API for making HTTP requests, handling responses, and working with various content types. Built on top of hyper, it offers both blocking and async APIs.

Key concepts:

  • Client — Reusable HTTP client with configuration
  • RequestBuilder — Builder pattern for constructing requests
  • Response — HTTP response with status, headers, body
  • Body — Request/response body handling
  • Middleware — Request/response interception

When to use Reqwest:

  • API clients and integrations
  • Web scraping (with permission)
  • HTTP webhooks
  • Downloading files
  • Testing HTTP servers

When NOT to use Reqwest:

  • Building HTTP servers (use axum or hyper directly)
  • When you need maximum performance (use hyper directly)
  • Embedded systems (too heavy)

Code Examples

Basic GET Request

use reqwest::blocking::get;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Simple GET request
    let response = get("https://httpbin.org/get")?;
    
    println!("Status: {}", response.status());
    println!("Headers:\n{:#?}", response.headers());
    
    // Get body as text
    let body = response.text()?;
    println!("Body:\n{}", body);
    
    Ok(())
}

Async GET Request

use reqwest::Client;
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let body = reqwest::get("https://httpbin.org/get").await?.text().await?;
    println!("Body: {}", body);
    Ok(())
}

Using Client for Multiple Requests

use reqwest::blocking::Client;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a reusable client
    let client = Client::new();
    
    // Make multiple requests with same client
    let resp1 = client.get("https://httpbin.org/get").send()?;
    let resp2 = client.get("https://httpbin.org/ip").send()?;
    
    println!("Response 1: {}", resp1.status());
    println!("Response 2: {}", resp2.status());
    
    Ok(())
}

POST Request with JSON

use reqwest::blocking::Client;
use serde_json::json;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let body = json!({
        "name": "Alice",
        "email": "alice@example.com"
    });
    
    let response = client
        .post("https://httpbin.org/post")
        .json(&body)
        .send()?;
    
    println!("Status: {}", response.status());
    println!("Response: {}", response.text()?);
    
    Ok(())
}

POST with Form Data

use reqwest::blocking::Client;
use std::collections::HashMap;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let mut form = HashMap::new();
    form.insert("username", "alice");
    form.insert("password", "secret");
    
    let response = client
        .post("https://httpbin.org/post")
        .form(&form)
        .send()?;
    
    println!("Response: {}", response.text()?);
    
    Ok(())
}

Custom Headers

use reqwest::blocking::Client;
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, CONTENT_TYPE, AUTHORIZATION};
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    // Build headers
    let mut headers = HeaderMap::new();
    headers.insert(USER_AGENT, HeaderValue::from_static("MyApp/1.0"));
    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
    headers.insert(AUTHORIZATION, HeaderValue::from_static("Bearer token123"));
    
    let response = client
        .get("https://httpbin.org/headers")
        .headers(headers)
        .send()?;
    
    println!("Response: {}", response.text()?);
    
    Ok(())
}

Query Parameters

use reqwest::blocking::Client;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let response = client
        .get("https://httpbin.org/get")
        .query(&[
            ("page", "1"),
            ("limit", "10"),
            ("sort", "created"),
        ])
        .send()?;
    
    println!("URL: {:?}", response.url());
    println!("Response: {}", response.text()?);
    
    Ok(())
}
 
### Handling JSON Responses
 
```rust
use reqwest::blocking::Client;
use serde::Deserialize;
 
#[derive(Deserialize, Debug)]
struct IpResponse {
    origin: String,
}
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    // Deserialize JSON directly
    let ip: IpResponse = client
        .get("https://httpbin.org/ip")
        .send()?\
        .json()?;
    
    println!("IP: {}", ip.origin);
    
    Ok(())
}

Error Handling

use reqwest::blocking::Client;
use reqwest::StatusCode;
 
fn fetch_url(url: &str) -> Result<String, Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let response = client.get(url).send()?;
    
    match response.status() {
        StatusCode::OK => Ok(response.text()?),
        StatusCode::NOT_FOUND => Err("Not found".into()),
        StatusCode::INTERNAL_SERVER_ERROR => Err("Server error".into()),
        status => Err(format!("Unexpected status: {}", status).into()),
    }
}
 
fn main() {
    match fetch_url("https://httpbin.org/get") {
        Ok(body) => println!("Success: {} bytes", body.len()),
        Err(e) => eprintln!("Error: {}", e),
    }
}

Timeouts

use reqwest::blocking::Client;
use std::time::Duration;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::builder()
        .timeout(Duration::from_secs(10))
        .connect_timeout(Duration::from_secs(5))
        .build()?;
    
    let response = client
        .get("https://httpbin.org/delay/2")
        .send()?;
    
    println!("Status: {}", response.status());
    
    Ok(())
}

Download File

use reqwest::blocking::Client;
use std::fs::File;
use std::io::copy;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let mut response = client
        .get("https://httpbin.org/image/png")
        .send()?;
    
    let mut dest = File::create("downloaded.png")?;
    copy(&mut response, &mut dest)?;
    
    println!("Downloaded successfully");
    
    Ok(())
}

Upload File

use reqwest::blocking::Client;
use std::fs::File;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let file = File::open("example.txt")?;
    
    let response = client
        .post("https://httpbin.org/post")
        .body(file)
        .send()?;
    
    println!("Response: {}", response.status());
    
    Ok(())
}

Multipart Form Upload

use reqwest::blocking::Client;
use reqwest::multipart;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let form = multipart::Form::new()
        .text("name", "Alice")
        .file("document", "example.txt")?;
    
    let response = client
        .post("https://httpbin.org/post")
        .multipart(form)
        .send()?;
    
    println!("Response: {}", response.status());
    
    Ok(())
}

Cookies

use reqwest::blocking::Client;
use reqwest::cookie::Jar;
use std::sync::Arc;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cookie_store = Arc::new(Jar::default());
    
    let client = Client::builder()
        .cookie_provider(cookie_store)
        .build()?;
    
    // First request sets cookie
    let resp1 = client.get("https://httpbin.org/cookies/set?session=abc123").send()?;
    println!("Set cookies: {:?}", resp1.cookies().collect::<Vec<_>>());
    
    // Second request sends cookie
    let resp2 = client.get("https://httpbin.org/cookies").send()?;
    println!("Response: {}", resp2.text()?);
    
    Ok(())
}

Redirect Handling

use reqwest::blocking::Client;
use reqwest::redirect;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Follow up to 10 redirects (default)
    let client = Client::builder()
        .redirect(redirect::Policy::limited(10))
        .build()?;
    
    // Or disable redirects
    let no_redirect_client = Client::builder()
        .redirect(redirect::Policy::none())
        .build()?;
    
    let response = client.get("https://httpbin.org/redirect/2").send()?;
    println!("Final URL: {}", response.url());
    
    Ok(())
}

Basic Auth

use reqwest::blocking::Client;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let response = client
        .get("https://httpbin.org/basic-auth/user/pass")
        .basic_auth("user", Some("pass"))
        .send()?;
    
    println!("Status: {}", response.status());
    
    Ok(())
}

Bearer Token Auth

use reqwest::blocking::Client;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let response = client
        .get("https://httpbin.org/bearer")
        .bearer_auth("my-token")
        .send()?;
    
    println!("Status: {}", response.status());
    
    Ok(())
}

HTTPS with Custom Certificates

use reqwest::blocking::Client;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Accept invalid certificates (for testing only!)
    let client = Client::builder()
        .danger_accept_invalid_certs(true)
        .build()?;
    
    // For production, add custom root certificates:
    // let cert = reqwest::Certificate::from_pem(include_bytes!("cert.pem"))?;
    // let client = Client::builder()
    //     .add_root_certificate(cert)
    //     .build()?;
    
    let response = client.get("https://example.com").send()?;
    println!("Status: {}", response.status());
    
    Ok(())
}

Proxy Configuration

use reqwest::blocking::Client;
use reqwest::Proxy;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let proxy = Proxy::all("http://proxy.example.com:8080")?;
    
    let client = Client::builder()
        .proxy(proxy)
        .build()?;
    
    let response = client.get("https://httpbin.org/ip").send()?;
    println!("Response: {}", response.text()?);
    
    Ok(())
}

Async with Tokio

use reqwest::Client;
use tokio;
 
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    // Make concurrent requests
    let urls = vec![
        "https://httpbin.org/get",
        "https://httpbin.org/ip",
        "https://httpbin.org/headers",
    ];
    
    let handles: Vec<_> = urls.into_iter()
        .map(|url| {
            let client = client.clone();
            tokio::spawn(async move {
                client.get(url).send().await?.text().await
            })
        })
        .collect();
    
    for handle in handles {
        let result = handle.await??;
        println!("Response: {} bytes", result.len());
    }
    
    Ok(())
}

Streaming Response

use reqwest::blocking::Client;
use std::io::Read;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let mut response = client.get("https://httpbin.org/stream/5").send()?;
    
    let mut buffer = [0u8; 1024];
    
    loop {
        let bytes_read = response.read(&mut buffer)?;
        if bytes_read == 0 {
            break;
        }
        println!("Read {} bytes", bytes_read);
    }
    
    Ok(())
}

Response Inspection

use reqwest::blocking::Client;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let response = client.get("https://httpbin.org/get").send()?;
    
    // Check status
    println!("Status: {}", response.status());
    println!("Is success: {}", response.status().is_success());
    println!("Is client error: {}", response.status().is_client_error());
    
    // Check headers
    let content_type = response.headers().get("content-type");
    println!("Content-Type: {:?}", content_type);
    
    // Check URL (after redirects)
    println!("Final URL: {}", response.url());
    
    // Version
    println!("HTTP version: {:?}", response.version());
    
    Ok(())
}

Request Builder Methods

use reqwest::blocking::Client;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let response = client
        .get("https://httpbin.org/put")
        .header("X-Custom", "value")
        .header("Accept", "application/json")
        .query(&["key", "value"])
        .body("request body")
        .send()?;
    
    println!("Status: {}", response.status());
    
    Ok(())
}

PUT and DELETE

use reqwest::blocking::Client;
use serde_json::json;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    // PUT request
    let put_response = client
        .put("https://httpbin.org/put")
        .json(&json!({"updated": true}))
        .send()?;
    println!("PUT: {}", put_response.status());
    
    // DELETE request
    let delete_response = client
        .delete("https://httpbin.org/delete")
        .send()?;
    println!("DELETE: {}", delete_response.status());
    
    Ok(())
}

Retry Logic

use reqwest::blocking::Client;
use std::time::Duration;
use std::thread;
 
fn fetch_with_retry(url: &str, max_retries: u32) -> Result<String, Box<dyn std::error::Error>> {
    let client = Client::builder()
        .timeout(Duration::from_secs(5))
        .build()?;
    
    for attempt in 0..max_retries {
        match client.get(url).send() {
            Ok(response) if response.status().is_success() => {
                return Ok(response.text()?);
            }
            Ok(response) => {
                eprintln!("Attempt {}: HTTP {}", attempt + 1, response.status());
            }
            Err(e) => {
                eprintln!("Attempt {}: Error {}", attempt + 1, e);
            }
        }
        
        if attempt < max_retries - 1 {
            thread::sleep(Duration::from_millis(100 * (attempt + 1)));
        }
    }
    
    Err("Max retries exceeded".into())
}
 
fn main() {
    match fetch_with_retry("https://httpbin.org/get", 3) {
        Ok(body) => println!("Success: {} bytes", body.len()),
        Err(e) => eprintln!("Failed: {}", e),
    }
}

User Agent

use reqwest::blocking::Client;
 
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::builder()
        .user_agent("MyApp/1.0 (contact@example.com)")
        .build()?;
    
    let response = client.get("https://httpbin.org/user-agent").send()?;
    println!("Response: {}", response.text()?);
    
    Ok(())
}

Summary

Reqwest Key Imports:

// Blocking API
use reqwest::blocking::{Client, get};
 
// Async API
use reqwest::Client;
 
// Common
use reqwest::{Method, StatusCode, header};

HTTP Methods:

Method Client Method
GET client.get(url)
POST client.post(url)
PUT client.put(url)
DELETE client.delete(url)
PATCH client.patch(url)
HEAD client.head(url)

Response Body Methods:

Method Returns
.text() String
.json() Deserialized type
.bytes() Vec<u8>
.copy_to(writer) Writes to impl Write

Common Headers:

use reqwest::header::*;
 
USER_AGENT, CONTENT_TYPE, AUTHORIZATION, ACCEPT,
CACHE_CONTROL, COOKIE, SET_COOKIE

Client Configuration:

let client = Client::builder()
    .timeout(Duration::from_secs(10))
    .connect_timeout(Duration::from_secs(5))
    .user_agent("MyApp/1.0")
    .default_headers(headers)
    .redirect(redirect::Policy::limited(10))
    .build()?;

Key Points:

  • Use blocking API for simple scripts
  • Use async API for concurrent requests
  • Reuse Client for multiple requests
  • reqwest::get() is convenient for one-off requests
  • .json() requires serde feature
  • Cookies require enabling cookies feature
  • HTTPS is enabled by default
  • Handle errors with ? operator
  • Check .status().is_success() before processing