Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ Cargo.lock
http-cacache/
/.idea
/public
/.DS_Store
**/.DS_Store

# Cache directories from examples
**/cache/
**/cache-*/
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ members = [
"http-cache-surf",
"http-cache-quickcache",
"http-cache-tower",
"http-cache-tower-server",
"http-cache-ureq"
]
4 changes: 3 additions & 1 deletion docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
- [Development](./development/development.md)
- [Supporting a Backend Cache Manager](./development/supporting-a-backend-cache-manager.md)
- [Supporting an HTTP Client](./development/supporting-an-http-client.md)
- [Client Implementations](./clients/clients.md)
- [Client-Side Caching](./clients/clients.md)
- [reqwest](./clients/reqwest.md)
- [surf](./clients/surf.md)
- [ureq](./clients/ureq.md)
- [tower](./clients/tower.md)
- [Server-Side Caching](./server/server.md)
- [tower-server](./server/tower-server.md)
- [Backend Cache Manager Implementations](./managers/managers.md)
- [cacache](./managers/cacache.md)
- [moka](./managers/moka.md)
Expand Down
15 changes: 14 additions & 1 deletion docs/src/clients/clients.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
# Client Implementations
# Client-Side Caching

These middleware implementations cache responses from external APIs that your application calls. This is different from server-side caching, which caches your own application's responses.

**Use client-side caching when:**
- Calling external APIs
- Reducing API rate limit consumption
- Improving offline support
- Reducing bandwidth usage
- Speeding up repeated API calls

