Cache

Fournit un middleware de mise en cache.

Le middleware Cache peut mettre en cache le StatusCode, les Headers et le Body d'une Response. Pour le contenu déjà mis en cache, lors du traitement d'une requête ultérieure, le middleware Cache enverra directement le contenu en mémoire cache au client.

Remarque : ce plugin ne met pas en cache les Response dont le Body est de type ResBody::Stream. Si appliqué à ce type de Response, Cache ne traitera pas ces requêtes et ne générera pas d'erreur.

Fonctionnalités principales

  • CacheIssuer fournit une abstraction pour la génération des clés de cache. RequestIssuer en est une implémentation qui permet de définir quelles parties de l'URL de la requête ainsi que la Method seront utilisées pour générer la clé de cache. Vous pouvez également définir votre propre logique de génération de clés. Les clés de cache ne sont pas nécessairement des chaînes de caractères - tout type satisfaisant les contraintes Hash + Eq + Send + Sync + 'static peut servir de clé.

  • CacheStore fournit les opérations de stockage et de récupération des données. MokaStore est une implémentation intégrée de cache mémoire basée sur moka. Vous pouvez également définir votre propre implémentation.

  • Cache est une structure implémentant Handler, contenant également un champ skipper permettant d'ignorer certaines requêtes qu'on ne souhaite pas mettre en cache. Par défaut, MethodSkipper est utilisé pour ignorer toutes les requêtes autres que Method::GET.

    Exemple de code d'implémentation interne :

    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),
          }
      }
    }

Migration rapide depuis d'autres frameworks

Si vous avez déjà utilisé des mécanismes de cache dans d'autres frameworks, les correspondances conceptuelles suivantes vous aideront à vous adapter plus rapidement à l'implémentation du cache dans Salvo :

Guide de migration depuis Rust

  • Depuis Actix-web : Les plugins comme actix-web-cache dans Actix-web nécessitent généralement une importation séparée, tandis que le cache de Salvo fait partie de la bibliothèque principale.

    // Exemple de cache dans Actix-web
    use actix_web_cache::Cache;
    App::new().wrap(Cache::new().ttl(30))
    
    // Implémentation équivalente dans Salvo
    use salvo::prelude::*;
    Router::new().hoop(Cache::new(MokaStore::new(100), RequestIssuer::new()))

Guide de migration depuis d'autres langages

  • Depuis Go/Gin : Gin utilise un modèle de middleware, tout comme Salvo :

    // Exemple de cache dans Gin
    store := persist.NewMemoryStore(time.Second * 60)
    router.Use(cache.CachePage(store, time.Second * 30))
    // Implémentation équivalente dans Salvo
    let store = MokaStore::new(100).with_ttl(Duration::from_secs(30));
    router.hoop(Cache::new(store, RequestIssuer::new()))
  • Depuis Spring Boot : Le cache déclaratif de Spring Boot doit être converti en configuration explicite de middleware dans Salvo :

    // Spring Boot
    @Cacheable(value = "books", key = "#isbn")
    public Book findBook(ISBN isbn) { ... }
    // Implémentation équivalente dans Salvo - application du cache au niveau des routes
    let custom_issuer = YourCustomIssuer::new(); // implémente l'interface CacheIssuer
    Router::with_path("books").hoop(Cache::new(MokaStore::new(100), custom_issuer))
  • Depuis Express.js : Le middleware de cache d'Express est conceptuellement similaire à Salvo, mais la syntaxe diffère :

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

Lors de la migration depuis d'autres frameworks, notez ces concepts clés du cache dans Salvo :

  1. Génération des clés de cache - contrôlée via l'interface CacheIssuer
  2. Stockage du cache - implémenté via l'interface CacheStore
  3. Logique d'ignorance du cache - personnalisable via le mécanisme skipper

Par défaut, Salvo ne met en cache que les requêtes GET, ce qui correspond au comportement par défaut de la plupart des frameworks.

Exemple de code

main.rs
Cargo.toml
cache-simple/src/main.rs
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 5800 and start serving
    let acceptor = TcpListener::new("0.0.0.0:5800").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>
"#;