Almacenamiento en Caché
Middleware que proporciona funcionalidad de caché.
El middleware Cache puede almacenar en caché el StatusCode
, Headers
y Body
de una Response
. Para contenido ya almacenado en caché, en solicitudes posteriores, el middleware enviará directamente el contenido almacenado en memoria al cliente.
Nota: Este complemento no almacena en caché Response
cuyo Body
sea ResBody::Stream
. Si se aplica a este tipo de Response
, Cache no procesará estas solicitudes ni generará errores.
Funcionalidades Principales
-
CacheIssuer
proporciona una abstracción para generar claves de caché. RequestIssuer
es una implementación que permite definir claves basadas en partes de la URL y el Method
de la solicitud. También puedes definir tu propia lógica de generación de claves. Las claves no necesariamente deben ser de tipo cadena; cualquier tipo que cumpla con los límites Hash + Eq + Send + Sync + 'static
puede ser utilizado como clave.
-
CacheStore
proporciona operaciones de lectura y escritura de datos. MokaStore
es una implementación integrada de caché en memoria basada en moka
. También puedes definir tu propia implementación.
-
Cache
es una estructura que implementa Handler
y contiene un campo skipper
para especificar qué solicitudes no deben almacenarse en caché. Por defecto, se utiliza MethodSkipper
para omitir todas las solicitudes excepto Method::GET
.
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, la siguiente guía de mapeo conceptual te ayudará a adaptarte más rápido a la implementación de Salvo:
Guía de Migración desde Frameworks Rust
Guía de Migración desde Frameworks de Otros Lenguajes
-
Desde Go/Gin: Gin utiliza un patrón de middleware similar al de Salvo:
// Ejemplo en Gin
store := persist.NewMemoryStore(time.Second * 60)
router.Use(cache.CachePage(store, time.Second * 30))
// Implementación equivalente en Salvo
let store = MokaStore::new(100).with_ttl(Duration::from_secs(30));
router.hoop(Cache::new(store, RequestIssuer::new()))
-
Desde Spring Boot: La caché declarativa de Spring Boot debe convertirse en configuración explícita de middleware en Salvo:
// Spring Boot
@Cacheable(value = "books", key = "#isbn")
public Book findBook(ISBN isbn) { ... }
// Implementación equivalente en Salvo (a nivel de ruta)
let custom_issuer = YourCustomIssuer::new(); // Implementa CacheIssuer
Router::with_path("books").hoop(Cache::new(MokaStore::new(100), custom_issuer))
-
Desde Express.js: El middleware de caché de Express es conceptualmente similar, pero con sintaxis diferente:
// Express.js
const apicache = require('apicache');
app.use(apicache.middleware('5 minutes'));
// Implementación equivalente 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, ten en cuenta estos conceptos clave de Salvo:
- Generación de claves de caché: Controlada por la interfaz
CacheIssuer
.
- Almacenamiento en caché: Implementado mediante la interfaz
CacheStore
.
- Lógica de omisión: Personalizable mediante el mecanismo
skipper
.
Por defecto, Salvo solo almacena en caché solicitudes GET, alineándose con el comportamiento predeterminado de la mayoría de los frameworks.
Ejemplo de Código
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>
"#;