mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-15 23:18: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
|
### Deprecated
|
||||||
|
|
||||||
### Fixed
|
### 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
|
## [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
|
.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)
|
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||||
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||||
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to decode the value from Olric
|
value, err := decodeValueFromOlric(gr)
|
||||||
// Values stored as JSON bytes need to be deserialized, while basic types
|
if err != nil {
|
||||||
// (strings, numbers, bools) can be retrieved directly
|
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
|
var value any
|
||||||
|
|
||||||
// First, try to get as bytes (for JSON-serialized complex types)
|
// 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{
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
"key": req.Key,
|
"results": results,
|
||||||
"value": value,
|
"dmap": req.DMap,
|
||||||
"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
|
// 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 {
|
func extractAPIKey(r *http.Request) string {
|
||||||
// Prefer Authorization header
|
// Prefer X-API-Key header (most explicit) - check this first
|
||||||
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
|
|
||||||
if v := strings.TrimSpace(r.Header.Get("X-API-Key")); v != "" {
|
if v := strings.TrimSpace(r.Header.Get("X-API-Key")); v != "" {
|
||||||
return 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)
|
// Fallback to query parameter (for WebSocket support)
|
||||||
if v := strings.TrimSpace(r.URL.Query().Get("api_key")); v != "" {
|
if v := strings.TrimSpace(r.URL.Query().Get("api_key")); v != "" {
|
||||||
return v
|
return v
|
||||||
|
|||||||
@ -50,6 +50,7 @@ func (g *Gateway) Routes() http.Handler {
|
|||||||
// cache endpoints (Olric)
|
// cache endpoints (Olric)
|
||||||
mux.HandleFunc("/v1/cache/health", g.cacheHealthHandler)
|
mux.HandleFunc("/v1/cache/health", g.cacheHealthHandler)
|
||||||
mux.HandleFunc("/v1/cache/get", g.cacheGetHandler)
|
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/put", g.cachePutHandler)
|
||||||
mux.HandleFunc("/v1/cache/delete", g.cacheDeleteHandler)
|
mux.HandleFunc("/v1/cache/delete", g.cacheDeleteHandler)
|
||||||
mux.HandleFunc("/v1/cache/scan", g.cacheScanHandler)
|
mux.HandleFunc("/v1/cache/scan", g.cacheScanHandler)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user