oxcache 0.1.4

A high-performance multi-level cache library for Rust with L1 (memory) and L2 (Redis) caching.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
# HTTP 缓存集成指南

## 概述

Oxcache 提供了完整的 HTTP 缓存集成功能,支持标准的 HTTP 缓存头(Cache-Control、ETag、Last-Modified 等),可以轻松地将 Oxcache 集成到 Web 应用中,实现高效的 HTTP 缓存策略。

### 核心特性

- **标准 HTTP 缓存**:支持 Cache-Control、ETag、Last-Modified
-**条件请求**:支持 If-None-Match、If-Modified-Since
-**缓存验证**:自动验证缓存有效性
-**Vary 支持**:支持基于 Vary 头的缓存键
-**Stale-While-Revalidate**:支持后台刷新
-**框架集成**:支持 Axum、Actix-web 等 Web 框架

## 工作原理

### HTTP 缓存流程

```
1. 客户端请求 → HTTP 服务器
   2. 检查缓存
   3. 缓存命中且有效:
   - 返回 304 Not Modified(如果支持条件请求)
   - 或返回缓存内容
   4. 缓存未命中或过期:
   - 从数据源获取数据
   - 更新缓存
   - 返回响应
```

### 条件请求流程

```
1. 客户端发送 If-None-Match: "abc123"
   2. 服务器检查 ETag
   3. ETag 匹配:
   - 返回 304 Not Modified
   - 不返回响应体
   4. ETag 不匹配:
   - 返回 200 OK
   - 返回新内容和新的 ETag
```

## 使用方式

### Axum 集成

```rust
use axum::{
    extract::State,
    http::{HeaderMap, StatusCode},
    response::{IntoResponse, Response},
};
use oxcache::{Cache, CacheOps};
use oxcache::http::axum::CacheLayer;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug)]
struct User {
    id: u64,
    name: String,
}

// 创建缓存
let cache: Cache<String, User> = Cache::tiered(10000, "redis://localhost:6379").await?;

// 创建缓存层
let cache_layer = CacheLayer::new(cache)
    .with_ttl(3600)  // 缓存 1 小时
    .with_etag(true) // 启用 ETag
    .with_stale_while_revalidate(300); // 过期后 5 分钟内继续使用

// 定义路由
async fn get_user(
    State(cache): State<Cache<String, User>>,
    axum::extract::Path(id): axum::extract::Path<u64>,
) -> Result<Response, StatusCode> {
    let key = format!("user:{}", id);
    
    // 尝试从缓存获取
    if let Some(user) = cache.get(&key).await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? {
        // 生成 ETag
        let etag = format!("\"{}\"", compute_etag(&user));
        
        // 检查条件请求
        if let Some(if_none_match) = headers.get("if-none-match") {
            if if_none_match.to_str().unwrap() == etag {
                return Ok((StatusCode::NOT_MODIFIED, []).into_response());
            }
        }
        
        // 返回缓存内容
        return Ok((
            StatusCode::OK,
            [(axum::http::header::ETAG, etag)],
            axum::Json(user),
        ).into_response());
    }
    
    // 缓存未命中,从数据库查询
    let user = database::query_user(id).await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    
    // 更新缓存
    cache.set(&key, &user, Some(3600)).await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    
    // 返回响应
    let etag = format!("\"{}\"", compute_etag(&user));
    Ok((
        StatusCode::OK,
        [(axum::http::header::ETAG, etag)],
        axum::Json(user),
    ).into_response())
}
```

### 使用中间件

```rust
use axum::{
    middleware,
    Router,
    http::StatusCode,
};
use oxcache::http::axum::CacheMiddleware;

// 创建缓存中间件
let cache_middleware = CacheMiddleware::new(cache)
    .with_ttl(3600)
    .with_cacheable_paths(vec![
        "/api/users/*",
        "/api/products/*",
    ]);

// 应用中间件
let app = Router::new()
    .route("/api/users/:id", get_user_handler)
    .route("/api/products/:id", get_product_handler)
    .layer(middleware::from_fn(cache_middleware));
```

