Skip to content

Commit 8144957

Browse files
committed
add HTTP entry point for cache keys
1 parent 4062e95 commit 8144957

2 files changed

Lines changed: 109 additions & 1 deletion

File tree

.env

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ USE_CACHE=true
2222
CACHE_TTL=60
2323
STALE_TTL=432000 # 5 days
2424
USE_STALE=true
25-
ENABLE_PROFILE=fasle
25+
ENABLE_PROFILE=fasle
26+
SECRET_KEY=secret # replace it with a strong secret key

FPC.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ type CacheConfig struct {
6969
StaleExpiry time.Duration
7070
EnableProfile bool
7171
ProfilePort string
72+
SecretKey string
7273
}
7374

7475
type CacheEntry struct {
@@ -132,6 +133,7 @@ func loadConfig() *CacheConfig {
132133
StaleExpiry: time.Duration(getEnvInt("STALE_TTL", 432000)) * time.Second, // 5 days = 432000 seconds
133134
EnableProfile: getEnvBool("ENABLE_PROFILE", true),
134135
ProfilePort: getEnv("PROFILE_PORT", "6060"),
136+
SecretKey: getEnv("SECRET_KEY", "changeme"),
135137
}
136138
})
137139
return cachedConfig
@@ -160,12 +162,17 @@ func main() {
160162
// Register HTTP handler
161163
http.HandleFunc("/", handleRequest)
162164

165+
// Register cache listing endpoint
166+
http.HandleFunc("/cache/list", handleSecuredCacheList)
167+
163168
// Log startup information
164169
infoLog("FPC Server starting:\n")
165170
infoLog("- Port: %s\n", port)
166171
infoLog("- Backend: %s://%s\n", map[bool]string{true: "https", false: "http"}[config.UseHTTPS], config.Host)
167172
infoLog("- Redis: %s:%s (DB: %d)\n", config.RedisHost, config.RedisPort, config.RedisDB)
168173
infoLog("- Cache: %v (TTL: %.0fs)\n", config.UseCache, config.CacheTTL.Seconds())
174+
infoLog("- Cache List URL: http://localhost:%s/cache/list (Secret Key Required)\n", port)
175+
infoLog("- Cache List JSON: http://localhost:%s/cache/list?format=json\n", port)
169176

170177

171178
fmt.Println(`
@@ -658,3 +665,103 @@ func getEnvInt(key string, defaultValue int) int {
658665
}
659666
return defaultValue
660667
}
668+
669+
// First, define a common struct type to use in both places
670+
type CacheKeyInfo struct {
671+
Key string `json:"key"`
672+
Size int `json:"size"`
673+
ExpiredAt string `json:"expired_at,omitempty"`
674+
IsStale bool `json:"is_stale"`
675+
}
676+
677+
// Update the handleSecuredCacheList function
678+
func handleSecuredCacheList(w http.ResponseWriter, r *http.Request) {
679+
config := loadConfig()
680+
681+
// Check secret key from header or query parameter
682+
secretKey := r.Header.Get("X-Secret-Key")
683+
if secretKey == "" {
684+
// as GET query parameter
685+
secretKey = r.URL.Query().Get("key")
686+
}
687+
688+
if secretKey != config.SecretKey {
689+
w.WriteHeader(http.StatusUnauthorized)
690+
errorLog("Unauthorized cache list access attempt\n")
691+
return
692+
}
693+
694+
format := r.URL.Query().Get("format")
695+
var cacheKeys []CacheKeyInfo
696+
697+
// Get items from local cache
698+
for k, item := range localCache.Items() {
699+
if entry, ok := item.Object.(CacheEntry); ok {
700+
cacheKeys = append(cacheKeys, CacheKeyInfo{
701+
Key: k,
702+
Size: len(entry.Content),
703+
ExpiredAt: time.Unix(0, item.Expiration).Format(time.RFC3339),
704+
IsStale: entry.Expired,
705+
})
706+
}
707+
}
708+
709+
switch format {
710+
case "json":
711+
w.Header().Set("Content-Type", "application/json")
712+
json.NewEncoder(w).Encode(cacheKeys)
713+
default:
714+
w.Header().Set("Content-Type", "text/html")
715+
serveHTMLCacheList(w, cacheKeys)
716+
}
717+
}
718+
719+
func serveHTMLCacheList(w http.ResponseWriter, keys []CacheKeyInfo) {
720+
tmpl := `<!DOCTYPE html>
721+
<html>
722+
<head>
723+
<title>FPC Cache Keys</title>
724+
<style>
725+
body { font-family: Arial, sans-serif; padding: 20px; }
726+
table { border-collapse: collapse; width: 100%; margin-top: 20px; }
727+
th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
728+
tr:nth-child(even) { background-color: #f9f9f9; }
729+
th { background-color: #4CAF50; color: white; }
730+
.stale { color: #ff9800; }
731+
.active { color: #4CAF50; }
732+
.count { font-size: 1.2em; margin-bottom: 20px; }
733+
</style>
734+
</head>
735+
<body>
736+
<h1>FPC Cache Keys</h1>
737+
<div class="count">Total Keys: %d</div>
738+
<table>
739+
<tr>
740+
<th>Key</th>
741+
<th>Size</th>
742+
<th>Expires</th>
743+
<th>Status</th>
744+
</tr>
745+
%s
746+
</table>
747+
</body>
748+
</html>`
749+
750+
var rows string
751+
for _, k := range keys {
752+
status := "active"
753+
statusClass := "active"
754+
if k.IsStale {
755+
status = "stale"
756+
statusClass = "stale"
757+
}
758+
rows += fmt.Sprintf("<tr><td>%s</td><td>%d</td><td>%s</td><td class='%s'>%s</td></tr>",
759+
k.Key,
760+
k.Size,
761+
k.ExpiredAt,
762+
statusClass,
763+
status)
764+
}
765+
766+
fmt.Fprintf(w, tmpl, len(keys), rows)
767+
}

0 commit comments

Comments
 (0)