#cache #middleware #request #header #prefix #body #ttl #back-end #context

actix-request-reply-cache

A simple request/reply cache middleware for Actix Web with Redis backend

8 releases

new 0.1.7 Apr 25, 2025
0.1.6 Apr 25, 2025

#74 in Caching

Download history

145 downloads per month

MIT license

39KB
624 lines

Actix Request-Reply Cache

Crates.io Documentation MIT licensed

A Redis-backed response caching middleware for Actix Web applications.

Features

  • Redis-backed caching: Store HTTP responses in Redis for fast retrieval
  • Configurable TTL: Set custom expiration times for cached responses
  • Size limits: Control maximum response size to cache
  • Flexible cache control: Use predicate functions to determine what should be cached
  • Cache key customization: Set your own cache key prefix
  • Standard compliant: Respects HTTP Cache-Control headers
  • Minimal performance impact: Fast cache lookup and non-blocking I/O

Installation

Add this to your Cargo.toml:

[dependencies]
actix-request-reply-cache = "0.1.0"

Basic Usage

use actix_web::{web, App, HttpServer};
use actix_request_reply_cache::RedisCacheMiddlewareBuilder;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // Create the cache middleware with default settings
    let cache = RedisCacheMiddlewareBuilder::new("redis://127.0.0.1:6379")
        .build()
        .await;
        
    HttpServer::new(move || {
        App::new()
            .wrap(cache.clone())
            .service(web::resource("/").to(|| async { "Hello world!" }))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Advanced Configuration

use actix_web::{web, App, HttpServer};
use actix_request_reply_cache::RedisCacheMiddlewareBuilder;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // Create a more customized cache middleware
    let cache = RedisCacheMiddlewareBuilder::new("redis://127.0.0.1:6379")
        .ttl(300)  // Cache for 5 minutes
        .max_cacheable_size(512 * 1024)  // Only cache responses <= 512KB
        .cache_prefix("my-api-cache:")  // Custom Redis key prefix
        .cache_if(|ctx| {
            // Only cache GET requests
            if ctx.method != "GET" {
                return false;
            }
            
            // Don't cache if Authorization header is present
            if ctx.headers.contains_key("Authorization") {
                return false;
            }
            
            // Don't cache admin routes
            if ctx.path.starts_with("/admin") {
                return false;
            }
            
            true
        })
        .build()
        .await;
        
    HttpServer::new(move || {
        App::new()
            .wrap(cache.clone())
            .service(web::resource("/api/users").to(get_users))
            .service(web::resource("/api/products").to(get_products))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Cache Decision Context

The cache_if predicate receives a CacheDecisionContext with the following information:

  • method: HTTP method string ("GET", "POST", etc.)
  • path: Request path
  • query_string: URL query parameters
  • headers: HTTP request headers
  • body: Request body as bytes

This allows for fine-grained control over what gets cached.

How It Works

  1. Incoming requests are intercepted by the middleware
  2. If Cache-Control headers specify no-cache/no-store, the request is processed normally
  3. Otherwise, a cache key is computed based on the request method, path, query string and body
  4. If a matching response is found in Redis, it's returned immediately with an X-Cache: HIT header
  5. If no match is found, the request is processed normally, and if the response is successful:
    • The response is serialized and stored in Redis with the configured TTL
    • The original response is returned to the client

License

This project is licensed under the MIT License.

Dependencies

~17–28MB
~494K SLoC