### 手动 HTTP 缓存

```rust
use oxcache::{Cache, CacheOps};
use oxcache::http::{HttpCache, CacheControl, ETag};

// 创建 HTTP 缓存
let http_cache = HttpCache::new(cache);

// 存储响应
let response = Response {
    status: 200,
    headers: Headers {
        cache_control: CacheControl::public()
            .max_age(3600)
            .stale_while_revalidate(300),
        etag: ETag::weak("abc123"),
    },
    body: user_data,
};

http_cache.set("/api/users/123", &response).await?;

// 获取响应
let cached_response = http_cache.get("/api/users/123").await?;

if let Some(response) = cached_response {
    // 检查是否过期
    if response.is_fresh() {
        return Ok(response);
    }
    
    // 过期但可以使用
    if response.can_stale() {
        // 后台刷新
        tokio::spawn(async move {
            refresh_cache("/api/users/123").await;
        });
        
        return Ok(response);
    }
}

// 缓存无效,重新获取
let fresh_response = fetch_from_origin("/api/users/123").await?;
http_cache.set("/api/users/123", &fresh_response).await?;

Ok(fresh_response)
```

## 缓存头配置

### Cache-Control

```rust
use oxcache::http::CacheControl;

// 公共缓存
let cache_control = CacheControl::public()
    .max_age(3600)
    .stale_while_revalidate(300);

// 私有缓存
let cache_control = CacheControl::private()
    .max_age(600);

// 不缓存
let cache_control = CacheControl::no_cache()
    .no_store();

// 必须重新验证
let cache_control = CacheControl::no_cache()
    .must_revalidate();
```

### ETag

```rust
use oxcache::http::ETag;

// 强 ETag
let etag = ETag::strong("abc123");

// 弱 ETag
let etag = ETag::weak("abc123");

// 从内容生成 ETag
fn compute_etag<T: Serialize>(data: &T) -> String {
    let serialized = serde_json::to_vec(data).unwrap();
    let hash = md5::compute(&serialized);
    format!("{:x}", hash)
}
```

### Last-Modified

```rust
use chrono::{DateTime, Utc};

// 设置 Last-Modified
let last_modified = DateTime::<Utc>::from_utc(
    NaiveDateTime::from_timestamp(1234567890, 0).unwrap(),
    Utc
);

// 检查条件请求
if let Some(if_modified_since) = headers.get("if-modified-since") {
    let if_modified_since = DateTime::parse_from_rfc2822(
        if_modified_since.to_str().unwrap()
    ).unwrap();
    
    if last_modified <= if_modified_since {
        return Ok(StatusCode::NOT_MODIFIED);
    }
}
```

## 高级用法

### Vary 支持

```rust
use oxcache::http::HttpCache;

// 创建支持 Vary 的缓存
let http_cache = HttpCache::new(cache)
    .with_vary_headers(vec![
        "Accept".to_string(),
        "Accept-Encoding".to_string(),
        "Accept-Language".to_string(),
    ]);

// 缓存会根据 Vary 头生成不同的缓存键
let response = http_cache.get("/api/users/123", headers).await?;
```

### Stale-While-Revalidate

```rust
use oxcache::http::CacheControl;

// 启用过期后继续使用
let cache_control = CacheControl::public()
    .max_age(3600)
    .stale_while_revalidate(300);  // 过期后 5 分钟内继续使用

// 后台刷新
async fn get_with_background_refresh(
    http_cache: &HttpCache,
    path: &str,
) -> Result<Response> {
    let response = http_cache.get(path).await?;
    
    if let Some(response) = response {
        if response.is_stale() && response.can_stale() {
            // 立即返回过期内容
            let stale_response = response.clone();
            
            // 后台刷新
            let path = path.to_string();
            tokio::spawn(async move {
                if let Ok(fresh) = fetch_from_origin(&path).await {
                    http_cache.set(&path, &fresh).await;
                }
            });
            
            return Ok(stale_response);
        }
    }
    
    // 缓存无效,重新获取
    let fresh_response = fetch_from_origin(path).await?;
    http_cache.set(path, &fresh_response).await?;
    
    Ok(fresh_response)
}
```

