Cache

Middleware that provides caching functionality.

The Cache middleware can cache the StatusCode, Headers, and Body of a Response. For content that has already been cached, the Cache middleware will directly send the cached content from memory to the client when processing subsequent requests.

Note: This plugin does not cache Response objects whose Body is ResBody::Stream. If applied to such a Response, Cache will not process these requests, and no error will occur.

Main Features

  • CacheIssuer provides an abstraction for generating cache keys. RequestIssuer is one of its implementations, allowing you to define which parts of the request URL and the request Method should be used to generate the cache key. You can also define your own cache key generation logic. The cache key does not necessarily have to be a string; any type that satisfies the Hash + Eq + Send + Sync + 'static constraints can be used as a key.

  • CacheStore provides operations for storing and retrieving data. MokaStore is a built-in memory-based cache implementation based on moka. You can also define your own implementation.

  • Cache is a struct that implements Handler. It also contains an internal skipper field, which can be used to specify requests that should skip caching. By default, it uses MethodSkipper to skip all requests except those with Method::GET.

    Example internal implementation code:

    impl<S, I> Cache<S, I> {
      pub fn new(store: S, issuer: I) -> Self {
          let skipper = MethodSkipper::new().skip_all().skip_get(false);
          Cache {
              store,
              issuer,
              skipper: Box::new(skipper),
          }
      }
    }

Quick Migration from Other Frameworks

If you have used caching mechanisms in other frameworks, the following conceptual mappings will help you adapt to Salvo's caching implementation more quickly:

Rust Framework Migration Guide

  • Migrating from Actix-web: Plugins like actix-web-cache in Actix-web typically need to be introduced separately, whereas caching in Salvo is part of the core library.

    // Actix-web cache example
    use actix_web_cache::Cache;
    App::new().wrap(Cache::new().ttl(30))
    
    // Corresponding Salvo implementation
    use salvo::prelude::*;
    Router::new().hoop(Cache::new(MokaStore::new(100), RequestIssuer::new()))

Migration Guide for Frameworks in Other Languages

  • Migrating from Go/Gin: Gin uses a middleware pattern, which Salvo also adopts in a similar manner:

    // Gin cache example
    store := persist.NewMemoryStore(time.Second * 60)
    router.Use(cache.CachePage(store, time.Second * 30))
    // Corresponding Salvo implementation
    let store = MokaStore::new(100).with_ttl(Duration::from_secs(30));
    router.hoop(Cache::new(store, RequestIssuer::new()))
  • Migrating from Spring Boot: Spring Boot's declarative caching needs to be converted to Salvo's explicit middleware configuration:

    // Spring Boot
    @Cacheable(value = "books", key = "#isbn")
    public Book findBook(ISBN isbn) { ... }
    // Corresponding Salvo implementation - applying cache at the route level
    let custom_issuer = YourCustomIssuer::new(); // Implement the CacheIssuer interface
    Router::with_path("books").hoop(Cache::new(MokaStore::new(100), custom_issuer))
  • Migrating from Express.js: Express's caching middleware is conceptually similar to Salvo's, but the syntax differs:

    // Express.js
    const apicache = require('apicache');
    app.use(apicache.middleware('5 minutes'));
    
    // Corresponding Salvo implementation
    let store = MokaStore::new(100).with_ttl(Duration::from_secs(300));
    router.hoop(Cache::new(store, RequestIssuer::new()))

When migrating from other frameworks, pay attention to several key concepts of Salvo's caching:

  1. Cache Key Generation - Controlled via the CacheIssuer interface.
  2. Cache Storage - Implemented via the CacheStore interface.
  3. Cache Skipping Logic - Customized via the skipper mechanism.

By default, Salvo only caches GET requests, which aligns with the default behavior of most frameworks.

Example Code

main.rs
Cargo.toml
use std::time::Duration;

use salvo::cache::{Cache, MokaStore, RequestIssuer};
use salvo::prelude::*;
use salvo::writing::Text;
use time::OffsetDateTime;

// Handler for serving the home page with HTML content
#[handler]
async fn home() -> Text<&'static str> {
    Text::Html(HOME_HTML)
}

// Handler for short-lived cached response (5 seconds)
#[handler]
async fn short() -> String {
    format!(
        "Hello World, my birth time is {}",
        OffsetDateTime::now_utc()
    )
}

// Handler for long-lived cached response (1 minute)
#[handler]
async fn long() -> String {
    format!(
        "Hello World, my birth time is {}",
        OffsetDateTime::now_utc()
    )
}

#[tokio::main]
async fn main() {
    // Initialize logging system
    tracing_subscriber::fmt().init();

    // Create cache middleware for short-lived responses (5 seconds TTL)
    let short_cache = Cache::new(
        MokaStore::builder()
            .time_to_live(Duration::from_secs(5))
            .build(),
        RequestIssuer::default(),
    );

    // Create cache middleware for long-lived responses (60 seconds TTL)
    let long_cache = Cache::new(
        MokaStore::builder()
            .time_to_live(Duration::from_secs(60))
            .build(),
        RequestIssuer::default(),
    );

    // Set up router with three endpoints:
    // - / : Home page
    // - /short : Response cached for 5 seconds
    // - /long : Response cached for 1 minute
    let router = Router::new()
        .get(home)
        .push(Router::with_path("short").hoop(short_cache).get(short))
        .push(Router::with_path("long").hoop(long_cache).get(long));

    // Bind server to port 8698 and start serving
    let acceptor = TcpListener::new("0.0.0.0:8698").bind().await;
    Server::new(acceptor).serve(router).await;
}

// HTML template for the home page with links to cached endpoints
static HOME_HTML: &str = r#"
<!DOCTYPE html>
<html>
    <head>
        <title>Cache Example</title>
    </head>
    <body>
        <h2>Cache Example</h2>
        <p>
            This examples shows how to use cache middleware.
        </p>
        <p>
            <a href="/short" target="_blank">Cache 5 seconds</a>
        </p>
        <p>
            <a href="/long" target="_blank">Cache 1 minute</a>
        </p>
    </body>
</html>
"#;