Caché

Middleware que proporciona funcionalidad de almacenamiento en caché.

El middleware Cache puede almacenar en caché el StatusCode, los Headers y el Body de una Response. Para el contenido que ya ha sido almacenado en caché, el middleware Cache enviará directamente el contenido almacenado en memoria al cliente al procesar solicitudes posteriores.

Nota: Este complemento no almacena en caché objetos Response cuyo Body sea ResBody::Stream. Si se aplica a dicha Response, Cache no procesará estas solicitudes y no se producirá ningún error.

Características Principales

  • CacheIssuer proporciona una abstracción para generar claves de caché. RequestIssuer es una de sus implementaciones, permitiéndote definir qué partes de la URL de la solicitud y del Method de la solicitud deben usarse para generar la clave de caché. También puedes definir tu propia lógica de generación de claves de caché. La clave de caché no necesariamente tiene que ser una cadena; cualquier tipo que cumpla con las restricciones Hash + Eq + Send + Sync + 'static puede usarse como clave.

  • CacheStore proporciona operaciones para almacenar y recuperar datos. MokaStore es una implementación de caché en memoria incorporada basada en moka. También puedes definir tu propia implementación.

  • Cache es una estructura que implementa Handler. También contiene un campo interno skipper, que puede usarse para especificar solicitudes que deben omitir el almacenamiento en caché. Por defecto, utiliza MethodSkipper para omitir todas las solicitudes excepto aquellas con Method::GET.

    Código de ejemplo de implementación 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),
          }
      }
    }

Migración Rápida desde Otros Frameworks

Si has utilizado mecanismos de caché en otros frameworks, las siguientes correspondencias conceptuales te ayudarán a adaptarte más rápidamente a la implementación de caché de Salvo:

Guía de Migración desde Frameworks Rust

  • Migrando desde Actix-web: Complementos como actix-web-cache en Actix-web normalmente deben introducirse por separado, mientras que el almacenamiento en caché en Salvo es parte de la biblioteca principal.

    // Ejemplo de caché en Actix-web
    use actix_web_cache::Cache;
    App::new().wrap(Cache::new().ttl(30))
    
    // Implementación correspondiente en Salvo
    use salvo::prelude::*;
    Router::new().hoop(Cache::new(MokaStore::new(100), RequestIssuer::new()))

Guía de Migración para Frameworks en Otros Lenguajes

  • Migrando desde Go/Gin: Gin utiliza un patrón de middleware, que Salvo también adopta de manera similar:

    // Ejemplo de caché en Gin
    store := persist.NewMemoryStore(time.Second * 60)
    router.Use(cache.CachePage(store, time.Second * 30))
    // Implementación correspondiente en Salvo
    let store = MokaStore::new(100).with_ttl(Duration::from_secs(30));
    router.hoop(Cache::new(store, RequestIssuer::new()))
  • Migrando desde Spring Boot: El almacenamiento en caché declarativo de Spring Boot debe convertirse a la configuración explícita de middleware de Salvo:

    // Spring Boot
    @Cacheable(value = "books", key = "#isbn")
    public Book findBook(ISBN isbn) { ... }
    // Implementación correspondiente en Salvo - aplicando caché a nivel de ruta
    let custom_issuer = YourCustomIssuer::new(); // Implementa la interfaz CacheIssuer
    Router::with_path("books").hoop(Cache::new(MokaStore::new(100), custom_issuer))
  • Migrando desde Express.js: El middleware de caché de Express es conceptualmente similar al de Salvo, pero la sintaxis difiere:

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

Al migrar desde otros frameworks, presta atención a varios conceptos clave del almacenamiento en caché de Salvo:

  1. Generación de Claves de Caché - Controlada a través de la interfaz CacheIssuer.
  2. Almacenamiento en Caché - Implementado a través de la interfaz CacheStore.
  3. Lógica de Omisión de Caché - Personalizada a través del mecanismo skipper.

Por defecto, Salvo solo almacena en caché solicitudes GET, lo que se alinea con el comportamiento predeterminado de la mayoría de los frameworks.

Código de Ejemplo

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