### 缓存失效

```rust
use oxcache::http::HttpCache;

// 按路径失效
http_cache.invalidate("/api/users/123").await?;

// 按模式失效
http_cache.invalidate_pattern("/api/users/*").await?;

// 全部失效
http_cache.invalidate_all().await?;

// 按标签失效
http_cache.invalidate_tag("user_data").await?;
```

### 缓存预热

```rust
use oxcache::http::HttpCache;

// 预热缓存
async fn warmup_cache(http_cache: &HttpCache) -> Result<()> {
    // 预热热门 API
    let hot_paths = vec![
        "/api/users/1",
        "/api/users/2",
        "/api/products/1",
        "/api/products/2",
    ];
    
    for path in hot_paths {
        let response = fetch_from_origin(path).await?;
        http_cache.set(path, &response).await?;
        println!("预热缓存: {}", path);
    }
    
    Ok(())
}
```

## 最佳实践

### ✅ 推荐做法

1. **合理设置 TTL**:根据数据更新频率设置合理的过期时间
2. **使用 ETag**:启用 ETag 支持条件请求,减少带宽消耗
3. **Stale-While-Revalidate**:使用过期后继续使用策略,提升用户体验
4. **Vary 正确使用**:只在必要时使用 Vary,避免缓存碎片化
5. **监控缓存命中率**:定期检查缓存命中率,优化缓存策略

### ❌ 避免做法

1. **缓存敏感数据**:不要缓存个人隐私、支付等敏感数据
2. **忽略缓存失效**:不要忽略缓存失效逻辑,导致数据不一致
3. **过长的 TTL**:不要设置过长的 TTL,导致数据过期
4. **过度使用 Vary**:不要过度使用 Vary,增加缓存复杂度
5. **忽略错误处理**:不要忽略缓存错误,导致服务不可用

## 性能优化

### 缓存键优化

```rust
// 使用简洁的缓存键
let key = format!("user:{}", user_id);  // ✅ 好

// 避免使用复杂的缓存键
let key = format!("api/v1/users/id/{}/name/{}/email/{}", 
    user_id, name, email);  // ❌ 差
```

### 压缩响应

```rust
use oxcache::http::HttpCache;

// 启用响应压缩
let http_cache = HttpCache::new(cache)
    .with_compression(true)
    .with_compression_threshold(1024);  // 超过 1KB 才压缩
```

### 批量预热

```rust
// 批量预热缓存
async fn batch_warmup(http_cache: &HttpCache) -> Result<()> {
    let tasks: Vec<_> = (1..=1000)
        .map(|id| {
            let http_cache = http_cache.clone();
            tokio::spawn(async move {
                let path = format!("/api/users/{}", id);
                if let Ok(response) = fetch_from_origin(&path).await {
                    http_cache.set(&path, &response).await;
                }
            })
        })
        .collect();
    
    for task in tasks {
        task.await?;
    }
    
    Ok(())
}
```

## 监控与统计

```rust
use oxcache::http::HttpCacheStats;

// 获取 HTTP 缓存统计
let stats = http_cache.get_stats().await?;

println!("HTTP 缓存统计:");
println!("  缓存命中: {}", stats.hits);
println!("  缓存未命中: {}", stats.misses);
println!("  命中率: {:.2}%", stats.hit_rate * 100.0);
println!("  条件请求命中: {}", stats.conditional_hits);
println!("  过期响应: {}", stats.stale_responses);
println!("  平均响应时间: {:?}", stats.avg_response_time);
```

## 完整示例

