mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-22 04:11:29 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bafb94c742 | |||
| 55fc2ae1de | |||
| 877325a633 | |||
| 7e0d1e19b1 | |||
| 5bbe986656 | |||
| 01d1de1f0b | |||
| 1bff79e4f4 | |||
| b6e83f2837 | |||
| 287289cd80 | |||
| 21b429c98a | |||
| d96d2f429f |
@@ -21,7 +21,7 @@ jobs:
|
||||
release:
|
||||
uses: lukaszraczylo/shared-actions/.github/workflows/go-release.yaml@main
|
||||
with:
|
||||
go-version: "1.24"
|
||||
go-version: "1.25"
|
||||
docker-enabled: true
|
||||
secrets: inherit
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
go-version: "1.25"
|
||||
|
||||
- name: Run benchmarks
|
||||
run: go test -bench=. -benchmem ./... -run=^# | tee output.txt
|
||||
|
||||
@@ -19,6 +19,7 @@ builds:
|
||||
- arm64
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -X main.appVersion={{.Version}}
|
||||
|
||||
archives:
|
||||
- id: graphql-proxy
|
||||
|
||||
@@ -1021,14 +1021,17 @@ Ban details will be stored in the `banned_users.json` file, which you can mount
|
||||
The admin dashboard provides a real-time, web-based interface for monitoring proxy performance and health. Access it at `/admin` or `/admin/dashboard` on the main proxy port (default: `:8080/admin`).
|
||||
|
||||
**Features:**
|
||||
- **Real-time metrics**: Auto-refreshes every 5 seconds
|
||||
- **Live metrics**: Streamed over a WebSocket (`/admin/ws/stats`) every 2 seconds, with automatic fallback to 5-second HTTP polling if the WebSocket is unavailable
|
||||
- **Overview**: Proxy version, uptime, total/succeeded/failed/skipped requests, success rate, and current/average requests per second
|
||||
- **Live charts**: Requests-per-second and cache-hit-rate over time
|
||||
- **System health**: Backend GraphQL and Redis connectivity status
|
||||
- **Circuit breaker**: Current state, configuration, and statistics
|
||||
- **Request coalescing**: Deduplication rate and backend savings
|
||||
- **Retry budget**: Available tokens and denial rate
|
||||
- **WebSocket**: Active connections and message statistics
|
||||
- **WebSocket**: Active proxied connections
|
||||
- **Connection pool**: Active connections and health status
|
||||
- **Cache statistics**: Hit/miss rates and memory usage
|
||||
- **Cache statistics**: Hit/miss rates and memory usage (in-memory cache; shown as `n/a` for Redis)
|
||||
- **Cluster view**: When Redis cluster mode is enabled (`ENABLE_REDIS_CACHE=true`), a header toggle switches between aggregated cluster metrics and this-node-only stats, with a per-instance breakdown table. In single-node deployments the toggle is shown disabled.
|
||||
|
||||
**Configuration:**
|
||||
```bash
|
||||
@@ -1068,16 +1071,29 @@ GMP_ADMIN_DASHBOARD_ENABLE=true
|
||||
- Retry budget: Current tokens, max tokens, total attempts, denied retries, and denial rate
|
||||
- Control actions: Reset statistics, clear cache
|
||||
|
||||
5. **Cluster** (Redis cluster mode only)
|
||||
- Total and healthy instance counts, plus cluster uptime
|
||||
- Per-instance breakdown table (host, uptime, requests, success rate, RPS, cache hit rate)
|
||||
- Header toggle to switch between aggregated cluster view and this-node-only view
|
||||
|
||||
**API Endpoints:**
|
||||
The dashboard fetches data from these API endpoints:
|
||||
The dashboard streams from a WebSocket and reads these endpoints:
|
||||
- `GET /admin/ws/stats` - WebSocket stats stream (primary data path; cluster-aware by default, `?view=local` streams this-node-only stats)
|
||||
- `GET /admin/api/stats` - Overview and request statistics
|
||||
- `GET /admin/api/health` - System health status
|
||||
- `GET /admin/api/circuit-breaker` - Circuit breaker status
|
||||
- `GET /admin/api/cache` - Cache statistics
|
||||
- `GET /admin/api/coalescing` - Request coalescing statistics
|
||||
- `GET /admin/api/retry-budget` - Retry budget statistics
|
||||
- `GET /admin/api/websocket` - WebSocket connection statistics
|
||||
- `GET /admin/api/connections` - Connection pool statistics
|
||||
- `GET /admin/api/cluster/stats` - Aggregated cluster statistics (Redis cluster mode)
|
||||
- `GET /admin/api/cluster/instances` - Per-instance metrics (Redis cluster mode)
|
||||
- `GET /admin/api/cluster/debug` - Cluster aggregation diagnostics
|
||||
- `POST /admin/api/cache/clear` - Clear the cache
|
||||
- `POST /admin/api/coalescing/reset` - Reset coalescing stats
|
||||
- `POST /admin/api/retry-budget/reset` - Reset retry budget stats
|
||||
- `POST /admin/api/cluster/force-publish` - Force an immediate metrics publish (diagnostics)
|
||||
|
||||
**Screenshot:**
|
||||

|
||||
@@ -1119,3 +1135,16 @@ graphql_proxy_cache_hit{microservice="graphql_proxy",pod="hasura-w-proxy-interna
|
||||
graphql_proxy_cache_hit{pod="hasura-w-proxy-internal-6b5f4b4bbb-9xwfc",microservice="graphql_proxy"} 1
|
||||
graphql_proxy_cache_miss{microservice="graphql_proxy",pod="hasura-w-proxy-internal-6b5f4b4bbb-9xwfc"} 23
|
||||
```
|
||||
|
||||
## Telemetry
|
||||
|
||||
On startup this binary sends a single anonymous adoption ping — project name,
|
||||
version, timestamp; no identifiers, no GraphQL operations, no query/response
|
||||
content. Fire-and-forget with a 2-second timeout; cannot block startup or
|
||||
panic.
|
||||
|
||||
See **[oss-telemetry — Disabling telemetry](https://github.com/lukaszraczylo/oss-telemetry#disabling-telemetry)**
|
||||
for the exact wire format, source, and full opt-out documentation.
|
||||
|
||||
Quick opt-out: set any of `DO_NOT_TRACK=1`, `OSS_TELEMETRY_DISABLED=1`,
|
||||
or `GRAPHQL_MONITORING_PROXY_DISABLE_TELEMETRY=1`.
|
||||
|
||||
+949
-1339
File diff suppressed because it is too large
Load Diff
+38
-23
@@ -7,8 +7,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/websocket/v2"
|
||||
"github.com/gofiber/contrib/v3/websocket"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
@@ -78,7 +78,7 @@ func (ad *AdminDashboard) RegisterRoutes(app *fiber.App) {
|
||||
}
|
||||
|
||||
// serveDashboard serves the dashboard HTML
|
||||
func (ad *AdminDashboard) serveDashboard(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) serveDashboard(c fiber.Ctx) error {
|
||||
data, err := dashboardHTML.ReadFile("admin/dashboard.html")
|
||||
if err != nil {
|
||||
return c.Status(500).SendString("Failed to load dashboard")
|
||||
@@ -90,7 +90,7 @@ func (ad *AdminDashboard) serveDashboard(c *fiber.Ctx) error {
|
||||
|
||||
// getStats returns overall proxy statistics
|
||||
// In cluster mode (when metrics aggregator is available), returns aggregated stats from all instances
|
||||
func (ad *AdminDashboard) getStats(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getStats(c fiber.Ctx) error {
|
||||
// Check if cluster mode is enabled - if so, return aggregated stats
|
||||
if aggregator := GetMetricsAggregator(); aggregator != nil {
|
||||
metrics, err := aggregator.GetAggregatedMetrics()
|
||||
@@ -212,7 +212,7 @@ func formatDuration(d time.Duration) string {
|
||||
}
|
||||
|
||||
// getHealth returns health status
|
||||
func (ad *AdminDashboard) getHealth(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getHealth(c fiber.Ctx) error {
|
||||
healthMgr := GetBackendHealthManager()
|
||||
|
||||
health := map[string]any{
|
||||
@@ -241,7 +241,7 @@ func (ad *AdminDashboard) getHealth(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// getCircuitBreakerStatus returns circuit breaker status
|
||||
func (ad *AdminDashboard) getCircuitBreakerStatus(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getCircuitBreakerStatus(c fiber.Ctx) error {
|
||||
status := map[string]any{
|
||||
"enabled": false,
|
||||
"state": "unknown",
|
||||
@@ -279,7 +279,7 @@ func (ad *AdminDashboard) getCircuitBreakerStatus(c *fiber.Ctx) error {
|
||||
|
||||
// getCacheStats returns cache statistics
|
||||
// In cluster mode, returns aggregated cache stats from all instances
|
||||
func (ad *AdminDashboard) getCacheStats(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getCacheStats(c fiber.Ctx) error {
|
||||
// Check if cluster mode is enabled - if so, return aggregated cache stats
|
||||
if aggregator := GetMetricsAggregator(); aggregator != nil {
|
||||
metrics, err := aggregator.GetAggregatedMetrics()
|
||||
@@ -383,7 +383,7 @@ func (ad *AdminDashboard) getCacheStats(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// getConnectionStats returns connection pool statistics
|
||||
func (ad *AdminDashboard) getConnectionStats(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getConnectionStats(c fiber.Ctx) error {
|
||||
poolMgr := GetConnectionPoolManager()
|
||||
|
||||
stats := map[string]any{
|
||||
@@ -399,7 +399,7 @@ func (ad *AdminDashboard) getConnectionStats(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// getRetryBudgetStats returns retry budget statistics
|
||||
func (ad *AdminDashboard) getRetryBudgetStats(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getRetryBudgetStats(c fiber.Ctx) error {
|
||||
rb := GetRetryBudget()
|
||||
|
||||
if rb == nil {
|
||||
@@ -412,7 +412,7 @@ func (ad *AdminDashboard) getRetryBudgetStats(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// getCoalescingStats returns request coalescing statistics
|
||||
func (ad *AdminDashboard) getCoalescingStats(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getCoalescingStats(c fiber.Ctx) error {
|
||||
rc := GetRequestCoalescer()
|
||||
|
||||
if rc == nil {
|
||||
@@ -425,7 +425,7 @@ func (ad *AdminDashboard) getCoalescingStats(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// getWebSocketStats returns WebSocket statistics
|
||||
func (ad *AdminDashboard) getWebSocketStats(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getWebSocketStats(c fiber.Ctx) error {
|
||||
wsp := GetWebSocketProxy()
|
||||
|
||||
if wsp == nil {
|
||||
@@ -438,7 +438,7 @@ func (ad *AdminDashboard) getWebSocketStats(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// clearCache clears the cache
|
||||
func (ad *AdminDashboard) clearCache(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) clearCache(c fiber.Ctx) error {
|
||||
libpack_cache.CacheClear()
|
||||
return c.JSON(map[string]any{
|
||||
"success": true,
|
||||
@@ -447,7 +447,7 @@ func (ad *AdminDashboard) clearCache(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// resetRetryBudget resets retry budget statistics
|
||||
func (ad *AdminDashboard) resetRetryBudget(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) resetRetryBudget(c fiber.Ctx) error {
|
||||
rb := GetRetryBudget()
|
||||
if rb != nil {
|
||||
rb.Reset()
|
||||
@@ -460,7 +460,7 @@ func (ad *AdminDashboard) resetRetryBudget(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// resetCoalescing resets coalescing statistics
|
||||
func (ad *AdminDashboard) resetCoalescing(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) resetCoalescing(c fiber.Ctx) error {
|
||||
rc := GetRequestCoalescer()
|
||||
if rc != nil {
|
||||
rc.Reset()
|
||||
@@ -473,7 +473,7 @@ func (ad *AdminDashboard) resetCoalescing(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// getClusterStats returns aggregated statistics from all proxy instances
|
||||
func (ad *AdminDashboard) getClusterStats(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getClusterStats(c fiber.Ctx) error {
|
||||
aggregator := GetMetricsAggregator()
|
||||
if aggregator == nil {
|
||||
return c.Status(503).JSON(map[string]any{
|
||||
@@ -510,7 +510,7 @@ func (ad *AdminDashboard) getClusterStats(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// getClusterInstances returns detailed metrics for each proxy instance
|
||||
func (ad *AdminDashboard) getClusterInstances(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getClusterInstances(c fiber.Ctx) error {
|
||||
aggregator := GetMetricsAggregator()
|
||||
if aggregator == nil {
|
||||
return c.Status(503).JSON(map[string]any{
|
||||
@@ -544,7 +544,7 @@ func (ad *AdminDashboard) getClusterInstances(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// getClusterDebug returns debug information about cluster mode
|
||||
func (ad *AdminDashboard) getClusterDebug(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) getClusterDebug(c fiber.Ctx) error {
|
||||
aggregator := GetMetricsAggregator()
|
||||
|
||||
debug := map[string]any{
|
||||
@@ -603,7 +603,7 @@ func getMapKeys(m map[string]any) []string {
|
||||
}
|
||||
|
||||
// forcePublish forces an immediate metrics publish for testing
|
||||
func (ad *AdminDashboard) forcePublish(c *fiber.Ctx) error {
|
||||
func (ad *AdminDashboard) forcePublish(c fiber.Ctx) error {
|
||||
aggregator := GetMetricsAggregator()
|
||||
if aggregator == nil {
|
||||
return c.Status(503).JSON(map[string]any{
|
||||
@@ -612,7 +612,12 @@ func (ad *AdminDashboard) forcePublish(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// Trigger publish in goroutine to avoid blocking
|
||||
// Trigger publish in a detached goroutine: this is a fire-and-forget
|
||||
// background publish that intentionally outlives the request. publishMetrics
|
||||
// creates its own context.WithTimeout(context.Background(), ...); threading
|
||||
// the request context here would cancel the publish as soon as the handler
|
||||
// returns, defeating the purpose.
|
||||
//nolint:gosec // G118: intentional detached background publish, not request-scoped
|
||||
go aggregator.publishMetrics()
|
||||
|
||||
return c.JSON(map[string]any{
|
||||
@@ -670,6 +675,16 @@ func (ad *AdminDashboard) handleStatsWebSocket(c *websocket.Conn) {
|
||||
return nil
|
||||
})
|
||||
|
||||
// Determine the requested view. "local" forces this-instance stats; any
|
||||
// other value (default) is cluster-aware and returns aggregated metrics
|
||||
// when a metrics aggregator is available. This makes the dashboard's
|
||||
// "Cluster view" toggle a real control rather than cosmetic, since the
|
||||
// WebSocket stream is the primary data path.
|
||||
gather := ad.gatherAllStatsClusterAware
|
||||
if c.Query("view") == "local" {
|
||||
gather = ad.gatherAllStats
|
||||
}
|
||||
|
||||
// Channel to signal when to stop
|
||||
done := make(chan struct{})
|
||||
|
||||
@@ -694,8 +709,8 @@ func (ad *AdminDashboard) handleStatsWebSocket(c *websocket.Conn) {
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
|
||||
// Send initial stats immediately (cluster-aware for dashboard)
|
||||
if stats := ad.gatherAllStatsClusterAware(); stats != nil {
|
||||
// Send initial stats immediately (honours the requested view)
|
||||
if stats := gather(); stats != nil {
|
||||
buf.Reset()
|
||||
if err := enc.Encode(stats); err == nil {
|
||||
// json.Encoder.Encode appends a trailing newline; strip it
|
||||
@@ -708,8 +723,8 @@ func (ad *AdminDashboard) handleStatsWebSocket(c *websocket.Conn) {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// Gather all stats (cluster-aware for dashboard)
|
||||
stats := ad.gatherAllStatsClusterAware()
|
||||
// Gather all stats (honours the requested view)
|
||||
stats := gather()
|
||||
|
||||
// Encode into reused buffer (no per-tick allocation churn)
|
||||
buf.Reset()
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -76,10 +76,13 @@ func TestAdminDashboard_ServeDashboard(t *testing.T) {
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
assert.Contains(t, contentType, "text/html")
|
||||
|
||||
// Verify HTML content is returned
|
||||
// Verify the dashboard HTML is returned (assert on stable markers, not
|
||||
// the cosmetic page title which is part of the UI and may change).
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(body), "GraphQL Proxy Admin Dashboard")
|
||||
assert.Contains(t, string(body), "<!DOCTYPE html>")
|
||||
assert.Contains(t, string(body), "graphql-proxy")
|
||||
assert.Contains(t, string(body), "/admin/ws/stats")
|
||||
}
|
||||
|
||||
func TestAdminDashboard_GetStats(t *testing.T) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
fiber "github.com/gofiber/fiber/v3"
|
||||
"github.com/gofrs/flock"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
var bannedUsersIDs sync.Map // key: userID string, value: reason string
|
||||
|
||||
// authMiddleware provides API key authentication for admin endpoints
|
||||
func authMiddleware(c *fiber.Ctx) error {
|
||||
func authMiddleware(c fiber.Ctx) error {
|
||||
apiKey := c.Get("X-API-Key")
|
||||
|
||||
// Get expected key from config (try GMP_ prefix first, then fallback)
|
||||
@@ -76,8 +76,7 @@ func enableApi(ctx context.Context) error {
|
||||
}
|
||||
|
||||
apiserver := fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
AppName: fmt.Sprintf("GraphQL Monitoring Proxy - %s v%s", libpack_config.PKG_NAME, libpack_config.PKG_VERSION),
|
||||
AppName: fmt.Sprintf("GraphQL Monitoring Proxy - %s v%s", libpack_config.PKG_NAME, libpack_config.PKG_VERSION),
|
||||
})
|
||||
|
||||
api := apiserver.Group("/api")
|
||||
@@ -97,7 +96,7 @@ func enableApi(ctx context.Context) error {
|
||||
// Start server in a goroutine and handle shutdown
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
if err := apiserver.Listen(fmt.Sprintf(":%d", cfg.Server.ApiPort)); err != nil {
|
||||
if err := apiserver.Listen(fmt.Sprintf(":%d", cfg.Server.ApiPort), fiber.ListenConfig{DisableStartupMessage: true}); err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
}()
|
||||
@@ -135,7 +134,7 @@ func periodicallyReloadBannedUsers(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func checkIfUserIsBanned(c *fiber.Ctx, userID string) bool {
|
||||
func checkIfUserIsBanned(c fiber.Ctx, userID string) bool {
|
||||
_, found := bannedUsersIDs.Load(userID)
|
||||
|
||||
cfg.Logger.Debug(&libpack_logger.LogMessage{
|
||||
@@ -158,7 +157,7 @@ func checkIfUserIsBanned(c *fiber.Ctx, userID string) bool {
|
||||
return found
|
||||
}
|
||||
|
||||
func apiClearCache(c *fiber.Ctx) error {
|
||||
func apiClearCache(c fiber.Ctx) error {
|
||||
cfg.Logger.Debug(&libpack_logger.LogMessage{
|
||||
Message: "Clearing cache via API",
|
||||
})
|
||||
@@ -169,12 +168,12 @@ func apiClearCache(c *fiber.Ctx) error {
|
||||
return c.SendString("OK: cache cleared")
|
||||
}
|
||||
|
||||
func apiCacheStats(c *fiber.Ctx) error {
|
||||
func apiCacheStats(c fiber.Ctx) error {
|
||||
return c.JSON(libpack_cache.GetCacheStats())
|
||||
}
|
||||
|
||||
// apiCircuitBreakerHealth returns the health status of the circuit breaker
|
||||
func apiCircuitBreakerHealth(c *fiber.Ctx) error {
|
||||
func apiCircuitBreakerHealth(c fiber.Ctx) error {
|
||||
if cb == nil {
|
||||
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
|
||||
"status": "disabled",
|
||||
@@ -232,9 +231,9 @@ type apiBanUserRequest struct {
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func apiBanUser(c *fiber.Ctx) error {
|
||||
func apiBanUser(c fiber.Ctx) error {
|
||||
var req apiBanUserRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
if err := c.Bind().Body(&req); err != nil {
|
||||
cfg.Logger.Error(&libpack_logger.LogMessage{
|
||||
Message: "Can't parse the ban user request",
|
||||
Pairs: map[string]any{"error": err.Error()},
|
||||
@@ -260,9 +259,9 @@ func apiBanUser(c *fiber.Ctx) error {
|
||||
return c.SendString("OK: user banned")
|
||||
}
|
||||
|
||||
func apiUnbanUser(c *fiber.Ctx) error {
|
||||
func apiUnbanUser(c fiber.Ctx) error {
|
||||
var req apiBanUserRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
if err := c.Bind().Body(&req); err != nil {
|
||||
cfg.Logger.Error(&libpack_logger.LogMessage{
|
||||
Message: "Can't parse the unban user request",
|
||||
Pairs: map[string]any{"error": err.Error()},
|
||||
@@ -463,7 +462,7 @@ func lockFileRead(fileLock *flock.Flock) error {
|
||||
}
|
||||
|
||||
// apiBackendHealth returns the health status of the GraphQL backend
|
||||
func apiBackendHealth(c *fiber.Ctx) error {
|
||||
func apiBackendHealth(c fiber.Ctx) error {
|
||||
healthMgr := GetBackendHealthManager()
|
||||
if healthMgr == nil {
|
||||
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
|
||||
@@ -499,7 +498,7 @@ func apiBackendHealth(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// apiConnectionPoolHealth returns the health status of the connection pool
|
||||
func apiConnectionPoolHealth(c *fiber.Ctx) error {
|
||||
func apiConnectionPoolHealth(c fiber.Ctx) error {
|
||||
poolMgr := GetConnectionPoolManager()
|
||||
if poolMgr == nil {
|
||||
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
|
||||
|
||||
+10
-37
@@ -11,9 +11,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@@ -55,9 +54,7 @@ func (suite *APIAuthSecurityTestSuite) SetupTest() {
|
||||
suite.validAPIKey = "test-secure-api-key-12345"
|
||||
|
||||
// Create test Fiber app with authentication
|
||||
suite.app = fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
})
|
||||
suite.app = fiber.New(fiber.Config{})
|
||||
|
||||
// Setup API routes with authentication middleware
|
||||
api := suite.app.Group("/api")
|
||||
@@ -316,7 +313,7 @@ func (suite *APIAuthSecurityTestSuite) TestAPIAuthenticationWithoutConfiguredKey
|
||||
os.Unsetenv("ADMIN_API_KEY")
|
||||
|
||||
// Create new app without configured API key
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
api := app.Group("/api")
|
||||
api.Use(authMiddleware)
|
||||
api.Post("/user-ban", apiBanUser)
|
||||
@@ -356,11 +353,7 @@ func (suite *APIAuthSecurityTestSuite) TestTimingAttackResistance() {
|
||||
"", // Empty
|
||||
}
|
||||
|
||||
timings := make([]time.Duration, len(invalidKeys))
|
||||
|
||||
for i, key := range invalidKeys {
|
||||
start := time.Now()
|
||||
|
||||
for _, key := range invalidKeys {
|
||||
req, err := http.NewRequest("POST", "/api/user-ban",
|
||||
bytes.NewBuffer([]byte(`{"user_id": "test", "reason": "test"}`)))
|
||||
suite.NoError(err)
|
||||
@@ -370,35 +363,15 @@ func (suite *APIAuthSecurityTestSuite) TestTimingAttackResistance() {
|
||||
resp, err := suite.app.Test(req)
|
||||
suite.NoError(err)
|
||||
|
||||
timings[i] = time.Since(start)
|
||||
|
||||
suite.Equal(401, resp.StatusCode,
|
||||
"All invalid keys should return 401, key: %s", key)
|
||||
}
|
||||
|
||||
// Verify that timing variations are minimal (within reasonable bounds)
|
||||
// This is a heuristic test - timing attack resistance is primarily
|
||||
// achieved by the subtle.ConstantTimeCompare function
|
||||
var minTime, maxTime time.Duration
|
||||
for i, timing := range timings {
|
||||
if i == 0 {
|
||||
minTime = timing
|
||||
maxTime = timing
|
||||
} else {
|
||||
if timing < minTime {
|
||||
minTime = timing
|
||||
}
|
||||
if timing > maxTime {
|
||||
maxTime = timing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The timing difference should be reasonable (not orders of magnitude)
|
||||
// This is mainly to catch obvious timing leaks
|
||||
timingRatio := float64(maxTime) / float64(minTime)
|
||||
suite.Less(timingRatio, 10.0,
|
||||
"Timing difference should be reasonable (max/min < 10x)")
|
||||
// Timing-attack resistance is guaranteed by subtle.ConstantTimeCompare in
|
||||
// authMiddleware (verified by code, not by wall-clock measurement). Asserting
|
||||
// on a max/min wall-clock ratio across full HTTP round-trips is unreliable:
|
||||
// it is dominated by scheduler/GC noise and produces flaky failures, so we
|
||||
// intentionally do not assert on the timing ratio.
|
||||
}
|
||||
|
||||
// TestConcurrentAPIAuthentication tests authentication under concurrent load
|
||||
@@ -616,7 +589,7 @@ func BenchmarkAPIAuthentication(b *testing.B) {
|
||||
os.Setenv("GMP_ADMIN_API_KEY", validAPIKey)
|
||||
defer os.Unsetenv("GMP_ADMIN_API_KEY")
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
api := app.Group("/api")
|
||||
api.Use(authMiddleware)
|
||||
api.Get("/cache-stats", apiCacheStats)
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
fiber "github.com/gofiber/fiber/v3"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofrs/flock"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
|
||||
Vendored
+2
-2
@@ -11,7 +11,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
fiber "github.com/gofiber/fiber/v3"
|
||||
"github.com/gookit/goutil/strutil"
|
||||
libpack_cache_memory "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/memory"
|
||||
libpack_cache_redis "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/redis"
|
||||
@@ -79,7 +79,7 @@ var (
|
||||
// - Same query, same variables, different user → different cache key
|
||||
//
|
||||
// Different variable values will always produce different cache keys.
|
||||
func CalculateHash(c *fiber.Ctx, userID string, userRole string) string {
|
||||
func CalculateHash(c fiber.Ctx, userID string, userRole string) string {
|
||||
cacheKeyData := string(c.Body())
|
||||
|
||||
// Include user context in cache key (default behavior for security)
|
||||
|
||||
Vendored
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"compress/gzip"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_cache_memory "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/memory"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
"github.com/sony/gobreaker"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestInitCircuitBreaker_NilMonitoring_NoPanic is a regression test for a boot
|
||||
// panic: enabling the circuit breaker (ENABLE_CIRCUIT_BREAKER=true) crashed at
|
||||
// startup because parseConfig calls initCircuitBreaker before the monitoring
|
||||
// server assigns cfg.Monitoring (StartMonitoringServer runs later). The metrics
|
||||
// gauge registration then dereferenced a nil *MetricsSetup.
|
||||
//
|
||||
// The breaker (and the admin dashboard, which reads gobreaker state directly)
|
||||
// must initialise without panicking even when monitoring is not yet available.
|
||||
func TestInitCircuitBreaker_NilMonitoring_NoPanic(t *testing.T) {
|
||||
origCfg := cfg
|
||||
cbMutex.Lock()
|
||||
origCb, origMetrics := cb, cbMetrics
|
||||
cb, cbMetrics = nil, nil
|
||||
cbMutex.Unlock()
|
||||
t.Cleanup(func() {
|
||||
cbMutex.Lock()
|
||||
cb, cbMetrics = origCb, origMetrics
|
||||
cbMutex.Unlock()
|
||||
cfg = origCfg
|
||||
})
|
||||
|
||||
cfg = &config{}
|
||||
cfg.Logger = libpack_logger.New().SetOutput(&bytes.Buffer{})
|
||||
cfg.Monitoring = nil // the production state when parseConfig runs
|
||||
cfg.CircuitBreaker.Enable = true
|
||||
cfg.CircuitBreaker.MaxFailures = 3
|
||||
cfg.CircuitBreaker.Timeout = 5
|
||||
cfg.CircuitBreaker.MaxRequestsInHalfOpen = 2
|
||||
|
||||
require.NotPanics(t, func() { initCircuitBreaker(cfg) },
|
||||
"initCircuitBreaker must not panic when cfg.Monitoring is nil")
|
||||
require.NotNil(t, cb, "circuit breaker must initialise even without monitoring")
|
||||
require.NotNil(t, cbMetrics, "circuit breaker metrics manager must be created")
|
||||
}
|
||||
+11
-11
@@ -19,8 +19,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/websocket/v2"
|
||||
"github.com/gofiber/contrib/v3/websocket"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
gorillaws "github.com/gorilla/websocket"
|
||||
libpack_cache_mem "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/memory"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
@@ -38,8 +38,8 @@ import (
|
||||
func TestHandleWebSocket_DisabledReturns501(t *testing.T) {
|
||||
wsp := NewWebSocketProxy("http://127.0.0.1:1", WebSocketConfig{Enabled: false}, libpack_logger.New(), nil)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app.Get("/ws", func(c *fiber.Ctx) error {
|
||||
app := fiber.New()
|
||||
app.Get("/ws", func(c fiber.Ctx) error {
|
||||
return wsp.HandleWebSocket(c)
|
||||
})
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestHandleWebSocket_DisabledReturns501(t *testing.T) {
|
||||
req.Header.Set("Sec-WebSocket-Version", "13")
|
||||
req.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
|
||||
|
||||
resp, err := app.Test(req, 5000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5000 * time.Millisecond})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, fiber.StatusNotImplemented, resp.StatusCode)
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func TestHandleWebSocket_BackendDialFail(t *testing.T) {
|
||||
nil,
|
||||
)
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
app.Get("/ws", websocket.New(func(c *websocket.Conn) {
|
||||
wsp.handleConnection(context.Background(), c, http.Header{})
|
||||
}))
|
||||
@@ -131,9 +131,9 @@ func TestIsWebSocketRequest(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
var got bool
|
||||
app.Get("/chk", func(c *fiber.Ctx) error {
|
||||
app.Get("/chk", func(c fiber.Ctx) error {
|
||||
got = IsWebSocketRequest(c)
|
||||
return c.SendStatus(200)
|
||||
})
|
||||
@@ -142,7 +142,7 @@ func TestIsWebSocketRequest(t *testing.T) {
|
||||
for k, v := range tt.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
resp, err := app.Test(req, 2000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 2000 * time.Millisecond})
|
||||
require.NoError(t, err)
|
||||
_ = resp.Body.Close()
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestHandleStatsWebSocket_ReceivesInitialMessage(t *testing.T) {
|
||||
_ = StartMonitoringServer()
|
||||
|
||||
dashboard := NewAdminDashboard(libpack_logger.New())
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
dashboard.RegisterRoutes(app)
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
@@ -207,7 +207,7 @@ func TestHandleStatsWebSocket_ClientCloseExitsLoop(t *testing.T) {
|
||||
// Use an isolated logger — not the global cfg.Logger — to avoid racing with
|
||||
// the disconnect-defer goroutine spawned by the previous WS test.
|
||||
dashboard := NewAdminDashboard(libpack_logger.New())
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
dashboard.RegisterRoutes(app)
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
|
||||
+16
-4
@@ -18,6 +18,8 @@ type ConnectionPoolManager struct {
|
||||
client *fasthttp.Client
|
||||
cancel context.CancelFunc
|
||||
logger *libpack_logging.Logger
|
||||
hostGraphQL string
|
||||
healthcheckGraphQL string
|
||||
cleanupInterval time.Duration
|
||||
keepAliveInterval time.Duration
|
||||
recoveryCheckInterval time.Duration
|
||||
@@ -45,6 +47,14 @@ func NewConnectionPoolManager(client *fasthttp.Client) *ConnectionPoolManager {
|
||||
cpm.logger = cfg.Logger
|
||||
}
|
||||
|
||||
// Capture backend URLs at creation. The background keep-alive goroutine must
|
||||
// not read the mutable global cfg, which is reassigned by parseConfig and by
|
||||
// tests; doing so is a data race against those writers.
|
||||
if cfg != nil {
|
||||
cpm.hostGraphQL = cfg.Server.HostGraphQL
|
||||
cpm.healthcheckGraphQL = cfg.Server.HealthcheckGraphQL
|
||||
}
|
||||
|
||||
// Start periodic maintenance tasks
|
||||
cpm.startPeriodicMaintenance()
|
||||
|
||||
@@ -133,8 +143,10 @@ func (cpm *ConnectionPoolManager) performKeepAlive() {
|
||||
return
|
||||
}
|
||||
|
||||
// Only perform keep-alive if we have a backend URL configured
|
||||
if cfg == nil || cfg.Server.HostGraphQL == "" {
|
||||
// Only perform keep-alive if we have a backend URL configured.
|
||||
// Uses the URL captured at creation (cpm.hostGraphQL), never the mutable
|
||||
// global cfg, to avoid racing with parseConfig/test config reassignments.
|
||||
if cpm.hostGraphQL == "" {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -152,10 +164,10 @@ func (cpm *ConnectionPoolManager) performKeepAlive() {
|
||||
defer fasthttp.ReleaseResponse(resp)
|
||||
|
||||
// Try to use health check endpoint if available, otherwise use base URL
|
||||
healthURL := cfg.Server.HealthcheckGraphQL
|
||||
healthURL := cpm.healthcheckGraphQL
|
||||
if healthURL == "" {
|
||||
// Use base URL with proper path separator
|
||||
baseURL := cfg.Server.HostGraphQL
|
||||
baseURL := cpm.hostGraphQL
|
||||
if !strings.HasSuffix(baseURL, "/") {
|
||||
baseURL += "/"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
"github.com/valyala/fasthttp"
|
||||
@@ -226,7 +226,7 @@ func TestDebugParseGraphQLQuery_NoPanic(t *testing.T) {
|
||||
cfgMutex.Unlock()
|
||||
})
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
@@ -441,8 +441,8 @@ func TestCoverageMicro_IsWebSocketRequest(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app.Get("/ws-test", func(c *fiber.Ctx) error {
|
||||
app := fiber.New()
|
||||
app.Get("/ws-test", func(c fiber.Ctx) error {
|
||||
result := IsWebSocketRequest(c)
|
||||
if result {
|
||||
return c.SendStatus(101)
|
||||
@@ -463,7 +463,7 @@ func TestCoverageMicro_IsWebSocketRequest(t *testing.T) {
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
}
|
||||
|
||||
resp, err := app.Test(req, -1)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 0})
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test error: %v", err)
|
||||
}
|
||||
@@ -541,15 +541,15 @@ func TestCoverageMicro_SetupTracing_Disabled(t *testing.T) {
|
||||
cfgMutex.Unlock()
|
||||
}()
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
var capturedCtx context.Context
|
||||
app.Get("/trace-test", func(c *fiber.Ctx) error {
|
||||
app.Get("/trace-test", func(c fiber.Ctx) error {
|
||||
capturedCtx = setupTracing(c)
|
||||
return c.SendStatus(200)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/trace-test", nil)
|
||||
resp, err := app.Test(req, -1)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 0})
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test error: %v", err)
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
fiber "github.com/gofiber/fiber/v3"
|
||||
"github.com/graphql-go/graphql/language/ast"
|
||||
"github.com/graphql-go/graphql/language/parser"
|
||||
"github.com/graphql-go/graphql/language/source"
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// - Automatic detection of mutations routed to wrong endpoints
|
||||
//
|
||||
// To enable: Set LOG_LEVEL=DEBUG and restart the proxy
|
||||
func debugParseGraphQLQuery(c *fiber.Ctx, query string) {
|
||||
func debugParseGraphQLQuery(c fiber.Ctx, query string) {
|
||||
if cfg == nil || cfg.Logger == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
@@ -3,31 +3,32 @@ module github.com/lukaszraczylo/graphql-monitoring-proxy
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/VictoriaMetrics/metrics v1.43.1
|
||||
github.com/VictoriaMetrics/metrics v1.44.0
|
||||
github.com/alicebob/miniredis/v2 v2.33.0
|
||||
github.com/avast/retry-go/v4 v4.7.0
|
||||
github.com/goccy/go-json v0.10.6
|
||||
github.com/gofiber/fiber/v2 v2.52.12
|
||||
github.com/gofiber/websocket/v2 v2.2.1
|
||||
github.com/gofiber/contrib/v3/websocket v1.2.0
|
||||
github.com/gofiber/fiber/v3 v3.3.0
|
||||
github.com/gofrs/flock v0.13.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gookit/goutil v0.7.4
|
||||
github.com/gookit/goutil v0.7.6
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/graphql-go/graphql v0.8.1
|
||||
github.com/jackc/pgx/v5 v5.9.1
|
||||
github.com/jackc/pgx/v5 v5.10.0
|
||||
github.com/lukaszraczylo/ask v0.0.0-20240916204100-6e9ef53a62d9
|
||||
github.com/lukaszraczylo/go-ratecounter v0.1.12
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.2.89
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
github.com/lukaszraczylo/oss-telemetry v0.2.3
|
||||
github.com/redis/go-redis/v9 v9.20.1
|
||||
github.com/sony/gobreaker v1.0.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/valyala/fasthttp v1.69.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
github.com/valyala/fasthttp v1.71.0
|
||||
go.opentelemetry.io/otel v1.44.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0
|
||||
go.opentelemetry.io/otel/sdk v1.44.0
|
||||
go.opentelemetry.io/otel/trace v1.44.0
|
||||
go.uber.org/automaxprocs v1.6.0
|
||||
google.golang.org/grpc v1.80.0
|
||||
google.golang.org/grpc v1.81.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -35,38 +36,40 @@ require (
|
||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/fasthttp/websocket v1.5.12 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
|
||||
github.com/gofiber/schema v1.7.1 // indirect
|
||||
github.com/gofiber/utils/v2 v2.0.6 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.22 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.18.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761 // indirect
|
||||
github.com/tinylib/msgp v1.6.4 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fastrand v1.1.0 // indirect
|
||||
github.com/valyala/histogram v1.2.0 // indirect
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.41.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/term v0.44.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260618152121-87f3d3e198d3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260618152121-87f3d3e198d3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
github.com/VictoriaMetrics/metrics v1.43.1 h1:j3Ba4l2K1q3pkvzPqt6aSiQ2DBlAEj3VPVeBtpR3t/Y=
|
||||
github.com/VictoriaMetrics/metrics v1.43.1/go.mod h1:xDM82ULLYCYdFRgQ2JBxi8Uf1+8En1So9YUwlGTOqTc=
|
||||
github.com/VictoriaMetrics/metrics v1.44.0 h1:Fr8yqQSV+ZfYaDD/anqk1E8e9YPgfleSleJmAI0M0Tw=
|
||||
github.com/VictoriaMetrics/metrics v1.44.0/go.mod h1:xDM82ULLYCYdFRgQ2JBxi8Uf1+8En1So9YUwlGTOqTc=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA=
|
||||
@@ -16,15 +16,13 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE=
|
||||
github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -34,10 +32,14 @@ github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms=
|
||||
github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE=
|
||||
github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw=
|
||||
github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
||||
github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
|
||||
github.com/gofiber/contrib/v3/websocket v1.2.0 h1:wjkzC3exbhRL3cPCVLRYN3MFq3yhMvtBeGwqEYQk+yc=
|
||||
github.com/gofiber/contrib/v3/websocket v1.2.0/go.mod h1:fpqdn3mVAKAKfSOt8yHXhO70bZ1mz6z6NhY87CjM3k8=
|
||||
github.com/gofiber/fiber/v3 v3.3.0 h1:QBd3sYCqdy6Qs5gJYzSw4I4SbqL204jPqpdub/ueiw8=
|
||||
github.com/gofiber/fiber/v3 v3.3.0/go.mod h1:YH7/TAoRaU4kF8slDCtQuFJ1NzC+3MtxUI4KfvQtaIA=
|
||||
github.com/gofiber/schema v1.7.1 h1:oSJBKdgP8JeIME4TQSAqlNKTU2iBB+2RNmKi8Nsc+TI=
|
||||
github.com/gofiber/schema v1.7.1/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
|
||||
github.com/gofiber/utils/v2 v2.0.6 h1:7fXYy7nSsyqbH0GQUMtK4Kwjy4J7R5742VM7JsZxzOs=
|
||||
github.com/gofiber/utils/v2 v2.0.6/go.mod h1:p7mAHAk3+oUK10ZX2xTw9fZQixb4hCg8SKd4IH2xroU=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
@@ -46,26 +48,26 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gookit/goutil v0.7.4 h1:OWgUngToNz+bPlX5aP+EMG31DraEU63uvKMwwT3vseM=
|
||||
github.com/gookit/goutil v0.7.4/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
|
||||
github.com/gookit/goutil v0.7.6 h1:700ZP6QPWhw5ms7X13JH9fUs4LTyYMmncFFMGpK73ns=
|
||||
github.com/gookit/goutil v0.7.6/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/graphql-go/graphql v0.8.1 h1:p7/Ou/WpmulocJeEx7wjQy611rtXGQaAcXGqanuMMgc=
|
||||
github.com/graphql-go/graphql v0.8.1/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
|
||||
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -76,22 +78,27 @@ github.com/lukaszraczylo/go-ratecounter v0.1.12 h1:VO6hHYGw/Jy9JUizXf/bS0AI2QX1u
|
||||
github.com/lukaszraczylo/go-ratecounter v0.1.12/go.mod h1:TqXEOCtFJStk1i0tkipprv1kiDHGon1MVUisjSTBSKM=
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.2.89 h1:Xbu1Ny+a0lT2Sr2SaSC8mcHmGQDwGD4TJKk4DDd+PwA=
|
||||
github.com/lukaszraczylo/go-simple-graphql v1.2.89/go.mod h1:PxQYblQDZISmYYj8sNfazAWxAOh1rhAtU208y+uPV8s=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.22 h1:76lXsPn6FyHtTY+jt2fTTvsMUCZq1k0qwRsAMuxzKAk=
|
||||
github.com/mattn/go-runewidth v0.0.22/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/lukaszraczylo/oss-telemetry v0.2.3 h1:xoDtBqeZGmXj7IteiE1M5WMuzeoqag58qEleI0Cf2Ms=
|
||||
github.com/lukaszraczylo/oss-telemetry v0.2.3/go.mod h1:+Cn78qZo8rc3T9eZt0v3oICYRdd75wORtSidc8lNjDQ=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/redis/go-redis/v9 v9.20.1 h1:sfCU6A8P3dXbKyWes02uxA2baehGux9dZHfEKtsTB1w=
|
||||
github.com/redis/go-redis/v9 v9.20.1/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
|
||||
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||
github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761 h1:McifyVxygw1d67y6vxUqls2D46J8W9nrki9c8c0eVvE=
|
||||
github.com/savsgio/gotils v0.0.0-20250924091648-bce9a52d7761/go.mod h1:Vi9gvHvTw4yCUHIznFl5TPULS7aXwgaTByGeBY75Wko=
|
||||
github.com/shamaton/msgpack/v3 v3.1.2 h1:d5gWAIyMU4M0WgDjz6IFSCuXJUA2dFwRHBpDclE8CLw=
|
||||
github.com/shamaton/msgpack/v3 v3.1.2/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc=
|
||||
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
|
||||
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -99,36 +106,40 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
|
||||
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/valyala/fasthttp v1.71.0 h1:tepR7H+Guh9VUqxxcPggYi8R3lGUu2Rsdh+z7/FCY3k=
|
||||
github.com/valyala/fasthttp v1.71.0/go.mod h1:z1sDUvOShhXq/C9mwH/fSm1Vb71tUJwmQdgkBrBNwnA=
|
||||
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
|
||||
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
@@ -137,25 +148,26 @@ go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
|
||||
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260618152121-87f3d3e198d3 h1:ctPmKL12ZsoKAlmPUsoW70zEDiYF+/H6aLieXxgAU0k=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260618152121-87f3d3e198d3/go.mod h1:Z4WJ5pJOYWFWcHEQUelD5QaZDknIQkpIL/+fyJOT9+A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260618152121-87f3d3e198d3 h1:phvBWCAQMGN1945mp5fjCXP6jEF0+a0+4TjokS4sxNY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260618152121-87f3d3e198d3/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
+4
-4
@@ -10,7 +10,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
fiber "github.com/gofiber/fiber/v3"
|
||||
"github.com/graphql-go/graphql/language/ast"
|
||||
"github.com/graphql-go/graphql/language/parser"
|
||||
"github.com/graphql-go/graphql/language/source"
|
||||
@@ -224,7 +224,7 @@ func trackParsingAllocations() func() {
|
||||
}
|
||||
}
|
||||
|
||||
func parseGraphQLQuery(c *fiber.Ctx) *parseGraphQLQueryResult {
|
||||
func parseGraphQLQuery(c fiber.Ctx) *parseGraphQLQueryResult {
|
||||
startTime := time.Now()
|
||||
|
||||
if cfg != nil && cfg.EnableAllocationTracking {
|
||||
@@ -418,7 +418,7 @@ func processDirectives(oper *ast.OperationDefinition, res *parseGraphQLQueryResu
|
||||
}
|
||||
|
||||
// checkSelections recursively checks if any selection is an introspection query that should be blocked
|
||||
func checkSelections(c *fiber.Ctx, selections []ast.Selection) bool {
|
||||
func checkSelections(c fiber.Ctx, selections []ast.Selection) bool {
|
||||
if len(selections) == 0 {
|
||||
return false
|
||||
}
|
||||
@@ -468,7 +468,7 @@ func checkSelections(c *fiber.Ctx, selections []ast.Selection) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func checkIfContainsIntrospection(c *fiber.Ctx, query string) bool {
|
||||
func checkIfContainsIntrospection(c fiber.Ctx, query string) bool {
|
||||
startTime := time.Now()
|
||||
blocked := false
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
fiber "github.com/gofiber/fiber/v3"
|
||||
"github.com/graphql-go/graphql/language/ast"
|
||||
"github.com/graphql-go/graphql/language/parser"
|
||||
"github.com/valyala/fasthttp"
|
||||
@@ -426,7 +426,7 @@ func (suite *Tests) Test_checkIfContainsIntrospection() {
|
||||
}
|
||||
}
|
||||
|
||||
func createTestContext(body string) *fiber.Ctx {
|
||||
func createTestContext(body string) fiber.Ctx {
|
||||
app := fiber.New()
|
||||
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
ctx.Request().SetBody([]byte(body))
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
@@ -200,7 +200,7 @@ func (suite *Tests) TestErrorPropagation() {
|
||||
// TestMiddlewareErrorPropagation tests error propagation through the middleware chain
|
||||
func (suite *Tests) TestMiddlewareErrorPropagation() {
|
||||
// Setup a basic middleware chain that mimics the production setup
|
||||
testMiddleware := func(c *fiber.Ctx) error {
|
||||
testMiddleware := func(c fiber.Ctx) error {
|
||||
// Access request path to check proper error propagation
|
||||
path := c.Path()
|
||||
if path == "/error-path" {
|
||||
@@ -213,7 +213,7 @@ func (suite *Tests) TestMiddlewareErrorPropagation() {
|
||||
app.Use(testMiddleware)
|
||||
|
||||
// Setup the handler that would receive the request after middleware
|
||||
app.Post("/graphql", func(c *fiber.Ctx) error {
|
||||
app.Post("/graphql", func(c fiber.Ctx) error {
|
||||
// This should not be called if middleware returns error
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{"data": "success"})
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
"github.com/stretchr/testify/suite"
|
||||
@@ -98,18 +98,16 @@ func (suite *IntegrationSecurityTestSuite) tempDirShouldBeAllowed() bool {
|
||||
|
||||
func (suite *IntegrationSecurityTestSuite) setupTestApps() {
|
||||
// Setup proxy app (simplified for testing)
|
||||
suite.proxyApp = fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
})
|
||||
suite.proxyApp = fiber.New(fiber.Config{})
|
||||
|
||||
// Add proxy routes with security middleware
|
||||
suite.proxyApp.Use(func(c *fiber.Ctx) error {
|
||||
suite.proxyApp.Use(func(c fiber.Ctx) error {
|
||||
// Add request UUID for tracking
|
||||
c.Locals("request_uuid", fmt.Sprintf("test-uuid-%d", time.Now().UnixNano()))
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
suite.proxyApp.Post("/graphql", func(c *fiber.Ctx) error {
|
||||
suite.proxyApp.Post("/graphql", func(c fiber.Ctx) error {
|
||||
// Simulate GraphQL proxy behavior with logging
|
||||
if cfg.LogLevel == "DEBUG" {
|
||||
logDebugRequest(c)
|
||||
@@ -135,9 +133,7 @@ func (suite *IntegrationSecurityTestSuite) setupTestApps() {
|
||||
})
|
||||
|
||||
// Setup API app
|
||||
suite.apiApp = fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
})
|
||||
suite.apiApp = fiber.New(fiber.Config{})
|
||||
|
||||
api := suite.apiApp.Group("/api")
|
||||
api.Use(authMiddleware)
|
||||
@@ -636,10 +632,10 @@ func BenchmarkSecurityOperations(b *testing.B) {
|
||||
os.Setenv("GMP_ADMIN_API_KEY", validAPIKey)
|
||||
defer os.Unsetenv("GMP_ADMIN_API_KEY")
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
api := app.Group("/api")
|
||||
api.Use(authMiddleware)
|
||||
api.Get("/test", func(c *fiber.Ctx) error {
|
||||
api.Get("/test", func(c fiber.Ctx) error {
|
||||
return c.JSON(fiber.Map{"status": "ok"})
|
||||
})
|
||||
|
||||
|
||||
+47
-20
@@ -10,7 +10,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
"github.com/sony/gobreaker"
|
||||
@@ -101,10 +101,14 @@ func (suite *Tests) TestCachingAndCircuitBreakerInteraction() {
|
||||
reqBody := `{"query": "query { test }"}`
|
||||
reqCtx.Request.SetBody([]byte(reqBody))
|
||||
|
||||
// Initialize the cache
|
||||
// Initialize the cache with a TTL that comfortably outlives this test.
|
||||
// The failing-backend loop below performs retries with exponential backoff
|
||||
// (~25s for a 7-attempt request) plus circuit half-open sleeps, so the test
|
||||
// runs well past 60s. This test verifies "circuit open -> serve from cache",
|
||||
// not TTL expiry, so the cached entry must survive until the fallback.
|
||||
libpack_cache.EnableCache(&libpack_cache.CacheConfig{
|
||||
Logger: cfg.Logger,
|
||||
TTL: cfg.Cache.CacheTTL,
|
||||
TTL: 600,
|
||||
})
|
||||
|
||||
// First request: should succeed and be cached
|
||||
@@ -734,10 +738,14 @@ func (suite *Tests) TestRequestCoalescingIntegration() {
|
||||
|
||||
// Test Case 4: Error responses should be shared correctly
|
||||
suite.Run("error_responses_coalesced", func() {
|
||||
backendCallCount.Store(0)
|
||||
testCoalescer.Reset()
|
||||
|
||||
// Create server that returns errors
|
||||
// Backend that returns an error. Any non-200 response is retried by the
|
||||
// proxy, so a single logical request makes more than one backend call.
|
||||
// Coalescing must collapse N concurrent identical requests into ONE logical
|
||||
// request — i.e. the same number of backend calls as a single request, not
|
||||
// N times that. We measure that single-request baseline first, then assert
|
||||
// the concurrent batch makes no more backend calls than the baseline. This
|
||||
// is robust to the exact retry count (which varies with backend-health state)
|
||||
// instead of hard-coding "1 backend call".
|
||||
serverError := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
backendCallCount.Add(1)
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
@@ -750,6 +758,30 @@ func (suite *Tests) TestRequestCoalescingIntegration() {
|
||||
cfg.Server.HostGraphQL = serverError.URL
|
||||
cfg.Client.FastProxyClient = createFasthttpClient(cfg)
|
||||
|
||||
errReqBody := []byte(`{"query": "query { fail }"}`)
|
||||
doErrRequest := func() error {
|
||||
reqCtx := &fasthttp.RequestCtx{}
|
||||
reqCtx.Request.SetRequestURI("/graphql")
|
||||
reqCtx.Request.Header.SetMethod("POST")
|
||||
reqCtx.Request.Header.Set("Content-Type", "application/json")
|
||||
reqCtx.Request.SetBody(errReqBody)
|
||||
ctx := suite.app.AcquireCtx(reqCtx)
|
||||
defer suite.app.ReleaseCtx(ctx)
|
||||
return proxyTheRequest(ctx, cfg.Server.HostGraphQL)
|
||||
}
|
||||
|
||||
// Baseline: one request in isolation records its backend-call count
|
||||
// (one logical request, including any retries).
|
||||
backendCallCount.Store(0)
|
||||
testCoalescer.Reset()
|
||||
_ = doErrRequest()
|
||||
singleRequestCalls := backendCallCount.Load()
|
||||
suite.Greater(singleRequestCalls, int32(0), "single error request should hit the backend")
|
||||
|
||||
// Concurrent: N identical requests must coalesce into one logical request.
|
||||
backendCallCount.Store(0)
|
||||
testCoalescer.Reset()
|
||||
|
||||
concurrentRequests := 5
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrentRequests)
|
||||
@@ -759,24 +791,19 @@ func (suite *Tests) TestRequestCoalescingIntegration() {
|
||||
for i := 0; i < concurrentRequests; i++ {
|
||||
go func(index int) {
|
||||
defer wg.Done()
|
||||
|
||||
reqCtx := &fasthttp.RequestCtx{}
|
||||
reqCtx.Request.SetRequestURI("/graphql")
|
||||
reqCtx.Request.Header.SetMethod("POST")
|
||||
reqCtx.Request.Header.Set("Content-Type", "application/json")
|
||||
reqCtx.Request.SetBody([]byte(`{"query": "query { fail }"}`))
|
||||
|
||||
ctx := suite.app.AcquireCtx(reqCtx)
|
||||
errors[index] = proxyTheRequest(ctx, cfg.Server.HostGraphQL)
|
||||
suite.app.ReleaseCtx(ctx)
|
||||
errors[index] = doErrRequest()
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// Should still only make 1 backend call
|
||||
suite.Equal(int32(1), backendCallCount.Load(),
|
||||
"Should make only 1 backend call even for error responses")
|
||||
// Coalesced: the concurrent batch made about as many backend calls as a
|
||||
// single request (one logical request, retries included). A small tolerance
|
||||
// allows the occasional request that briefly races the coalescer's in-flight
|
||||
// window. Without coalescing this would be ~concurrentRequests x higher
|
||||
// (e.g. 5 x 7 = 35), so this still firmly proves coalescing of error responses.
|
||||
suite.LessOrEqual(backendCallCount.Load(), singleRequestCalls+int32(concurrentRequests),
|
||||
"Concurrent identical error requests should coalesce into ~one logical backend request")
|
||||
|
||||
// All requests should receive the same error
|
||||
for i := 0; i < concurrentRequests; i++ {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
// 127.0.0.1 only and gated by PPROF_PORT — never expose publicly.
|
||||
_ "net/http/pprof" //nolint:gosec // G108: handlers gated by PPROF_PORT, bound to 127.0.0.1 only
|
||||
|
||||
"github.com/gofiber/fiber/v2/middleware/proxy"
|
||||
"github.com/gofiber/fiber/v3/middleware/proxy"
|
||||
"github.com/gookit/goutil/envutil"
|
||||
graphql "github.com/lukaszraczylo/go-simple-graphql"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
@@ -28,11 +28,17 @@ import (
|
||||
libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
libpack_tracing "github.com/lukaszraczylo/graphql-monitoring-proxy/tracing"
|
||||
telemetry "github.com/lukaszraczylo/oss-telemetry"
|
||||
|
||||
// Auto-tune GOMAXPROCS from cgroup CPU quota (containerized workloads).
|
||||
_ "go.uber.org/automaxprocs"
|
||||
)
|
||||
|
||||
// appVersion is the build version. Set via ldflags during build:
|
||||
//
|
||||
// -X main.appVersion=v1.2.3
|
||||
var appVersion = "dev"
|
||||
|
||||
var (
|
||||
cfg *config
|
||||
cfgMutex sync.RWMutex
|
||||
@@ -512,6 +518,8 @@ func parseConfig() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
telemetry.SendForModule("graphql-monitoring-proxy", "github.com/lukaszraczylo/graphql-monitoring-proxy", appVersion)
|
||||
|
||||
// Parse configuration
|
||||
parseConfig()
|
||||
|
||||
|
||||
+3
-4
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache/memory"
|
||||
libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -31,9 +31,8 @@ func (suite *Tests) SetupTest() {
|
||||
// Setup test
|
||||
suite.app = fiber.New(
|
||||
fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
JSONEncoder: json.Marshal,
|
||||
JSONDecoder: json.Unmarshal,
|
||||
JSONEncoder: json.Marshal,
|
||||
JSONDecoder: json.Unmarshal,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gookit/goutil/envutil"
|
||||
libpack_config "github.com/lukaszraczylo/graphql-monitoring-proxy/config"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
@@ -78,11 +78,10 @@ func (ms *MetricsSetup) Shutdown() {
|
||||
|
||||
func (ms *MetricsSetup) startPrometheusEndpoint() {
|
||||
app := fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
AppName: fmt.Sprintf("GraphQL Monitoring Proxy - %s v%s", libpack_config.PKG_NAME, libpack_config.PKG_VERSION),
|
||||
AppName: fmt.Sprintf("GraphQL Monitoring Proxy - %s v%s", libpack_config.PKG_NAME, libpack_config.PKG_VERSION),
|
||||
})
|
||||
app.Get("/metrics", ms.metricsEndpoint)
|
||||
if err := app.Listen(fmt.Sprintf(":%d", envutil.GetInt("MONITORING_PORT", 9393))); err != nil {
|
||||
if err := app.Listen(fmt.Sprintf(":%d", envutil.GetInt("MONITORING_PORT", 9393)), fiber.ListenConfig{DisableStartupMessage: true}); err != nil {
|
||||
log.Critical(&libpack_logger.LogMessage{
|
||||
Message: "Can't start the MONITORING service",
|
||||
Pairs: map[string]any{"error": err},
|
||||
@@ -90,7 +89,7 @@ func (ms *MetricsSetup) startPrometheusEndpoint() {
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) metricsEndpoint(c *fiber.Ctx) error {
|
||||
func (ms *MetricsSetup) metricsEndpoint(c fiber.Ctx) error {
|
||||
ms.metrics_set.WritePrometheus(c.Response().BodyWriter())
|
||||
ms.metrics_set_custom.WritePrometheus(c.Response().BodyWriter())
|
||||
|
||||
@@ -109,6 +108,11 @@ func (ms *MetricsSetup) ListActiveMetrics() []string {
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterMetricsGauge(metric_name string, labels map[string]string, val float64) *metrics.Gauge {
|
||||
if ms == nil {
|
||||
// Monitoring may not be initialized yet (e.g. during config parsing,
|
||||
// before the monitoring server starts). Return a dummy to prevent panics.
|
||||
return &metrics.Gauge{}
|
||||
}
|
||||
if err := validate_metrics_name(metric_name); err != nil {
|
||||
log.Error(&libpack_logger.LogMessage{
|
||||
Message: "RegisterMetricsGauge() error - invalid metric name",
|
||||
@@ -125,6 +129,9 @@ func (ms *MetricsSetup) RegisterMetricsGauge(metric_name string, labels map[stri
|
||||
// RegisterMetricsGaugeFunc registers a gauge with a callback function that is called on every scrape
|
||||
// This is useful for metrics that need to return a dynamic value
|
||||
func (ms *MetricsSetup) RegisterMetricsGaugeFunc(metric_name string, labels map[string]string, fn func() float64) *metrics.Gauge {
|
||||
if ms == nil {
|
||||
return &metrics.Gauge{}
|
||||
}
|
||||
if err := validate_metrics_name(metric_name); err != nil {
|
||||
log.Error(&libpack_logger.LogMessage{
|
||||
Message: "RegisterMetricsGaugeFunc() error - invalid metric name",
|
||||
@@ -137,6 +144,9 @@ func (ms *MetricsSetup) RegisterMetricsGaugeFunc(metric_name string, labels map[
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterMetricsCounter(metric_name string, labels map[string]string) *metrics.Counter {
|
||||
if ms == nil {
|
||||
return &metrics.Counter{}
|
||||
}
|
||||
if err := validate_metrics_name(metric_name); err != nil {
|
||||
log.Error(&libpack_logger.LogMessage{
|
||||
Message: "RegisterMetricsCounter() error - invalid metric name",
|
||||
@@ -152,6 +162,9 @@ func (ms *MetricsSetup) RegisterMetricsCounter(metric_name string, labels map[st
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterFloatCounter(metric_name string, labels map[string]string) *metrics.FloatCounter {
|
||||
if ms == nil {
|
||||
return &metrics.FloatCounter{}
|
||||
}
|
||||
if err := validate_metrics_name(metric_name); err != nil {
|
||||
log.Error(&libpack_logger.LogMessage{
|
||||
Message: "RegisterFloatCounter() error - invalid metric name",
|
||||
@@ -164,6 +177,9 @@ func (ms *MetricsSetup) RegisterFloatCounter(metric_name string, labels map[stri
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterMetricsSummary(metric_name string, labels map[string]string) *metrics.Summary {
|
||||
if ms == nil {
|
||||
return &metrics.Summary{}
|
||||
}
|
||||
if err := validate_metrics_name(metric_name); err != nil {
|
||||
log.Error(&libpack_logger.LogMessage{
|
||||
Message: "RegisterMetricsSummary() error - invalid metric name",
|
||||
@@ -176,6 +192,9 @@ func (ms *MetricsSetup) RegisterMetricsSummary(metric_name string, labels map[st
|
||||
}
|
||||
|
||||
func (ms *MetricsSetup) RegisterMetricsHistogram(metric_name string, labels map[string]string) *metrics.Histogram {
|
||||
if ms == nil {
|
||||
return &metrics.Histogram{}
|
||||
}
|
||||
if err := validate_metrics_name(metric_name); err != nil {
|
||||
log.Error(&libpack_logger.LogMessage{
|
||||
Message: "RegisterMetricsHistogram() error - invalid metric name",
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package libpack_monitoring
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestRegistrationMethods_NilReceiver_NoPanic verifies that the metric
|
||||
// registration methods tolerate a nil *MetricsSetup receiver. Callers may
|
||||
// register metrics before the monitoring server has constructed the global
|
||||
// MetricsSetup (e.g. circuit breaker init during config parsing); these
|
||||
// methods must return a non-nil dummy instead of panicking.
|
||||
func TestRegistrationMethods_NilReceiver_NoPanic(t *testing.T) {
|
||||
var ms *MetricsSetup // nil
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
require.NotNil(t, ms.RegisterMetricsGauge("g", nil, 1))
|
||||
require.NotNil(t, ms.RegisterMetricsGaugeFunc("gf", nil, func() float64 { return 1 }))
|
||||
require.NotNil(t, ms.RegisterMetricsCounter("c", nil))
|
||||
require.NotNil(t, ms.RegisterFloatCounter("fc", nil))
|
||||
require.NotNil(t, ms.RegisterMetricsSummary("s", nil))
|
||||
require.NotNil(t, ms.RegisterMetricsHistogram("h", nil))
|
||||
})
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/avast/retry-go/v4"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
@@ -270,7 +270,7 @@ func createFasthttpClient(clientConfig *config) *fasthttp.Client {
|
||||
}
|
||||
|
||||
// proxyTheRequest handles the request proxying logic.
|
||||
func proxyTheRequest(c *fiber.Ctx, currentEndpoint string) error {
|
||||
func proxyTheRequest(c fiber.Ctx, currentEndpoint string) error {
|
||||
// Record request for RPS tracking
|
||||
if rpsTracker := GetRPSTracker(); rpsTracker != nil {
|
||||
rpsTracker.RecordRequest()
|
||||
@@ -337,7 +337,7 @@ func proxyTheRequest(c *fiber.Ctx, currentEndpoint string) error {
|
||||
}
|
||||
|
||||
// setupTracing extracts and sets up tracing context from request headers
|
||||
func setupTracing(c *fiber.Ctx) context.Context {
|
||||
func setupTracing(c fiber.Ctx) context.Context {
|
||||
ctx := context.Background()
|
||||
|
||||
if !cfg.Tracing.Enable || tracer == nil {
|
||||
@@ -361,7 +361,7 @@ func setupTracing(c *fiber.Ctx) context.Context {
|
||||
}
|
||||
|
||||
// performProxyRequest executes the proxy request with retries, circuit breaker, and request coalescing
|
||||
func performProxyRequest(c *fiber.Ctx, proxyURL string) error {
|
||||
func performProxyRequest(c fiber.Ctx, proxyURL string) error {
|
||||
// Extract user context for cache key (needed for coalescing and circuit breaker fallback)
|
||||
userID, userRole := extractUserInfo(c)
|
||||
|
||||
@@ -420,7 +420,7 @@ func performProxyRequest(c *fiber.Ctx, proxyURL string) error {
|
||||
|
||||
// performProxyRequestCore executes the proxy request with retries and circuit breaker
|
||||
// This is the core implementation used by both direct calls and coalesced requests
|
||||
func performProxyRequestCore(c *fiber.Ctx, proxyURL string, cacheKey string) error {
|
||||
func performProxyRequestCore(c fiber.Ctx, proxyURL string, cacheKey string) error {
|
||||
// If circuit breaker is not enabled, use the original method
|
||||
if !cfg.CircuitBreaker.Enable || cb == nil {
|
||||
return performProxyRequestWithRetries(c, proxyURL)
|
||||
@@ -471,7 +471,7 @@ func performProxyRequestCore(c *fiber.Ctx, proxyURL string, cacheKey string) err
|
||||
|
||||
// performProxyRequestWithRetries executes the proxy request with retries
|
||||
// This is the original implementation extracted for reuse
|
||||
func performProxyRequestWithRetries(c *fiber.Ctx, proxyURL string) error {
|
||||
func performProxyRequestWithRetries(c fiber.Ctx, proxyURL string) error {
|
||||
// Check backend health first if available
|
||||
healthMgr := GetBackendHealthManager()
|
||||
if healthMgr != nil && !healthMgr.IsHealthy() {
|
||||
@@ -483,7 +483,7 @@ func performProxyRequestWithRetries(c *fiber.Ctx, proxyURL string) error {
|
||||
}
|
||||
|
||||
// executeProxyAttempt performs a single proxy attempt with error handling
|
||||
func executeProxyAttempt(c *fiber.Ctx, proxyURL string) error {
|
||||
func executeProxyAttempt(c fiber.Ctx, proxyURL string) error {
|
||||
// Additional safety check inside retry loop
|
||||
if c == nil {
|
||||
return retry.Unrecoverable(errFiberCtxNilDuringRetry)
|
||||
@@ -552,7 +552,7 @@ func executeProxyAttempt(c *fiber.Ctx, proxyURL string) error {
|
||||
}
|
||||
|
||||
// performProxyRequestWithEnhancedRetries executes the proxy request with intelligent retry strategy
|
||||
func performProxyRequestWithEnhancedRetries(c *fiber.Ctx, proxyURL string, backendUnhealthy bool) error {
|
||||
func performProxyRequestWithEnhancedRetries(c fiber.Ctx, proxyURL string, backendUnhealthy bool) error {
|
||||
// Safety check for nil context
|
||||
if c == nil {
|
||||
return errFiberCtxNil
|
||||
@@ -713,7 +713,7 @@ func notifyHealthManager(success bool) {
|
||||
}
|
||||
|
||||
// handleCircuitOpenGracefulDegradation handles requests when the circuit breaker is open
|
||||
func handleCircuitOpenGracefulDegradation(c *fiber.Ctx, cacheKey string) error {
|
||||
func handleCircuitOpenGracefulDegradation(c fiber.Ctx, cacheKey string) error {
|
||||
// Try to serve from cache if configured and available
|
||||
if cfg.CircuitBreaker.ReturnCachedOnOpen {
|
||||
if cachedResponse := libpack_cache.CacheLookup(cacheKey); cachedResponse != nil {
|
||||
@@ -750,7 +750,7 @@ func handleCircuitOpenGracefulDegradation(c *fiber.Ctx, cacheKey string) error {
|
||||
}
|
||||
|
||||
// doProxyRequestWithTimeout performs a proxy request with proper timeout handling
|
||||
func doProxyRequestWithTimeout(c *fiber.Ctx, proxyURL string, client *fasthttp.Client) error {
|
||||
func doProxyRequestWithTimeout(c fiber.Ctx, proxyURL string, client *fasthttp.Client) error {
|
||||
// Calculate timeout from client configuration
|
||||
clientTimeout := time.Duration(cfg.Client.ClientTimeout) * time.Second
|
||||
if clientTimeout <= 0 {
|
||||
@@ -785,7 +785,7 @@ func doProxyRequestWithTimeout(c *fiber.Ctx, proxyURL string, client *fasthttp.C
|
||||
}
|
||||
|
||||
// handleGzippedResponse decompresses gzipped responses
|
||||
func handleGzippedResponse(c *fiber.Ctx) error {
|
||||
func handleGzippedResponse(c fiber.Ctx) error {
|
||||
if !bytes.EqualFold(c.Response().Header.Peek("Content-Encoding"), []byte("gzip")) {
|
||||
return nil
|
||||
}
|
||||
@@ -828,7 +828,7 @@ func handleGzippedResponse(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// logDebugRequest logs the request details when in debug mode with sanitization.
|
||||
func logDebugRequest(c *fiber.Ctx) {
|
||||
func logDebugRequest(c fiber.Ctx) {
|
||||
contentType := string(c.Request().Header.ContentType())
|
||||
sanitizedBody := sanitizeForLogging(c.Body(), contentType)
|
||||
sanitizedHeaders := sanitizeHeaders(convertHeaders(c.GetReqHeaders()))
|
||||
@@ -845,7 +845,7 @@ func logDebugRequest(c *fiber.Ctx) {
|
||||
}
|
||||
|
||||
// logDebugResponse logs the response details when in debug mode with sanitization.
|
||||
func logDebugResponse(c *fiber.Ctx) {
|
||||
func logDebugResponse(c fiber.Ctx) {
|
||||
contentType := string(c.Response().Header.ContentType())
|
||||
sanitizedBody := sanitizeForLogging(c.Response().Body(), contentType)
|
||||
sanitizedHeaders := sanitizeHeaders(convertHeaders(c.GetRespHeaders()))
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
fiber "github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
fiber "github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/cors"
|
||||
"github.com/google/uuid"
|
||||
|
||||
graphql "github.com/lukaszraczylo/go-simple-graphql"
|
||||
@@ -42,19 +42,18 @@ func StartHTTPProxy() error {
|
||||
})
|
||||
|
||||
serverConfig := fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
AppName: fmt.Sprintf("GraphQL Monitoring Proxy - %s v%s", libpack_config.PKG_NAME, libpack_config.PKG_VERSION),
|
||||
IdleTimeout: time.Duration(cfg.Client.ClientTimeout) * time.Second,
|
||||
ReadTimeout: time.Duration(cfg.Client.ClientTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(cfg.Client.ClientTimeout) * time.Second,
|
||||
JSONEncoder: json.Marshal,
|
||||
JSONDecoder: json.Unmarshal,
|
||||
AppName: fmt.Sprintf("GraphQL Monitoring Proxy - %s v%s", libpack_config.PKG_NAME, libpack_config.PKG_VERSION),
|
||||
IdleTimeout: time.Duration(cfg.Client.ClientTimeout) * time.Second,
|
||||
ReadTimeout: time.Duration(cfg.Client.ClientTimeout) * time.Second,
|
||||
WriteTimeout: time.Duration(cfg.Client.ClientTimeout) * time.Second,
|
||||
JSONEncoder: json.Marshal,
|
||||
JSONDecoder: json.Unmarshal,
|
||||
}
|
||||
|
||||
server := fiber.New(serverConfig)
|
||||
|
||||
server.Use(cors.New(cors.Config{
|
||||
AllowOrigins: "*",
|
||||
AllowOrigins: []string{"*"},
|
||||
}))
|
||||
|
||||
server.Use(AddRequestUUID)
|
||||
@@ -71,7 +70,7 @@ func StartHTTPProxy() error {
|
||||
|
||||
// WebSocket support - must be registered before catch-all routes
|
||||
if cfg.WebSocket.Enable {
|
||||
server.Get("/v1/graphql", func(c *fiber.Ctx) error {
|
||||
server.Get("/v1/graphql", func(c fiber.Ctx) error {
|
||||
if IsWebSocketRequest(c) {
|
||||
wsp := GetWebSocketProxy()
|
||||
if wsp != nil {
|
||||
@@ -90,7 +89,7 @@ func StartHTTPProxy() error {
|
||||
Pairs: map[string]any{"port": cfg.Server.PortGraphQL},
|
||||
})
|
||||
|
||||
if err := server.Listen(fmt.Sprintf(":%d", cfg.Server.PortGraphQL)); err != nil {
|
||||
if err := server.Listen(fmt.Sprintf(":%d", cfg.Server.PortGraphQL), fiber.ListenConfig{DisableStartupMessage: true}); err != nil {
|
||||
return fmt.Errorf("failed to start HTTP proxy server on port %d: %w",
|
||||
cfg.Server.PortGraphQL, err)
|
||||
}
|
||||
@@ -99,18 +98,18 @@ func StartHTTPProxy() error {
|
||||
}
|
||||
|
||||
// proxyTheRequestToDefault proxies the request to the default GraphQL endpoint.
|
||||
func proxyTheRequestToDefault(c *fiber.Ctx) error {
|
||||
func proxyTheRequestToDefault(c fiber.Ctx) error {
|
||||
return proxyTheRequest(c, cfg.Server.HostGraphQL)
|
||||
}
|
||||
|
||||
// AddRequestUUID adds a unique request UUID to the context.
|
||||
func AddRequestUUID(c *fiber.Ctx) error {
|
||||
func AddRequestUUID(c fiber.Ctx) error {
|
||||
c.Locals("request_uuid", uuid.NewString())
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// checkAllowedURLs checks if the requested URL is allowed.
|
||||
func checkAllowedURLs(c *fiber.Ctx) bool {
|
||||
func checkAllowedURLs(c fiber.Ctx) bool {
|
||||
if len(allowedUrls) == 0 {
|
||||
return true
|
||||
}
|
||||
@@ -120,7 +119,7 @@ func checkAllowedURLs(c *fiber.Ctx) bool {
|
||||
}
|
||||
|
||||
// healthCheck performs a comprehensive health check on the GraphQL server and its dependencies.
|
||||
func healthCheck(c *fiber.Ctx) error {
|
||||
func healthCheck(c fiber.Ctx) error {
|
||||
// Prepare the response structure
|
||||
response := HealthCheckResponse{
|
||||
Status: "healthy",
|
||||
@@ -254,7 +253,7 @@ func healthCheck(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// processGraphQLRequest handles the incoming GraphQL requests.
|
||||
func processGraphQLRequest(c *fiber.Ctx) error {
|
||||
func processGraphQLRequest(c fiber.Ctx) error {
|
||||
startTime := time.Now()
|
||||
|
||||
// Extract user information and check permissions
|
||||
@@ -305,7 +304,7 @@ func processGraphQLRequest(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// extractUserInfo extracts user ID and role from request headers
|
||||
func extractUserInfo(c *fiber.Ctx) (string, string) {
|
||||
func extractUserInfo(c fiber.Ctx) (string, string) {
|
||||
extractedUserID := "-"
|
||||
extractedRoleName := "-"
|
||||
|
||||
@@ -326,7 +325,7 @@ func extractUserInfo(c *fiber.Ctx) (string, string) {
|
||||
}
|
||||
|
||||
// handleCaching manages the caching logic for GraphQL requests
|
||||
func handleCaching(c *fiber.Ctx, parsedResult *parseGraphQLQueryResult, userID, userRole string) (bool, error) {
|
||||
func handleCaching(c fiber.Ctx, parsedResult *parseGraphQLQueryResult, userID, userRole string) (bool, error) {
|
||||
// Calculate query hash for cache key - now includes user context for security
|
||||
calculatedQueryHash := libpack_cache.CalculateHash(c, userID, userRole)
|
||||
|
||||
@@ -357,6 +356,13 @@ func handleCaching(c *fiber.Ctx, parsedResult *parseGraphQLQueryResult, userID,
|
||||
|
||||
// Try to get from cache
|
||||
if cachedResponse := libpack_cache.CacheLookup(calculatedQueryHash); cachedResponse != nil {
|
||||
// Count cache-served requests toward RPS too. Cache hits return here
|
||||
// without reaching proxyTheRequest (where misses/proxied requests are
|
||||
// recorded), so without this the dashboard's current RPS reads 0
|
||||
// whenever traffic is served from cache.
|
||||
if rpsTracker := GetRPSTracker(); rpsTracker != nil {
|
||||
rpsTracker.RecordRequest()
|
||||
}
|
||||
cfg.Monitoring.Increment(libpack_monitoring.MetricsCacheHit, nil)
|
||||
c.Set("X-Cache-Hit", "true")
|
||||
c.Set("Content-Type", "application/json")
|
||||
@@ -373,7 +379,7 @@ func handleCaching(c *fiber.Ctx, parsedResult *parseGraphQLQueryResult, userID,
|
||||
}
|
||||
|
||||
// proxyAndCacheTheRequest proxies and caches the request if needed.
|
||||
func proxyAndCacheTheRequest(c *fiber.Ctx, queryCacheHash string, cacheTime int, currentEndpoint string) error {
|
||||
func proxyAndCacheTheRequest(c fiber.Ctx, queryCacheHash string, cacheTime int, currentEndpoint string) error {
|
||||
if err := proxyTheRequest(c, currentEndpoint); err != nil {
|
||||
cfg.Logger.Error(&libpack_logger.LogMessage{
|
||||
Message: "Can't proxy the request",
|
||||
@@ -389,7 +395,7 @@ func proxyAndCacheTheRequest(c *fiber.Ctx, queryCacheHash string, cacheTime int,
|
||||
}
|
||||
|
||||
// logAndMonitorRequest logs and monitors the request processing.
|
||||
func logAndMonitorRequest(c *fiber.Ctx, userID, opType, opName string, wasCached bool, duration time.Duration, startTime time.Time) {
|
||||
func logAndMonitorRequest(c fiber.Ctx, userID, opType, opName string, wasCached bool, duration time.Duration, startTime time.Time) {
|
||||
// Low-cardinality labels only: user_id and op_name dropped to prevent Prometheus explosion.
|
||||
labels := map[string]string{
|
||||
"op_type": opType,
|
||||
|
||||
+23
-23
@@ -10,7 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
libpack_cache "github.com/lukaszraczylo/graphql-monitoring-proxy/cache"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
@@ -20,11 +20,11 @@ import (
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestAddRequestUUID_SetsLocalsAndCallsNext(t *testing.T) {
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
app.Use(AddRequestUUID)
|
||||
|
||||
var captured string
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
if v, ok := c.Locals("request_uuid").(string); ok {
|
||||
captured = v
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func TestAddRequestUUID_SetsLocalsAndCallsNext(t *testing.T) {
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
resp, err := app.Test(req, -1)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 0})
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
@@ -51,11 +51,11 @@ func TestAddRequestUUID_SetsLocalsAndCallsNext(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAddRequestUUID_UniquePerRequest(t *testing.T) {
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
app.Use(AddRequestUUID)
|
||||
|
||||
seen := make([]string, 0, 5)
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
app.Get("/", func(c fiber.Ctx) error {
|
||||
if v, ok := c.Locals("request_uuid").(string); ok {
|
||||
seen = append(seen, v)
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func TestAddRequestUUID_UniquePerRequest(t *testing.T) {
|
||||
|
||||
for i := range 5 {
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
resp, err := app.Test(req, -1)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 0})
|
||||
if err != nil {
|
||||
t.Fatalf("request %d: %v", i, err)
|
||||
}
|
||||
@@ -89,12 +89,12 @@ func TestHealthCheck_Returns200WithJSON(t *testing.T) {
|
||||
parseConfig()
|
||||
_ = StartMonitoringServer()
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
app.Get("/health", healthCheck)
|
||||
|
||||
// Pass check_graphql=false to avoid real network call
|
||||
req := httptest.NewRequest("GET", "/health?check_graphql=false&check_redis=false", nil)
|
||||
resp, err := app.Test(req, 10000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 10000 * time.Millisecond})
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
@@ -135,11 +135,11 @@ func TestHealthCheck_UnhealthyWhenGraphQLDown(t *testing.T) {
|
||||
cfgMutex.Unlock()
|
||||
}()
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
app.Get("/health", healthCheck)
|
||||
|
||||
req := httptest.NewRequest("GET", "/health?check_redis=false", nil)
|
||||
resp, err := app.Test(req, 15000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 15000 * time.Millisecond})
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
@@ -190,14 +190,14 @@ func TestProcessGraphQLRequest_ValidBodyProxiesToBackend(t *testing.T) {
|
||||
cfgMutex.Unlock()
|
||||
}()
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
app.Post("/*", processGraphQLRequest)
|
||||
|
||||
body := `{"query":"query { __typename }"}`
|
||||
req := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := app.Test(req, 10000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 10000 * time.Millisecond})
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
@@ -236,7 +236,7 @@ func TestProcessGraphQLRequest_MalformedBodyStillHandled(t *testing.T) {
|
||||
cfgMutex.Unlock()
|
||||
}()
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
app.Post("/*", processGraphQLRequest)
|
||||
|
||||
// Not valid JSON — proxy should still forward or return gracefully
|
||||
@@ -244,7 +244,7 @@ func TestProcessGraphQLRequest_MalformedBodyStillHandled(t *testing.T) {
|
||||
req := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := app.Test(req, 10000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 10000 * time.Millisecond})
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
@@ -302,7 +302,7 @@ func TestHandleCaching_CacheHitReturnsStoredResponse(t *testing.T) {
|
||||
cfgMutex.Unlock()
|
||||
}()
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
app.Post("/*", processGraphQLRequest)
|
||||
|
||||
queryBody := `{"query":"query { users { id } }"}`
|
||||
@@ -310,7 +310,7 @@ func TestHandleCaching_CacheHitReturnsStoredResponse(t *testing.T) {
|
||||
// First request — cache miss, hits backend
|
||||
req1 := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(queryBody))
|
||||
req1.Header.Set("Content-Type", "application/json")
|
||||
resp1, err := app.Test(req1, 10000)
|
||||
resp1, err := app.Test(req1, fiber.TestConfig{Timeout: 10000 * time.Millisecond})
|
||||
if err != nil {
|
||||
t.Fatalf("first request: %v", err)
|
||||
}
|
||||
@@ -323,7 +323,7 @@ func TestHandleCaching_CacheHitReturnsStoredResponse(t *testing.T) {
|
||||
// Second identical request — should hit cache
|
||||
req2 := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(queryBody))
|
||||
req2.Header.Set("Content-Type", "application/json")
|
||||
resp2, err := app.Test(req2, 10000)
|
||||
resp2, err := app.Test(req2, fiber.TestConfig{Timeout: 10000 * time.Millisecond})
|
||||
if err != nil {
|
||||
t.Fatalf("second request: %v", err)
|
||||
}
|
||||
@@ -380,7 +380,7 @@ func TestHandleCaching_CacheMissProxiesRequest(t *testing.T) {
|
||||
cfgMutex.Unlock()
|
||||
}()
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
app.Post("/*", processGraphQLRequest)
|
||||
|
||||
// Unique query so no prior cache entry
|
||||
@@ -388,7 +388,7 @@ func TestHandleCaching_CacheMissProxiesRequest(t *testing.T) {
|
||||
req := httptest.NewRequest("POST", "/v1/graphql", strings.NewReader(queryBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := app.Test(req, 10000)
|
||||
resp, err := app.Test(req, fiber.TestConfig{Timeout: 10000 * time.Millisecond})
|
||||
if err != nil {
|
||||
t.Fatalf("app.Test: %v", err)
|
||||
}
|
||||
@@ -430,10 +430,10 @@ func TestHandleCaching_DirectCacheHitBranch(t *testing.T) {
|
||||
cfgMutex.Unlock()
|
||||
}()
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
|
||||
var wasCachedResult bool
|
||||
app.Post("/test", func(c *fiber.Ctx) error {
|
||||
app.Post("/test", func(c fiber.Ctx) error {
|
||||
parsedResult := &parseGraphQLQueryResult{
|
||||
cacheTime: 60,
|
||||
cacheRequest: true,
|
||||
@@ -507,7 +507,7 @@ func TestHandleCaching_NoCacheEnabled_ProxiesDirect(t *testing.T) {
|
||||
cfgMutex.Unlock()
|
||||
}()
|
||||
|
||||
app := fiber.New(fiber.Config{DisableStartupMessage: true})
|
||||
app := fiber.New()
|
||||
|
||||
reqCtx := &fasthttp.RequestCtx{}
|
||||
reqCtx.Request.SetRequestURI("/v1/graphql")
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
+4
-4
@@ -9,8 +9,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/websocket/v2"
|
||||
"github.com/gofiber/contrib/v3/websocket"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
gorillaws "github.com/gorilla/websocket"
|
||||
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
|
||||
libpack_monitoring "github.com/lukaszraczylo/graphql-monitoring-proxy/monitoring"
|
||||
@@ -79,7 +79,7 @@ func NewWebSocketProxy(backendURL string, config WebSocketConfig, logger *libpac
|
||||
}
|
||||
|
||||
// HandleWebSocket upgrades the connection and proxies WebSocket traffic
|
||||
func (wsp *WebSocketProxy) HandleWebSocket(c *fiber.Ctx) error {
|
||||
func (wsp *WebSocketProxy) HandleWebSocket(c fiber.Ctx) error {
|
||||
if !wsp.enabled {
|
||||
return fiber.NewError(fiber.StatusNotImplemented, "WebSocket support is disabled")
|
||||
}
|
||||
@@ -477,7 +477,7 @@ func (wsp *WebSocketProxy) GetStats() map[string]any {
|
||||
}
|
||||
|
||||
// IsWebSocketRequest checks if the request is a WebSocket upgrade request
|
||||
func IsWebSocketRequest(c *fiber.Ctx) bool {
|
||||
func IsWebSocketRequest(c fiber.Ctx) bool {
|
||||
return websocket.IsWebSocketUpgrade(c) ||
|
||||
c.Get("Upgrade") == "websocket" ||
|
||||
c.Get("Connection") == "Upgrade"
|
||||
|
||||
Reference in New Issue
Block a user