Cache

Middleware offrant une fonctionnalité 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, le middleware Cache enverra directement le contenu en mémoire au client lors du traitement des requêtes suivantes.

Note : Ce plugin ne met pas en cache les objets Response dont le Body est ResBody::Stream. S'il est appliqué à une telle Response, Cache ne traitera pas ces requêtes et aucune erreur ne se produira.

Fonctionnalités principales

  • CacheIssuer fournit une abstraction pour générer des clés de cache. RequestIssuer est l'une de ses implémentations, vous permettant de définir quelles parties de l'URL de la requête et de la Méthode de requête doivent être utilisées pour générer la clé de cache. Vous pouvez également définir votre propre logique de génération de clé de cache. La clé de cache ne doit pas nécessairement être une chaîne de caractères ; tout type satisfaisant les contraintes Hash + Eq + Send + Sync + 'static peut être utilisé comme clé.

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

  • Cache est une structure qui implémente Handler. Elle contient également un champ interne skipper, qui peut être utilisé pour spécifier les requêtes qui doivent ignorer la mise en cache. Par défaut, il utilise MethodSkipper pour ignorer toutes les requêtes sauf celles avec 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 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 de Salvo :

Guide de migration pour les frameworks Rust

  • Migration depuis Actix-web : Les plugins comme actix-web-cache dans Actix-web doivent généralement être introduits séparément, tandis que la mise en cache dans Salvo fait partie de la bibliothèque principale.

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

Guide de migration pour les frameworks dans d'autres langages

  • Migration depuis Go/Gin : Gin utilise un modèle de middleware, que Salvo adopte également de manière similaire :

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

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

    // Express.js
    const apicache = require('apicache');
    app.use(apicache.middleware('5 minutes'));
    
    // Implémentation correspondante 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, prêtez attention à plusieurs concepts clés de la mise en cache de Salvo :

  1. Génération de clé 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
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>
"#;