**For server-side caching** (caching your own app's responses), see [Server-Side Caching](../server/server.md).

## Available Client Implementations

The following client implementations are provided by this crate:

Expand Down
47 changes: 45 additions & 2 deletions docs/src/introduction.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,62 @@
# Introduction

`http-cache` is a library that acts as a middleware for caching HTTP responses. It is intended to be used by other libraries to support multiple HTTP clients and backend cache managers, though it does come with multiple optional manager implementations out of the box. `http-cache` is built on top of [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics) which parses HTTP headers to correctly compute cacheability of responses.
`http-cache` is a comprehensive library for HTTP response caching in Rust. It provides both **client-side** and **server-side** caching middleware for multiple HTTP clients and frameworks. Built on top of [`http-cache-semantics`](https://github.com/kornelski/rusty-http-cache-semantics), it correctly implements HTTP cache semantics as defined in RFC 7234.

## Key Features

- **Client-Side Caching**: Cache responses from external APIs you're calling
- **Server-Side Caching**: Cache your own application's responses to reduce load
- **Traditional Caching**: Standard HTTP response caching with full buffering
- **Streaming Support**: Memory-efficient caching for large responses without full buffering
- **Cache-Aware Rate Limiting**: Intelligent rate limiting that only applies on cache misses, not cache hits
- **Cache-Aware Rate Limiting**: Intelligent rate limiting that only applies on cache misses
- **Multiple Backends**: Support for disk-based (cacache) and in-memory (moka, quick-cache) storage
- **Client Integrations**: Support for reqwest, surf, tower, and ureq HTTP clients
- **Server Framework Support**: Tower-based servers (Axum, Hyper, Tonic)
- **RFC 7234 Compliance**: Proper HTTP cache semantics with respect for cache-control headers

## Client-Side vs Server-Side Caching

### Client-Side Caching

Cache responses from external APIs your application calls:

```rust
// Example: Caching API responses you fetch
let client = reqwest::Client::new();
let cached_client = HttpCache::new(client, cache_manager);
let response = cached_client.get("https://api.example.com/users").send().await?;
```

**Use cases:**
- Reducing calls to external APIs
- Offline support
- Bandwidth optimization
- Rate limit compliance

### Server-Side Caching

Cache responses your application generates:

```rust
// Example: Caching your own endpoint responses
let app = Router::new()
.route("/users/:id", get(get_user))
.layer(ServerCacheLayer::new(cache_manager)); // Cache your responses
```

**Use cases:**
- Reducing database queries
- Caching expensive computations
- Improving response times
- Reducing server load

**Critical:** Server-side cache middleware must be placed **after** routing to preserve request context (path parameters, state, etc.).

## Streaming vs Traditional Caching

The library supports two caching approaches:

- **Traditional Caching** (`CacheManager` trait): Buffers entire responses in memory before caching. Suitable for smaller responses and simpler use cases.
- **Streaming Caching** (`StreamingCacheManager` trait): Processes responses as streams without full buffering. Ideal for large files, media content, or memory-constrained environments.

Note: Streaming is currently only available for client-side caching. Server-side caching uses buffered responses.
201 changes: 201 additions & 0 deletions docs/src/server/server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# Server-Side Caching

Server-side HTTP response caching is fundamentally different from client-side caching. While client-side middleware caches responses from external APIs, server-side middleware caches your own application's responses to reduce load and improve performance.

## What is Server-Side Caching?

Server-side caching stores the responses your application generates so that subsequent identical requests can be served from cache without re-executing expensive operations like database queries or complex computations.

### Example Flow

**Without Server-Side Caching:**
```
Request → Routing → Handler → Database Query → Response (200ms)
Request → Routing → Handler → Database Query → Response (200ms)
Request → Routing → Handler → Database Query → Response (200ms)
```

**With Server-Side Caching:**
```
Request → Routing → Cache MISS → Handler → Database Query → Response (200ms) → Cached
Request → Routing → Cache HIT → Response (2ms)
Request → Routing → Cache HIT → Response (2ms)
```

## Key Differences from Client-Side Caching

| Aspect | Client-Side | Server-Side |
|--------|-------------|-------------|
| **What it caches** | External API responses | Your app's responses |
| **Position** | Before making outbound requests | After routing, before handlers |
| **Use case** | Reduce external API calls | Reduce internal computation |
| **RFC 7234 behavior** | Client cache rules | Shared cache rules |
| **Request extensions** | N/A | Must preserve (path params, state) |

## Available Implementations

Currently, server-side caching is available for:

- **Tower-based servers** (Axum, Hyper, Tonic) - See [tower-server](./tower-server.md)

## When to Use Server-Side Caching

### Good Use Cases ✅

1. **Public API endpoints** with expensive database queries
2. **Read-heavy workloads** where data doesn't change frequently
3. **Dashboard or analytics data** that updates periodically
4. **Static-like content** that requires dynamic generation
5. **Search results** for common queries
6. **Rendered HTML** for public pages

### Avoid Caching ❌

1. **User-specific data** (unless using proper cache key differentiation)
2. **Authenticated endpoints** (without user ID in cache key)
3. **Real-time data** that must always be fresh
4. **Write operations** (POST/PUT/DELETE requests)
5. **Sensitive information** that shouldn't be shared
6. **Session-dependent responses** (without session ID in cache key)

## Security Considerations

Server-side caches are **shared caches** - cached responses are served to ALL users. This is different from client-side caches which are per-client.

### Critical Security Rule

**Never cache user-specific data without including the user/session identifier in the cache key.**

### Safe Patterns

**Pattern 1: Mark user-specific responses as private**
```rust
async fn user_profile() -> Response {
(
[(header::CACHE_CONTROL, "private")], // Won't be cached
"User profile data"
).into_response()
}
```

**Pattern 2: Include user ID in cache key**
```rust
let keyer = CustomKeyer::new(|req: &Request<()>| {
let user_id = extract_user_id(req);
format!("{} {} user:{}", req.method(), req.uri().path(), user_id)
});
```

**Pattern 3: Don't cache at all**
```rust
async fn sensitive_data() -> Response {
(
[(header::CACHE_CONTROL, "no-store")],
"Sensitive data"
).into_response()
}
```

## RFC 7234 Compliance

Server-side caches implement **shared cache** semantics as defined in RFC 7234:

### Must NOT Cache

- Responses with `Cache-Control: private` (user-specific)
- Responses with `Cache-Control: no-store` (sensitive)
- Responses with `Cache-Control: no-cache` (requires revalidation)
- Non-2xx status codes (errors)
- Responses with `Authorization` header (unless explicitly allowed)

### Must Cache Correctly

- Prefer `s-maxage` over `max-age` (shared cache specific)
- Respect `Vary` headers (content negotiation)
- Handle `Expires` header as fallback
- Support `max-age` and `public` directives

## Performance Characteristics

### Benefits

- **Reduced database load**: Cached responses don't hit the database
- **Lower CPU usage**: Expensive computations run once
- **Faster response times**: Cache hits are typically <5ms
- **Better scalability**: Handle more requests with same resources

### Considerations

- **Memory usage**: Cached responses stored in memory or disk
- **Stale data**: Cached data may become outdated
- **Cache warming**: Initial requests (cache misses) are slower
- **Invalidation complexity**: Updating cached data can be tricky

## Cache Invalidation Strategies

### Time-Based (TTL)

Set expiration times on cached responses:

```rust
async fn handler() -> Response {
(
[(header::CACHE_CONTROL, "max-age=300")], // 5 minutes
"Response data"
).into_response()
}
```

### Event-Based

Manually invalidate cache entries when data changes:

```rust
// After updating user data
cache_manager.delete(&format!("GET /users/{}", user_id)).await?;
```

### Hybrid Approach

Combine TTL with manual invalidation:
- Use TTL for automatic expiration
- Invalidate early when you know data changed

## Best Practices

1. **Start conservative**: Use shorter TTLs initially, increase as you gain confidence
2. **Monitor cache hit rates**: Track X-Cache headers to measure effectiveness
3. **Set size limits**: Prevent cache from consuming too much memory
4. **Use appropriate keyers**: Match cache key strategy to your needs
5. **Document caching behavior**: Make it clear which endpoints are cached
6. **Test cache invalidation**: Ensure updates propagate correctly
7. **Consider cache warming**: Pre-populate cache for common requests
8. **Handle cache failures gracefully**: Application should work even if cache fails

## Monitoring and Debugging

### Enable Cache Status Headers

```rust
let options = ServerCacheOptions {
cache_status_headers: true,
..Default::default()
};
```

This adds `X-Cache` headers to responses:
- `X-Cache: HIT` - Served from cache
- `X-Cache: MISS` - Generated by handler

### Track Metrics

Monitor these key metrics:
- Cache hit rate (hits / total requests)
- Average response time (hits vs misses)
- Cache size and memory usage
- Cache eviction rate
- Stale response rate

## Getting Started

See the [tower-server](./tower-server.md) documentation for detailed implementation guide.
Loading