Skip to content

Commit 3d16bbf

Browse files
authored
add readme file
1 parent 0bfc5ea commit 3d16bbf

1 file changed

Lines changed: 149 additions & 1 deletion

File tree

README.md

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,149 @@
1-
# Capsule Cache
1+
## Capsule Cache
2+
3+
A lightweight, memory-quota-aware HTTP output cache middleware for Go web frameworks like Gin, Chi, and Fiber. This package addresses the challenge of safely deploying in-memory caches in containerized environments (like Kubernetes) by enforcing a strict memory limit.
4+
5+
---
6+
7+
### Key Features
8+
9+
* **Hard Memory Limit (Quota):** Prevents Out-of-Memory (OOM) issues in containerized environments by evicting Least Recently Used (LRU) items when a configurable MB limit is hit. This ensures stability in memory-constrained setups.
10+
* **Framework Agnostic:** Implemented as a standard `net/http` middleware, ensuring compatibility with all major Go routers (Gin, Chi, Fiber via their adapter methods).
11+
* **Stale-While-Revalidate (SWR):** Serves stale content instantly while triggering a non-blocking background refresh to update the cache entry.
12+
* **Time-to-Live (TTL):** Standard freshness control for cached entries.
13+
* **Pluggable Backend:** Ships with a memory-safe LRU store and an interface for easy integration with external services like Redis.
14+
* **Flexible Key Strategy:** Supports cache keys based on URL, headers, and request body hash for varied API use cases (e.g., translation services, API throttling).
15+
16+
---
17+
18+
### Installation
19+
20+
```bash
21+
go get github.com/spdeepak/capsulecache
22+
```
23+
24+
---
25+
26+
### Usage
27+
28+
The core of the package is the `NewCacheMiddleware` function, which requires a `Store` implementation and a `Config`.
29+
30+
#### 1. Setup the Cache Store (In-Memory LRU with Quota)
31+
32+
The example below sets a hard memory limit of **100 Megabytes (MB)**.
33+
34+
```go
35+
package main
36+
37+
import (
38+
"log"
39+
"net/http"
40+
"time"
41+
42+
"github.com/go-chi/chi/v5"
43+
"github.com/spdeepak/capsulecache"
44+
)
45+
46+
// Global store for access in other handlers (e.g., for manual invalidation)
47+
var store cache.CacheStore
48+
49+
func main() {
50+
// Initialize the In-Memory Cache Store with a hard limit of 100 MB
51+
const maxMemoryMB = 100
52+
store = cache.NewInMemoryQuotaLRU(maxMemoryMB)
53+
defer store.Close()
54+
55+
// Configure the Middleware
56+
cfg := &cache.Config{
57+
DefaultTTL: 1 * time.Minute,
58+
DefaultSWR: 15 * time.Second, // Allow stale content for 15s while refreshing
59+
}
60+
61+
// Initialize the Middleware
62+
cacheMiddleware := cache.NewCacheMiddleware(store, cfg)
63+
64+
// Setup Chi Router
65+
r := chi.NewRouter()
66+
67+
// Apply the cache middleware to a specific route group or handler
68+
r.Group(func(r chi.Router) {
69+
r.Use(cacheMiddleware) // Apply cache to handlers in this group
70+
71+
r.Get("/api/products/{id}", productHandler)
72+
r.Get("/public/data", publicDataHandler)
73+
})
74+
75+
// Non-cached route
76+
r.Post("/api/products", createProductHandler)
77+
78+
log.Println("Server starting on :8080")
79+
http.ListenAndServe(":8080", r)
80+
}
81+
82+
// Example Handler
83+
func publicDataHandler(w http.ResponseWriter, r *http.Request) {
84+
// This expensive operation will only run once per cache TTL
85+
time.Sleep(500 * time.Millisecond)
86+
w.Write([]byte("Cached response: " + time.Now().Format(time.RFC3339)))
87+
}
88+
```
89+
90+
#### 2. Integration with Frameworks
91+
92+
| Framework | Integration Method | Example |
93+
| :--- | :--- | :--- |
94+
| **Chi** | Standard `r.Use(middleware)` | See example above (`r.Use(cacheMiddleware)`) |
95+
| **Gin** | Use `gin.WrapH()` | `r.GET("/path", gin.WrapH(cacheMiddleware(http.HandlerFunc(myHandler))))` |
96+
| **Fiber** | Use the standard `net/http` adapter (e.g., `adaptor.HTTPHandlerFunc`) | `app.Use(adaptor.HTTPHandlerFunc(cacheMiddleware(http.HandlerFunc(myHandler))))` |
97+
98+
---
99+
100+
### Cache Invalidation
101+
102+
For compliance or immediate content updates, the `Delete` method on the `CacheStore` can be used to manually purge an entry.
103+
104+
```go
105+
// Example: Purging a cache entry after a POST request
106+
func createProductHandler(w http.ResponseWriter, r *http.Request) {
107+
// ... logic to create product ...
108+
109+
productID := "123" // Assume product ID is known
110+
productKey := "GET:/api/products/" + productID // Must match the KeyGenerator logic
111+
112+
// Invalidate the cached GET response for the product
113+
if err := store.Delete(productKey); err != nil {
114+
log.Printf("Failed to purge cache for key %s: %v", productKey, err)
115+
}
116+
117+
w.WriteHeader(http.StatusCreated)
118+
}
119+
```
120+
121+
---
122+
123+
### Custom Cache Key Strategy
124+
125+
You can define a custom function to include specific headers (e.g., `Accept-Language` for translation services) in the cache key.
126+
127+
```go
128+
// Example: Caching based on URL AND Accept-Language header
129+
func KeyWithLanguage(r *http.Request) string {
130+
// Assuming DefaultKeyGenerator is also exported from capsulecache/cache
131+
baseKey := cache.DefaultKeyGenerator(r)
132+
133+
lang := r.Header.Get("Accept-Language")
134+
if lang != "" {
135+
// Simple extraction for the primary language tag (e.g., "en-US,en;q=0.9" -> "en-US")
136+
if len(lang) > 5 {
137+
lang = lang[:5]
138+
}
139+
return baseKey + ":lang=" + lang
140+
}
141+
return baseKey
142+
}
143+
144+
// In main():
145+
// cfg := &cache.Config{
146+
// DefaultTTL: 1 * time.Minute,
147+
// KeyGenerator: KeyWithLanguage,
148+
// }
149+
```

0 commit comments

Comments
 (0)