mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-12 22:58:49 +00:00
feat: add cache multi-get handler and improve API key extraction
- Implemented a new cacheMultiGetHandler to retrieve multiple keys from the Olric cache in a single request. - Enhanced the extractAPIKey function to prioritize the X-API-Key header and improve handling of non-JWT Bearer tokens. - Updated routes to include the new multi-get endpoint for cache operations.
This commit is contained in:
parent
05ca685eee
commit
5b21774e04
15
CHANGELOG.md
15
CHANGELOG.md
@ -13,6 +13,21 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant
|
||||
### Deprecated
|
||||
|
||||
### Fixed
|
||||
## [0.57.0] - 2025-11-07
|
||||
|
||||
### Added
|
||||
- Added a new endpoint `/v1/cache/mget` to retrieve multiple keys from the distributed cache in a single request.
|
||||
|
||||
### Changed
|
||||
- Improved API key extraction logic to prioritize the `X-API-Key` header and better handle different authorization schemes (Bearer, ApiKey) while avoiding confusion with JWTs.
|
||||
- Refactored cache retrieval logic to use a dedicated function for decoding values from the distributed cache.
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
\n
|
||||
|
||||
## [0.56.0] - 2025-11-05
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@ -21,7 +21,7 @@ test-e2e:
|
||||
|
||||
.PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports install-hooks kill
|
||||
|
||||
VERSION := 0.56.0
|
||||
VERSION := 0.57.0
|
||||
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'
|
||||
|
||||
@ -80,9 +80,22 @@ func (g *Gateway) cacheGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
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
|
||||
value, err := decodeValueFromOlric(gr)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("failed to decode value: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"key": req.Key,
|
||||
"value": value,
|
||||
"dmap": req.DMap,
|
||||
})
|
||||
}
|
||||
|
||||
// decodeValueFromOlric decodes a value from Olric GetResponse
|
||||
// Handles JSON-serialized complex types and basic types (string, number, bool)
|
||||
func decodeValueFromOlric(gr *olriclib.GetResponse) (any, error) {
|
||||
var value any
|
||||
|
||||
// First, try to get as bytes (for JSON-serialized complex types)
|
||||
@ -113,10 +126,84 @@ func (g *Gateway) cacheGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (g *Gateway) cacheMultiGetHandler(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
|
||||
Keys []string `json:"keys"` // Keys 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) == "" {
|
||||
writeError(w, http.StatusBadRequest, "dmap is required")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Keys) == 0 {
|
||||
writeError(w, http.StatusBadRequest, "keys array is required and cannot be empty")
|
||||
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
|
||||
}
|
||||
|
||||
// Get all keys and collect results
|
||||
var results []map[string]any
|
||||
for _, key := range req.Keys {
|
||||
if strings.TrimSpace(key) == "" {
|
||||
continue // Skip empty keys
|
||||
}
|
||||
|
||||
gr, err := dm.Get(ctx, key)
|
||||
if err != nil {
|
||||
// Skip keys that are not found - don't include them in results
|
||||
// This matches the SDK's expectation that only found keys are returned
|
||||
if err == olriclib.ErrKeyNotFound {
|
||||
continue
|
||||
}
|
||||
// For other errors, log but continue with other keys
|
||||
// We don't want one bad key to fail the entire request
|
||||
continue
|
||||
}
|
||||
|
||||
value, err := decodeValueFromOlric(gr)
|
||||
if err != nil {
|
||||
// If we can't decode, skip this key
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, map[string]any{
|
||||
"key": key,
|
||||
"value": value,
|
||||
})
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"key": req.Key,
|
||||
"value": value,
|
||||
"dmap": req.DMap,
|
||||
"results": results,
|
||||
"dmap": req.DMap,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -131,27 +131,40 @@ func (g *Gateway) authMiddleware(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// extractAPIKey extracts API key from Authorization, X-API-Key header, or query parameters
|
||||
// Note: Bearer tokens that look like JWTs (have 2 dots) are skipped (they're JWTs, handled separately)
|
||||
// X-API-Key header is preferred when both Authorization and X-API-Key are present
|
||||
func extractAPIKey(r *http.Request) string {
|
||||
// Prefer Authorization header
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth != "" {
|
||||
// Support "Bearer <token>" and "ApiKey <token>"
|
||||
lower := strings.ToLower(auth)
|
||||
if strings.HasPrefix(lower, "bearer ") {
|
||||
return strings.TrimSpace(auth[len("Bearer "):])
|
||||
}
|
||||
if strings.HasPrefix(lower, "apikey ") {
|
||||
return strings.TrimSpace(auth[len("ApiKey "):])
|
||||
}
|
||||
// If header has no scheme, treat the whole value as token (lenient for dev)
|
||||
if !strings.Contains(auth, " ") {
|
||||
return strings.TrimSpace(auth)
|
||||
}
|
||||
}
|
||||
// Fallback to X-API-Key header
|
||||
// Prefer X-API-Key header (most explicit) - check this first
|
||||
if v := strings.TrimSpace(r.Header.Get("X-API-Key")); v != "" {
|
||||
return v
|
||||
}
|
||||
|
||||
// Check Authorization header for ApiKey scheme or non-JWT Bearer tokens
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth != "" {
|
||||
lower := strings.ToLower(auth)
|
||||
if strings.HasPrefix(lower, "bearer ") {
|
||||
tok := strings.TrimSpace(auth[len("Bearer "):])
|
||||
// Skip Bearer tokens that look like JWTs (have 2 dots) - they're JWTs
|
||||
// But allow Bearer tokens that don't look like JWTs (for backward compatibility)
|
||||
if strings.Count(tok, ".") == 2 {
|
||||
// This is a JWT, skip it
|
||||
} else {
|
||||
// This doesn't look like a JWT, treat as API key (backward compatibility)
|
||||
return tok
|
||||
}
|
||||
} else if strings.HasPrefix(lower, "apikey ") {
|
||||
return strings.TrimSpace(auth[len("ApiKey "):])
|
||||
} else if !strings.Contains(auth, " ") {
|
||||
// If header has no scheme, treat the whole value as token (lenient for dev)
|
||||
// But skip if it looks like a JWT (has 2 dots)
|
||||
tok := strings.TrimSpace(auth)
|
||||
if strings.Count(tok, ".") != 2 {
|
||||
return tok
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to query parameter (for WebSocket support)
|
||||
if v := strings.TrimSpace(r.URL.Query().Get("api_key")); v != "" {
|
||||
return v
|
||||
|
||||
@ -50,6 +50,7 @@ func (g *Gateway) Routes() http.Handler {
|
||||
// cache endpoints (Olric)
|
||||
mux.HandleFunc("/v1/cache/health", g.cacheHealthHandler)
|
||||
mux.HandleFunc("/v1/cache/get", g.cacheGetHandler)
|
||||
mux.HandleFunc("/v1/cache/mget", g.cacheMultiGetHandler)
|
||||
mux.HandleFunc("/v1/cache/put", g.cachePutHandler)
|
||||
mux.HandleFunc("/v1/cache/delete", g.cacheDeleteHandler)
|
||||
mux.HandleFunc("/v1/cache/scan", g.cacheScanHandler)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user