reqwest
The http-cache-reqwest crate provides a Middleware implementation for the reqwest HTTP client. It accomplishes this by utilizing reqwest_middleware.
Getting Started
cargo add http-cache-reqwest
Features
manager-redb: (default) Enables theRedbManagerbackend cache manager.manager-cacache: Enables theCACacheManagerbackend cache manager.manager-moka: Enables theMokaManagerbackend cache manager.manager-foyer: Enables theFoyerManagerbackend cache manager.streaming: Enables streaming cache support for memory-efficient handling of large response bodies.rate-limiting: Enables cache-aware rate limiting functionality.url-ada: Enables ada-url for URL parsing.
Usage
In the following example we will construct our client using the builder provided by reqwest_middleware with our cache struct from http-cache-reqwest. This example will use the default mode, default redb manager, and default http cache options.
After constructing our client, we will make a request to the MDN Caching Docs which should result in an object stored in cache on disk.
use reqwest::Client; use reqwest_middleware::ClientBuilder; use http_cache_reqwest::{Cache, CacheMode, RedbManager, HttpCache, HttpCacheOptions}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let client = ClientBuilder::new(Client::new()) .with(Cache(HttpCache { mode: CacheMode::Default, manager: RedbManager::new("./http-cache.redb")?, options: HttpCacheOptions::default(), })) .build(); client .get("https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching") .send() .await?; Ok(()) }
Streaming Cache Support
For memory-efficient caching of large response bodies, you can use the streaming cache feature. This is particularly useful for handling large files, media content, or API responses without loading the entire response into memory.
To enable streaming cache support, add the streaming feature to your Cargo.toml:
[dependencies]
http-cache-reqwest = { version = "1.0", features = ["streaming"] }
Basic Streaming Example
use http_cache::StreamingManager; use http_cache_reqwest::{StreamingCache, CacheMode}; use reqwest::Client; use reqwest_middleware::ClientBuilder; use futures_util::StreamExt; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { // Create streaming cache manager (disk-backed; uses a temp dir here) let cache_manager = StreamingManager::with_temp_dir(1000).await?; let streaming_cache = StreamingCache::new(cache_manager, CacheMode::Default); // Build client with streaming cache let client = ClientBuilder::new(Client::new()) .with(streaming_cache) .build(); // Make request to large content let response = client .get("https://example.com/large-file.zip") .send() .await?; // Stream the response body let mut stream = response.bytes_stream(); let mut total_bytes = 0; while let Some(chunk) = stream.next().await { let chunk = chunk?; total_bytes += chunk.len(); // Process chunk without loading entire response into memory } println!("Downloaded {total_bytes} bytes"); Ok(()) }
Key Benefits of Streaming Cache
- Memory Efficiency: Cache hits stream from disk in 64KB chunks without loading the full body into memory. On the write path,
StreamingCacheno longer buffers the upstream response — the network stream flows straight into the cache manager, which spools it to disk frame-by-frame (bounded RAM: roughly one frame, regardless of body size). - Performance: Cached responses can be streamed immediately without waiting for complete download
- Scalability: Read-heavy and write-heavy workloads both handle arbitrarily large bodies with bounded memory.
max_body_size(default 100MB) caps which responses get cached, not write-path memory — an oversize response is simply not cached (decline, not error) and still streams through to the caller.
Non-Cloneable Request Handling
The reqwest middleware gracefully handles requests with non-cloneable bodies (such as multipart forms, streaming uploads, and custom body types). When a request cannot be cloned for caching operations, the middleware automatically:
- Bypasses the cache gracefully: The request proceeds normally without caching
- Performs cache maintenance: Still handles cache deletion and busting operations where possible
- Avoids errors: No "Request object is not cloneable" errors are thrown
This ensures that your application continues to work seamlessly even when using complex request body types.
Example with Multipart Forms
use reqwest::Client; use reqwest_middleware::ClientBuilder; use http_cache_reqwest::{Cache, CacheMode, RedbManager, HttpCache, HttpCacheOptions}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let client = ClientBuilder::new(Client::new()) .with(Cache(HttpCache { mode: CacheMode::Default, manager: RedbManager::new("./http-cache.redb")?, options: HttpCacheOptions::default(), })) .build(); // Multipart forms are handled gracefully - no caching errors let form = reqwest::multipart::Form::new() .text("field1", "value1") .file("upload", "/path/to/file.txt").await?; let response = client .post("https://httpbin.org/post") .multipart(form) .send() .await?; println!("Status: {}", response.status()); Ok(()) }
Example with Streaming Bodies
use reqwest::Client; use reqwest_middleware::ClientBuilder; use http_cache_reqwest::{Cache, CacheMode, RedbManager, HttpCache, HttpCacheOptions}; use futures_util::{stream, StreamExt}; use bytes::Bytes; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let client = ClientBuilder::new(Client::new()) .with(Cache(HttpCache { mode: CacheMode::Default, manager: RedbManager::new("./http-cache.redb")?, options: HttpCacheOptions::default(), })) .build(); // Create a streaming body let stream_data = vec!["chunk1", "chunk2", "chunk3"]; let stream = stream::iter(stream_data) .map(|s| Ok::<_, reqwest::Error>(Bytes::from(s))); let body = reqwest::Body::wrap_stream(stream); // Streaming bodies are handled gracefully - no caching errors let response = client .put("https://httpbin.org/put") .body(body) .send() .await?; println!("Status: {}", response.status()); Ok(()) }