Crate http_cache_tags

Source
Expand description

Experimental: Alpha Release

This crate is in early development. APIs are unstable and may change without notice. Not recommended for production use yet.

HTTP Cache Tags: Framework Integrations for Actix and Axum

This crate provides seamless integration of HTTP cache tagging and validation features into popular Rust web frameworks, including Actix-web and Axum. It builds on the core http_cache_tags_core library to deliver middleware, extractors, API handlers, and runtime components that enable tag-based cache invalidation and metadata resolution.

§Features

  • Axum Integration (axum): Middleware, extractors, and runtime for Axum framework.
  • Actix Integration (actix): Middleware, extractors, and runtime for Actix-web framework.
  • Middleware for injecting cache metadata headers like ETag and Last-Modified
  • Extractors for validated JSON payloads and cache metadata
  • API controllers for cache invalidation and validation routes
  • Support for custom cache stores and seeders
  • Optional features mirroring the core crate (etag, last_modified, redis, etc.)

§Getting Started

§1. Define Your Cache Config

The cache config maps URL routes to cache tags, and configures invalidation endpoints.

use http_cache_tags::axum::prelude::*;

let config = CacheConfig::builder()
    .invalidation_api_route("/_invalidate")
    .invalidation_api_secret("123abc")
    .add_route_mapping("/blog/*", vec!["blog"])
    .add_route_mapping("/media/*", vec!["media"])
    .add_ignore_mapping("/blog/i-am-no-blog", vec!["blog"])
    .redis_uri("redis://127.0.0.1/")
    .build();

§2. Build a Runtime

The CacheRuntime composes the cache config with stores, API controllers, and middleware components.

use http_cache_tags::axum::prelude::*;

let config = CacheConfig::builder().build();

let runtime = CacheRuntime::builder()
    .config(config)
    // Optionally add a custom seeder to pre-populate cache tags
    // .seeder(Box::new(MyCustomSeeder {}))
    .build();

§3a. Axum Example Setup

use http_cache_tags::axum::prelude::*;
use axum::{Router, routing::get};
use std::{net::SocketAddr, sync::Arc};

#[tokio::main]
async fn main() {
    let config = CacheConfig::builder()
        .invalidation_api_route("/_invalidate")
        .add_route_mapping("/blog/*", vec!["blog"])
        .build();

    let runtime = CacheRuntime::builder().config(config).build();

    let router = Router::new()
        .route("/blog/{slug}", get(blog_handler));

    let app = runtime.attach_to(router);

    let addr = SocketAddr::from(([0, 0, 0, 0], 3000));

    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();

    // axum::serve(listener, app).await.unwrap();
    // ...
}

async fn blog_handler(CacheMeta(meta): CacheMeta) -> String {
    format!("Tags: {:?}, Last Modified: {:?}", meta.tags, meta.last_modified)
}

§3b. Actix Example Setup

use actix_web::{App, HttpServer, web, HttpResponse};
use http_cache_tags::actix::prelude::*;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let config = CacheConfig::builder()
        .invalidation_api_route("/_invalidate")
        .add_route_mapping("/blog/*", vec!["blog"])
        .build();

    let runtime = CacheRuntime::builder().config(config).build();

    let server = HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(runtime.extractor()))
            .wrap(runtime.cache_meta_middleware())
            .wrap(runtime.cache_validation_middleware())
            .service(runtime.invalidation_scope().unwrap())
            .route("/blog/{slug}", web::get().to(blog_handler))
    });


    // server.bind(("0.0.0.0", 3000))?
    //    .run()
    //    .await

    Ok(())
}

async fn blog_handler(meta: CacheMeta) -> HttpResponse {
    HttpResponse::Ok().body(format!(
        "Tags: {:?}, Last Modified: {:?}",
        meta.tags, meta.last_modified
    ))
}

§Custom Cache Store Seeder Example

You can implement CacheStoreSeeder to prepopulate the cache store with tags or metadata on startup, useful for warming caches or integrating external systems.

use http_cache_tags::axum::prelude::*;
use std::{collections::HashSet, future::Future, pin::Pin};

