Cache
Middleware for providing caching functionality.
The Cache middleware can cache the StatusCode
, Headers
, and Body
of a Response
. For content that has already been cached, when handling subsequent requests, the Cache middleware will directly send the content stored in memory to the client.
Note that this plugin does not cache Response
objects where the Body
is ResBody::Stream
. If applied to this type of Response
, the Cache middleware will not process these requests, nor will it cause errors.
Main Features
-
CacheIssuer
provides an abstraction for generating cache keys. RequestIssuer
is an implementation that allows defining cache keys based on parts of the request URL and the request Method
. You can also define your own custom logic for generating cache keys. The cache key does not have to be a string; any type satisfying the Hash + Eq + Send + Sync + 'static
constraints can be used as a key.
-
CacheStore
provides operations for storing and retrieving data. MokaStore
is a built-in in-memory cache implementation based on the moka
crate. You can also define your own storage implementation.
-
Cache
is a struct that implements the Handler
trait. It internally contains a skipper
field, which can be used to specify requests that should be skipped for caching. By default, MethodSkipper
is used to skip all requests except those using Method::GET
.
Internal implementation example code:
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),
}
}
}
Quick Migration from Other Frameworks
If you have previously used caching mechanisms from other frameworks, the following concept mapping will help you adapt more quickly to Salvo's cache implementation:
Rust Framework Migration Guide
Other Language Framework Migration Guide
-
Migrating from Go/Gin: Gin uses a middleware pattern, and Salvo adopts a similar approach:
// Gin Cache Example
store := persist.NewMemoryStore(time.Second * 60)
router.Use(cache.CachePage(store, time.Second * 30))
// Salvo Equivalent Implementation
use std::time::Duration;
use salvo::prelude::*;
use salvo::cache::{Cache, MokaStore, RequestIssuer};
let store = MokaStore::new(100).with_ttl(Duration::from_secs(30));
// Assuming 'router' is a Salvo Router instance
// router.hoop(Cache::new(store, RequestIssuer::new()))
-
Migrating from Spring Boot: Spring Boot's declarative caching needs to be converted to Salvo's explicit middleware configuration:
// Spring Boot
@Cacheable(value = "books", key = "#isbn")
public Book findBook(ISBN isbn) { ... }
// Salvo Equivalent Implementation - Applying cache at the route level
use salvo::prelude::*;
use salvo::cache::{Cache, MokaStore, CacheIssuer}; // Import necessary traits/structs
// Define YourCustomIssuer implementing CacheIssuer trait
// struct YourCustomIssuer { /* ... */ }
// impl CacheIssuer for YourCustomIssuer { /* ... */ }
// let custom_issuer = YourCustomIssuer::new(); // Implement CacheIssuer trait
// Router::with_path("books").hoop(Cache::new(MokaStore::new(100), custom_issuer))
-
Migrating from Express.js: Express's caching middleware is conceptually similar to Salvo's, but the syntax differs:
// Express.js
const apicache = require('apicache');
app.use(apicache.middleware('5 minutes'));
// Salvo Equivalent Implementation
use std::time::Duration;
use salvo::prelude::*;
use salvo::cache::{Cache, MokaStore, RequestIssuer};
let store = MokaStore::new(100).with_ttl(Duration::from_secs(300));
// Assuming 'router' is a Salvo Router instance
// router.hoop(Cache::new(store, RequestIssuer::new()))
When migrating from other frameworks, pay attention to several key concepts in Salvo's cache:
- Cache Key Generation - Controlled via the
CacheIssuer
trait
- Cache Storage - Implemented via the
CacheStore
trait
- Cache Skipping Logic - Customized via the
skipper
mechanism
By default, Salvo only caches GET requests, which is consistent with the default behavior of most frameworks.
Example Code
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>
"#;