```rust
use axum::{
    extract::{Path, State},
    http::{HeaderMap, StatusCode},
    response::{IntoResponse, Response},
    Json, Router,
};
use oxcache::{Cache, CacheOps};
use oxcache::http::axum::CacheLayer;
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Serialize, Deserialize, Clone, Debug)]
struct User {
    id: u64,
    name: String,
    email: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("=== HTTP 缓存集成示例 ===\n");
    
    // 1. 创建缓存
    println!("1. 创建缓存...");
    let cache: Cache<String, User> = Cache::tiered(10000, "redis://localhost:6379").await?;
    println!("   ✅ 缓存创建成功\n");
    
    // 2. 创建缓存层
    println!("2. 创建缓存层...");
    let cache_layer = CacheLayer::new(cache.clone())
        .with_ttl(3600)
        .with_etag(true)
        .with_stale_while_revalidate(300);
    println!("   ✅ 缓存层创建成功\n");
    
    // 3. 创建路由
    println!("3. 创建路由...");
    let app = Router::new()
        .route("/api/users/:id", get_user)
        .with_state(Arc::new(cache));
    
    // 4. 启动服务器
    println!("4. 启动服务器...");
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
    println!("   ✅ 服务器启动成功: http://localhost:3000\n");
    
    axum::serve(listener, app).await?;
    
    Ok(())
}

async fn get_user(
    State(cache): State<Arc<Cache<String, User>>>,
    Path(id): Path<u64>,
    headers: HeaderMap,
) -> Result<Response, StatusCode> {
    let key = format!("user:{}", id);
    
    // 尝试从缓存获取
    if let Some(user) = cache.get(&key).await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? {
        // 生成 ETag
        let etag = format!("\"{}\"", compute_etag(&user));
        
        // 检查条件请求
        if let Some(if_none_match) = headers.get("if-none-match") {
            if if_none_match.to_str().unwrap() == etag {
                println!("💾 缓存命中,返回 304: user_id={}", id);
                return Ok((
                    StatusCode::NOT_MODIFIED,
                    [(axum::http::header::ETAG, etag)],
                ).into_response());
            }
        }
        
        println!("💾 缓存命中: user_id={}", id);
        
        // 返回缓存内容
        return Ok((
            StatusCode::OK,
            [
                (axum::http::header::ETAG, etag),
                (axum::http::header::CACHE_CONTROL, "public, max-age=3600, stale-while-revalidate=300"),
            ],
            Json(user),
        ).into_response());
    }
    
    println!("🔍 缓存未命中,查询数据库: user_id={}", id);
    
    // 缓存未命中,从数据库查询
    let user = database::query_user(id).await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    
    // 更新缓存
    cache.set(&key, &user, Some(3600)).await
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    
    println!("✅ 数据库查询成功,已更新缓存");
    
    // 返回响应
    let etag = format!("\"{}\"", compute_etag(&user));
    Ok((
        StatusCode::OK,
        [
            (axum::http::header::ETAG, etag),
            (axum::http::header::CACHE_CONTROL, "public, max-age=3600, stale-while-revalidate=300"),
        ],
        Json(user),
    ).into_response())
}

fn compute_etag(user: &User) -> String {
    let serialized = serde_json::to_vec(user).unwrap();
    let hash = md5::compute(&serialized);
    format!("{:x}", hash)
}
```

## 故障排除

### 问题:缓存不生效

**原因**:
- 缓存头配置错误
- 缓存键不一致
- TTL 设置过短

**解决方案**:
1. 检查 Cache-Control 头配置
2. 确保缓存键一致
3. 增加 TTL

### 问题:ETag 不匹配

**原因**:
- ETag 生成逻辑不一致
- 内容序列化方式不同
- 时区问题

**解决方案**:
1. 统一 ETag 生成逻辑
2. 使用相同的序列化方式
3. 使用 UTC 时间

### 问题:条件请求失败

**原因**:
- If-None-Match 头格式错误
- ETag 比较逻辑错误
- 缓存未正确存储

**解决方案**:
1. 检查 If-None-Match 头格式
2. 修复 ETag 比较逻辑
3. 确保缓存正确存储

## 相关文档

- [用户指南]USER_GUIDE.md
- [架构文档]ARCHITECTURE.md
- [API 参考]API_REFERENCE.md
- [Axum 文档]https://docs.rs/axum/

## 示例代码

- `examples/src/http_cache.rs` - HTTP 缓存完整示例
- `src/http/axum.rs` - Axum 集成实现
- `src/http/mod.rs` - HTTP 缓存实现