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
andLast-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.
Feature | Description |
---|---|
axum | Enable Axum framework middleware and extractors |
actix | Enable Actix-web middleware and extractors |
config_file | Load configuration from TOML file |
last_modified | Support Last-Modified timestamps |
etag | Generate and handle ETags |
redis | Redis backend for cache metadata persistence |