Cache

Middleware che fornisce funzionalità di memorizzazione nella cache.

Il middleware Cache può memorizzare nella cache lo StatusCode, gli Headers e il Body di una Response. Per i contenuti già memorizzati, il middleware Cache invierà direttamente il contenuto in cache dalla memoria al client durante le richieste successive.

Nota: Questo plugin non memorizza nella cache gli oggetti Response il cui Body è ResBody::Stream. Se applicato a una Response di questo tipo, Cache non elaborerà queste richieste e non si verificherà alcun errore.

Caratteristiche Principali

  • CacheIssuer fornisce un'astrazione per la generazione delle chiavi di cache. RequestIssuer è una delle sue implementazioni, che consente di definire quali parti dell'URL della richiesta e del Method della richiesta devono essere utilizzate per generare la chiave di cache. È possibile definire anche una propria logica di generazione delle chiavi di cache. La chiave di cache non deve necessariamente essere una stringa; qualsiasi tipo che soddisfi i vincoli Hash + Eq + Send + Sync + 'static può essere utilizzato come chiave.

  • CacheStore fornisce operazioni per l'archiviazione e il recupero dei dati. MokaStore è un'implementazione di cache in memoria integrata basata su moka. È possibile definire anche una propria implementazione.

  • Cache è una struct che implementa Handler. Contiene anche un campo interno skipper, che può essere utilizzato per specificare le richieste che devono saltare la cache. Per impostazione predefinita, utilizza MethodSkipper per saltare tutte le richieste tranne quelle con Method::GET.

    Codice di esempio dell'implementazione interna:

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

Migrazione Rapida da Altri Framework

Se hai utilizzato meccanismi di cache in altri framework, le seguenti corrispondenze concettuali ti aiuteranno ad adattarti più rapidamente all'implementazione della cache di Salvo:

Guida alla Migrazione da Framework Rust

  • Migrazione da Actix-web: Plugin come actix-web-cache in Actix-web di solito devono essere introdotti separatamente, mentre la cache in Salvo fa parte della libreria core.

    // Esempio di cache in Actix-web
    use actix_web_cache::Cache;
    App::new().wrap(Cache::new().ttl(30))
    
    // Implementazione corrispondente in Salvo
    use salvo::prelude::*;
    Router::new().hoop(Cache::new(MokaStore::new(100), RequestIssuer::new()))

Guida alla Migrazione da Framework in Altri Linguaggi

  • Migrazione da Go/Gin: Gin utilizza un pattern middleware, che Salvo adotta in modo simile:

    // Esempio di cache in Gin
    store := persist.NewMemoryStore(time.Second * 60)
    router.Use(cache.CachePage(store, time.Second * 30))
    // Implementazione corrispondente in Salvo
    let store = MokaStore::new(100).with_ttl(Duration::from_secs(30));
    router.hoop(Cache::new(store, RequestIssuer::new()))
  • Migrazione da Spring Boot: La cache dichiarativa di Spring Boot deve essere convertita nella configurazione esplicita del middleware di Salvo:

    // Spring Boot
    @Cacheable(value = "books", key = "#isbn")
    public Book findBook(ISBN isbn) { ... }
    // Implementazione corrispondente in Salvo - applicazione della cache a livello di route
    let custom_issuer = YourCustomIssuer::new(); // Implementa l'interfaccia CacheIssuer
    Router::with_path("books").hoop(Cache::new(MokaStore::new(100), custom_issuer))
  • Migrazione da Express.js: Il middleware di cache di Express è concettualmente simile a quello di Salvo, ma la sintassi è diversa:

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

Durante la migrazione da altri framework, presta attenzione a diversi concetti chiave della cache di Salvo:

  1. Generazione della Chiave di Cache - Controllata tramite l'interfaccia CacheIssuer.
  2. Archiviazione della Cache - Implementata tramite l'interfaccia CacheStore.
  3. Logica di Esclusione dalla Cache - Personalizzabile tramite il meccanismo skipper.

Per impostazione predefinita, Salvo memorizza nella cache solo le richieste GET, in linea con il comportamento predefinito della maggior parte dei framework.

Codice di Esempio

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>
"#;