struct MyCustomSeeder;

impl CacheStoreSeeder for MyCustomSeeder {
    fn seed<'a>(
        &'a self,
        store: &'a (dyn CacheStore),
    ) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
        Box::pin(async move {
            // if `etag` feature is enabled
            // store.delete_etag("home");
            // store.set_etag("home", "v1");

            // if `last_modified` is enabled
            // store.delete_last_modified("home");
            // store.set_last_modified("home", chrono::Utc::now());
        })
    }
}

§API Usage Examples

This crate supports HTTP cache tagging and invalidation with detailed control over cache metadata such as ETag and Last-Modified headers.

Below are typical HTTP request examples demonstrating how to interact with the cache system:

§Conditional GET with If-Modified-Since

GET /blog/test HTTP/1.1
If-Modified-Since: Sun, 20 Jul 2025 23:12:31 +0000

§Conditional GET with If-None-Match (ETag)

GET /blog/test HTTP/1.1
If-None-Match: W/"82f493c74aaf8d7557ee619db325d6948f9cfb19"

§Invalidate with update of last_modified and etag (with auth header)

POST /_invalidate HTTP/1.1
Content-Type: application/json
Authorization: Bearer my-secret

{
  "tags": [
    { "tag": "blog", "last_modified": true, "etag": true }
  ]
}

§Remove all cache metadata for a tag

POST /_invalidate HTTP/1.1
Content-Type: application/json

{
  "tags": [
    { "tag": "blog", "last_modified": null, "etag": null }
  ]
}

§Set explicit last_modified timestamp

POST /_invalidate HTTP/1.1
Content-Type: application/json

{
  "tags": [
    { "tag": "blog", "last_modified": "2024-01-01T12:00:00Z" }
  ]
}

§Remove last_modified timestamp only

POST /_invalidate HTTP/1.1
Content-Type: application/json

{
  "tags": [
    { "tag": "blog", "last_modified": null }
  ]
}

§Trigger ETag regeneration only

POST /_invalidate HTTP/1.1
Content-Type: application/json

{
  "tags": [
    { "tag": "blog", "etag": true }
  ]
}

§Remove ETag completely

POST /_invalidate HTTP/1.1
Content-Type: application/json

{
  "tags": [
    { "tag": "blog", "etag": null }
  ]
}

§Invalidate multiple tags with different operations

POST /_invalidate HTTP/1.1
Content-Type: application/json

{
  "tags": [
    { "tag": "user:42", "etag": true },
    { "tag": "product:99", "last_modified": true },
    { "tag": "stale:tag", "last_modified": null, "etag": null }
  ]
}

§Example Response – Mixed Success

{
  "details": [
    {
      "tag": "user:42",
      "etag": {
        "outcome": "updated",
        "value": "\"etag-abc123\""
      }
    },
    {
      "tag": "user:43",
      "error": {
        "reason": "unknown tag"
      }
    }
  ],
  "summary": {
    "etag": {
      "updated": 1
    },
    "tag_errors": 1
  }
}

§Example Response – All Tags Invalid

{
  "details": [
    {
      "tag": "nonexistent",
      "error": {
        "reason": "unknown tag"
      }
    }
  ],
  "summary": {
    "tag_errors": 1
  }
}

§Error Response

Used for request-level errors (e.g. auth failure).

§Example:

{
  "error": "Unauthorized",
  "details": "Authorization header not matching"
}

For more examples, see the examples/ folder with HTTP request files demonstrating common usage patterns and advanced configurations.

§Features

By default, the axum integration and last_modified support features are enabled. At least one server integration (axum or actix) and one validation header feature (etag or last_modified) must be activated for proper operation.

FeatureDescription
axumEnable Axum framework middleware and extractors
actixEnable Actix-web middleware and extractors
config_fileLoad configuration from TOML file
last_modifiedSupport Last-Modified timestamps
etagGenerate and handle ETags
redisRedis backend for cache metadata persistence

Modules§

actix
Actix integration for HTTP cache tagging and validation.
axum
Axum integration for HTTP cache tagging and validation.