mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-12 22:38:50 +00:00
- Added Olric cache server integration, including configuration options for Olric servers and timeout settings. - Implemented HTTP handlers for cache operations: health check, get, put, delete, and scan. - Enhanced Makefile with commands to run the Olric server and manage its configuration. - Updated README and setup scripts to include Olric installation and configuration instructions. - Introduced tests for cache handlers to ensure proper functionality and error handling.
357 lines
9.2 KiB
Go
357 lines
9.2 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
olriclib "github.com/olric-data/olric"
|
|
)
|
|
|
|
// Cache HTTP handlers for Olric distributed cache
|
|
|
|
func (g *Gateway) cacheHealthHandler(w http.ResponseWriter, r *http.Request) {
|
|
if g.olricClient == nil {
|
|
writeError(w, http.StatusServiceUnavailable, "Olric cache client not initialized")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
err := g.olricClient.Health(ctx)
|
|
if err != nil {
|
|
writeError(w, http.StatusServiceUnavailable, fmt.Sprintf("cache health check failed: %v", err))
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"status": "ok",
|
|
"service": "olric",
|
|
})
|
|
}
|
|
|
|
func (g *Gateway) cacheGetHandler(w http.ResponseWriter, r *http.Request) {
|
|
if g.olricClient == nil {
|
|
writeError(w, http.StatusServiceUnavailable, "Olric cache client not initialized")
|
|
return
|
|
}
|
|
|
|
if r.Method != http.MethodPost {
|
|
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
DMap string `json:"dmap"` // Distributed map name
|
|
Key string `json:"key"` // Key to retrieve
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid json body")
|
|
return
|
|
}
|
|
|
|
if strings.TrimSpace(req.DMap) == "" || strings.TrimSpace(req.Key) == "" {
|
|
writeError(w, http.StatusBadRequest, "dmap and key are required")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
client := g.olricClient.GetClient()
|
|
dm, err := client.NewDMap(req.DMap)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
|
|
return
|
|
}
|
|
|
|
gr, err := dm.Get(ctx, req.Key)
|
|
if err != nil {
|
|
if err == olriclib.ErrKeyNotFound {
|
|
writeError(w, http.StatusNotFound, "key not found")
|
|
return
|
|
}
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to get key: %v", err))
|
|
return
|
|
}
|
|
|
|
// Try to decode the value from Olric
|
|
// Values stored as JSON bytes need to be deserialized, while basic types
|
|
// (strings, numbers, bools) can be retrieved directly
|
|
var value any
|
|
|
|
// First, try to get as bytes (for JSON-serialized complex types)
|
|
var bytesVal []byte
|
|
if err := gr.Scan(&bytesVal); err == nil && len(bytesVal) > 0 {
|
|
// Try to deserialize as JSON
|
|
var jsonVal any
|
|
if err := json.Unmarshal(bytesVal, &jsonVal); err == nil {
|
|
value = jsonVal
|
|
} else {
|
|
// If JSON unmarshal fails, treat as string
|
|
value = string(bytesVal)
|
|
}
|
|
} else {
|
|
// Try as string (for simple string values)
|
|
if strVal, err := gr.String(); err == nil {
|
|
value = strVal
|
|
} else {
|
|
// Fallback: try to scan as any type
|
|
var anyVal any
|
|
if err := gr.Scan(&anyVal); err == nil {
|
|
value = anyVal
|
|
} else {
|
|
// Last resort: try String() again, ignoring error
|
|
strVal, _ := gr.String()
|
|
value = strVal
|
|
}
|
|
}
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"key": req.Key,
|
|
"value": value,
|
|
"dmap": req.DMap,
|
|
})
|
|
}
|
|
|
|
func (g *Gateway) cachePutHandler(w http.ResponseWriter, r *http.Request) {
|
|
if g.olricClient == nil {
|
|
writeError(w, http.StatusServiceUnavailable, "Olric cache client not initialized")
|
|
return
|
|
}
|
|
|
|
if r.Method != http.MethodPost {
|
|
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
DMap string `json:"dmap"` // Distributed map name
|
|
Key string `json:"key"` // Key to store
|
|
Value any `json:"value"` // Value to store
|
|
TTL string `json:"ttl"` // Optional TTL (duration string like "1h", "30m")
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid json body")
|
|
return
|
|
}
|
|
|
|
if strings.TrimSpace(req.DMap) == "" || strings.TrimSpace(req.Key) == "" {
|
|
writeError(w, http.StatusBadRequest, "dmap and key are required")
|
|
return
|
|
}
|
|
|
|
if req.Value == nil {
|
|
writeError(w, http.StatusBadRequest, "value is required")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
client := g.olricClient.GetClient()
|
|
dm, err := client.NewDMap(req.DMap)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
|
|
return
|
|
}
|
|
|
|
// TODO: TTL support - need to check Olric v0.7 API for TTL/expiry options
|
|
// For now, ignore TTL if provided
|
|
if req.TTL != "" {
|
|
_, err := time.ParseDuration(req.TTL)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid ttl format: %v", err))
|
|
return
|
|
}
|
|
// TTL parsing succeeded but not yet implemented in API
|
|
// Will be added once we confirm the correct Olric API method
|
|
}
|
|
|
|
// Serialize complex types (maps, slices) to JSON bytes for Olric storage
|
|
// Olric can handle basic types (string, number, bool) directly, but complex
|
|
// types need to be serialized to bytes
|
|
var valueToStore any
|
|
switch req.Value.(type) {
|
|
case map[string]any:
|
|
// Serialize maps to JSON bytes
|
|
jsonBytes, err := json.Marshal(req.Value)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to marshal value: %v", err))
|
|
return
|
|
}
|
|
valueToStore = jsonBytes
|
|
case []any:
|
|
// Serialize slices to JSON bytes
|
|
jsonBytes, err := json.Marshal(req.Value)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to marshal value: %v", err))
|
|
return
|
|
}
|
|
valueToStore = jsonBytes
|
|
case string:
|
|
// Basic string type can be stored directly
|
|
valueToStore = req.Value
|
|
case float64:
|
|
// Basic number type can be stored directly
|
|
valueToStore = req.Value
|
|
case int:
|
|
// Basic int type can be stored directly
|
|
valueToStore = req.Value
|
|
case int64:
|
|
// Basic int64 type can be stored directly
|
|
valueToStore = req.Value
|
|
case bool:
|
|
// Basic bool type can be stored directly
|
|
valueToStore = req.Value
|
|
case nil:
|
|
// Nil can be stored directly
|
|
valueToStore = req.Value
|
|
default:
|
|
// For any other type, serialize to JSON to be safe
|
|
jsonBytes, err := json.Marshal(req.Value)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to marshal value: %v", err))
|
|
return
|
|
}
|
|
valueToStore = jsonBytes
|
|
}
|
|
|
|
err = dm.Put(ctx, req.Key, valueToStore)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to put key: %v", err))
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"status": "ok",
|
|
"key": req.Key,
|
|
"dmap": req.DMap,
|
|
})
|
|
}
|
|
|
|
func (g *Gateway) cacheDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|
if g.olricClient == nil {
|
|
writeError(w, http.StatusServiceUnavailable, "Olric cache client not initialized")
|
|
return
|
|
}
|
|
|
|
if r.Method != http.MethodPost {
|
|
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
DMap string `json:"dmap"` // Distributed map name
|
|
Key string `json:"key"` // Key to delete
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid json body")
|
|
return
|
|
}
|
|
|
|
if strings.TrimSpace(req.DMap) == "" || strings.TrimSpace(req.Key) == "" {
|
|
writeError(w, http.StatusBadRequest, "dmap and key are required")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
client := g.olricClient.GetClient()
|
|
dm, err := client.NewDMap(req.DMap)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
|
|
return
|
|
}
|
|
|
|
deletedCount, err := dm.Delete(ctx, req.Key)
|
|
if err != nil {
|
|
if err == olriclib.ErrKeyNotFound {
|
|
writeError(w, http.StatusNotFound, "key not found")
|
|
return
|
|
}
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to delete key: %v", err))
|
|
return
|
|
}
|
|
if deletedCount == 0 {
|
|
writeError(w, http.StatusNotFound, "key not found")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"status": "ok",
|
|
"key": req.Key,
|
|
"dmap": req.DMap,
|
|
})
|
|
}
|
|
|
|
func (g *Gateway) cacheScanHandler(w http.ResponseWriter, r *http.Request) {
|
|
if g.olricClient == nil {
|
|
writeError(w, http.StatusServiceUnavailable, "Olric cache client not initialized")
|
|
return
|
|
}
|
|
|
|
if r.Method != http.MethodPost {
|
|
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
DMap string `json:"dmap"` // Distributed map name
|
|
Match string `json:"match"` // Optional regex pattern to match keys
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid json body")
|
|
return
|
|
}
|
|
|
|
if strings.TrimSpace(req.DMap) == "" {
|
|
writeError(w, http.StatusBadRequest, "dmap is required")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
client := g.olricClient.GetClient()
|
|
dm, err := client.NewDMap(req.DMap)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to create DMap: %v", err))
|
|
return
|
|
}
|
|
|
|
var iterator olriclib.Iterator
|
|
if req.Match != "" {
|
|
iterator, err = dm.Scan(ctx, olriclib.Match(req.Match))
|
|
} else {
|
|
iterator, err = dm.Scan(ctx)
|
|
}
|
|
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to scan: %v", err))
|
|
return
|
|
}
|
|
defer iterator.Close()
|
|
|
|
var keys []string
|
|
for iterator.Next() {
|
|
keys = append(keys, iterator.Key())
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"keys": keys,
|
|
"count": len(keys),
|
|
"dmap": req.DMap,
|
|
})
|
|
}
|