ureq

The http-cache-ureq crate provides HTTP caching for the ureq HTTP client.

Since ureq is a synchronous HTTP client, this implementation uses the smol async runtime to integrate with the async http-cache system. The caching wrapper preserves ureq's synchronous interface while providing async caching capabilities internally.

Features

  • json - Enables JSON request/response support via send_json() and into_json() methods (requires serde_json)
  • manager-cacache - Enable cacache cache manager (default)
  • manager-moka - Enable moka cache manager

Basic Usage

Add the dependency to your Cargo.toml:

[dependencies]
http-cache-ureq = "1.0.0-alpha.1"

Use the CachedAgent builder to create a cached HTTP client:

use http_cache_ureq::{CachedAgent, CACacheManager, CacheMode};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    smol::block_on(async {
        let agent = CachedAgent::builder()
            .cache_manager(CACacheManager::new("./cache".into(), true))
            .cache_mode(CacheMode::Default)
            .build()?;
        
        // This request will be cached according to response headers
        let response = agent.get("https://httpbin.org/cache/60").call().await?;
        println!("Status: {}", response.status());
        println!("Cached: {}", response.is_cached());
        println!("Response: {}", response.into_string()?);
        
        // Subsequent identical requests may be served from cache
        let cached_response = agent.get("https://httpbin.org/cache/60").call().await?;
        println!("Cached status: {}", cached_response.status());
        println!("Is cached: {}", cached_response.is_cached());
        println!("Cached response: {}", cached_response.into_string()?);
        
        Ok(())
    })
}

JSON Support

Enable the json feature to send and parse JSON data:

[dependencies]
http-cache-ureq = { version = "1.0.0-alpha.1", features = ["json"] }
use http_cache_ureq::{CachedAgent, CACacheManager, CacheMode};
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    smol::block_on(async {
        let agent = CachedAgent::builder()
            .cache_manager(CACacheManager::new("./cache".into(), true))
            .cache_mode(CacheMode::Default)
            .build()?;
        
        // Send JSON data
        let response = agent.post("https://httpbin.org/post")
            .send_json(json!({"key": "value"}))
            .await?;
        
        // Parse JSON response
        let json: serde_json::Value = response.into_json()?;
        println!("Response: {}", json);
        
        Ok(())
    })
}

Cache Modes

Control caching behavior with different modes:

use http_cache_ureq::{CachedAgent, CACacheManager, CacheMode};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    smol::block_on(async {
        let agent = CachedAgent::builder()
            .cache_manager(CACacheManager::new("./cache".into(), true))
            .cache_mode(CacheMode::ForceCache) // Cache everything, ignore headers
            .build()?;
        
        // This will be cached even if headers say not to cache
        let response = agent.get("https://httpbin.org/uuid").call().await?;
        println!("Response: {}", response.into_string()?);
        
        Ok(())
    })
}

Custom ureq Configuration

Preserve your ureq agent configuration while adding caching:

use http_cache_ureq::{CachedAgent, CACacheManager, CacheMode};
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    smol::block_on(async {
        // Create custom ureq configuration
        let config = ureq::config::Config::builder()
            .timeout_global(Some(Duration::from_secs(30)))
            .user_agent("MyApp/1.0")
            .build();
        
        let agent = CachedAgent::builder()
            .agent_config(config)
            .cache_manager(CACacheManager::new("./cache".into(), true))
            .cache_mode(CacheMode::Default)
            .build()?;
        
        let response = agent.get("https://httpbin.org/cache/60").call().await?;
        println!("Response: {}", response.into_string()?);
        
        Ok(())
    })
}

In-Memory Caching

Use the Moka in-memory cache:

[dependencies]
http-cache-ureq = { version = "1.0.0-alpha.1", features = ["manager-moka"] }
use http_cache_ureq::{CachedAgent, MokaManager, MokaCache, CacheMode};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    smol::block_on(async {
        let agent = CachedAgent::builder()
            .cache_manager(MokaManager::new(MokaCache::new(1000))) // Max 1000 entries
            .cache_mode(CacheMode::Default)
            .build()?;
            
        let response = agent.get("https://httpbin.org/cache/60").call().await?;
        println!("Response: {}", response.into_string()?);
        
        Ok(())
    })
}

Maximum TTL Control

Control cache expiration times, particularly useful with IgnoreRules mode:

use http_cache_ureq::{CachedAgent, CACacheManager, CacheMode, HttpCacheOptions};
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    smol::block_on(async {
        let agent = CachedAgent::builder()
            .cache_manager(CACacheManager::new("./cache".into(), true))
            .cache_mode(CacheMode::IgnoreRules) // Ignore server cache headers
            .cache_options(HttpCacheOptions {
                max_ttl: Some(Duration::from_secs(300)), // Limit cache to 5 minutes maximum
                ..Default::default()
            })
            .build()?;
        
        // This will be cached for max 5 minutes even if server says cache longer
        let response = agent.get("https://httpbin.org/cache/3600").call().await?;
        println!("Response: {}", response.into_string()?);
        
        Ok(())
    })
}

Implementation Notes

  • The wrapper preserves ureq's synchronous interface while using async caching internally
  • The http_status_as_error setting is automatically disabled to ensure proper cache operation
  • All HTTP methods are supported (GET, POST, PUT, DELETE, HEAD, etc.)
  • Cache invalidation occurs for non-GET/HEAD requests to the same resource
  • Only GET and HEAD requests are cached by default
  • max_ttl provides expiration control when using CacheMode::IgnoreRules