缓存

提供缓存功能的中间件.

Cache 中间件可以对 Response 中的 StatusCode, Headers, Body 提供缓存功能. 对于已经缓存的内容, 当下次处理请求时, Cache 中间件会直接把缓存在内存中的内容发送给客户端.

注意, 此插件不会缓存 BodyResBody::StreamResponse. 如果应用到了这一类型的 Response, Cache 不会处理这些请求, 也不会引起错误.

主要功能

  • CacheIssuer 提供了对分配的缓存键值的抽象. RequestIssuer 是它的一个实现, 可以定义依据请求的 URL 的哪些部分以及请求的 Method 生成缓存的键. 你也可以定义你自己的缓存键生成的逻辑. 缓存的键不一定是字符串类型, 任何满足 Hash + Eq + Send + Sync + 'static 约束的类型都可以作为键.

  • CacheStore 提供对数据的存取操作. MokaStore 是内置的基于 moka 的一个内存的缓存实现. 你也可以定义自己的实现方式.

  • Cache 是实现了 Handler 的结构体, 内部还有一个 skipper 字段, 可以指定跳过某些不需要缓存的请求. 默认情况下, 会使用 MethodSkipper 跳过除了 Method::GET 以外的所有请求.

    内部实现示例代码:

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

从其他框架快速迁移

如果你之前使用过其他框架的缓存机制,下面的概念映射将帮助你更快适应Salvo的缓存实现:

Rust框架迁移指南

  • 从Actix-web迁移: Actix-web中的actix-web-cache等插件通常需要单独引入,而Salvo的缓存是核心库的一部分。

    // Actix-web 缓存示例
    use actix_web_cache::Cache;
    App::new().wrap(Cache::new().ttl(30))
    
    // Salvo 对应实现
    use salvo::prelude::*;
    Router::new().hoop(Cache::new(MokaStore::new(100), RequestIssuer::new()))

其他语言框架迁移指南

  • 从Go/Gin迁移: Gin使用中间件模式,Salvo也采用类似的方式:

    // Gin 缓存示例
    store := persist.NewMemoryStore(time.Second * 60)
    router.Use(cache.CachePage(store, time.Second * 30))
    // Salvo 对应实现
    let store = MokaStore::new(100).with_ttl(Duration::from_secs(30));
    router.hoop(Cache::new(store, RequestIssuer::new()))
  • 从Spring Boot迁移: Spring Boot的声明式缓存需要转换为Salvo的显式中间件配置:

    // Spring Boot
    @Cacheable(value = "books", key = "#isbn")
    public Book findBook(ISBN isbn) { ... }
    // Salvo 对应实现 - 在路由级别应用缓存
    let custom_issuer = YourCustomIssuer::new(); // 实现CacheIssuer接口
    Router::with_path("books").hoop(Cache::new(MokaStore::new(100), custom_issuer))
  • 从Express.js迁移: Express的缓存中间件与Salvo概念上类似,但语法不同:

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

在从其他框架迁移时,需要注意Salvo缓存的几个关键概念:

  1. 缓存键生成 - 通过CacheIssuer接口控制
  2. 缓存存储 - 通过CacheStore接口实现
  3. 缓存跳过逻辑 - 通过skipper机制自定义

默认情况下,Salvo仅缓存GET请求,这与多数框架的默认行为一致。

示例代码

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