77 "io"
88 "net/http"
99 "regexp"
10+ "sort"
1011 "strings"
1112 "time"
1213
@@ -20,6 +21,12 @@ import (
2021// wafBreaker protects against cascading failures when the WAF service is down.
2122var wafBreaker = appMW .NewCircuitBreaker (3 , 30 * time .Second )
2223
24+ // Shared HTTP clients — reused across requests to avoid per-request allocation.
25+ var (
26+ wafClient = & http.Client {Timeout : 3 * time .Second }
27+ probeClient = & http.Client {Timeout : 1 * time .Second }
28+ )
29+
2330type AnalyticsHandlers struct {
2431 db * sql.DB
2532 cfg * config.Config
@@ -50,8 +57,7 @@ func (h *AnalyticsHandlers) Register(r chi.Router, authMW func(http.Handler) htt
5057
5158func (h * AnalyticsHandlers ) Status (w http.ResponseWriter , r * http.Request ) {
5259 proxyStatus := "error"
53- client := & http.Client {Timeout : 1 * time .Second }
54- resp , err := client .Get (h .cfg .ProxyURL )
60+ resp , err := probeClient .Get (h .cfg .ProxyURL )
5561 if err == nil {
5662 resp .Body .Close ()
5763 if resp .StatusCode == 400 || resp .StatusCode == 200 {
@@ -284,8 +290,7 @@ func (h *AnalyticsHandlers) WAFStats(w http.ResponseWriter, r *http.Request) {
284290 writeError (w , http .StatusServiceUnavailable , "WAF service circuit open — retrying soon" )
285291 return
286292 }
287- client := & http.Client {Timeout : 3 * time .Second }
288- resp , err := client .Get (h .cfg .WAFURL + "/stats" )
293+ resp , err := wafClient .Get (h .cfg .WAFURL + "/stats" )
289294 if err != nil {
290295 wafBreaker .RecordFailure ()
291296 writeError (w , http .StatusBadGateway , "WAF service unreachable" )
@@ -308,9 +313,8 @@ func (h *AnalyticsHandlers) ResetCounters(w http.ResponseWriter, r *http.Request
308313 deleted , _ = res .RowsAffected ()
309314 }
310315
311- client := & http.Client {Timeout : 3 * time .Second }
312316 wafReset := false
313- if resp , err := client .Post (h .cfg .WAFURL + "/reset" , "application/json" , nil ); err == nil {
317+ if resp , err := wafClient .Post (h .cfg .WAFURL + "/reset" , "application/json" , nil ); err == nil {
314318 resp .Body .Close ()
315319 wafReset = resp .StatusCode == 200
316320 }
@@ -320,13 +324,16 @@ func (h *AnalyticsHandlers) ResetCounters(w http.ResponseWriter, r *http.Request
320324func (h * AnalyticsHandlers ) DashboardSummary (w http.ResponseWriter , r * http.Request ) {
321325 result := map [string ]any {}
322326
323- var totalReqs , blockedReqs , todayReqs , todayBlocked , ipBLCount , domainBLCount int
324- h .db .QueryRow ("SELECT COUNT(*) FROM proxy_logs" ).Scan (& totalReqs ) //nolint:errcheck
325- h .db .QueryRow ("SELECT COUNT(*) FROM proxy_logs WHERE status LIKE '%DENIED%' OR status LIKE '%403%' OR status LIKE '%BLOCKED%'" ).Scan (& blockedReqs ) //nolint:errcheck
326-
327327 today := time .Now ().Format ("2006-01-02" )
328- h .db .QueryRow ("SELECT COUNT(*) FROM proxy_logs WHERE timestamp >= ?" , today ).Scan (& todayReqs ) //nolint:errcheck
329- h .db .QueryRow ("SELECT COUNT(*) FROM proxy_logs WHERE (status LIKE '%DENIED%' OR status LIKE '%403%') AND timestamp >= ?" , today ).Scan (& todayBlocked ) //nolint:errcheck
328+ var totalReqs , blockedReqs , todayReqs , todayBlocked int
329+ h .db .QueryRow (`SELECT
330+ COUNT(*),
331+ SUM(CASE WHEN status LIKE '%DENIED%' OR status LIKE '%403%' OR status LIKE '%BLOCKED%' THEN 1 ELSE 0 END),
332+ SUM(CASE WHEN timestamp >= ? THEN 1 ELSE 0 END),
333+ SUM(CASE WHEN (status LIKE '%DENIED%' OR status LIKE '%403%') AND timestamp >= ? THEN 1 ELSE 0 END)
334+ FROM proxy_logs` , today , today ).Scan (& totalReqs , & blockedReqs , & todayReqs , & todayBlocked ) //nolint:errcheck
335+
336+ var ipBLCount , domainBLCount int
330337
331338 result ["total_requests" ] = totalReqs
332339 result ["blocked_requests" ] = blockedReqs
@@ -351,6 +358,7 @@ func (h *AnalyticsHandlers) DashboardSummary(w http.ResponseWriter, r *http.Requ
351358
352359 h .db .QueryRow ("SELECT COUNT(*) FROM ip_blacklist" ).Scan (& ipBLCount ) //nolint:errcheck
353360 h .db .QueryRow ("SELECT COUNT(*) FROM domain_blacklist" ).Scan (& domainBLCount ) //nolint:errcheck
361+ // Note: these two are on different tables so cannot be combined into the proxy_logs query above.
354362 result ["ip_blacklist_count" ] = ipBLCount
355363 result ["domain_blacklist_count" ] = domainBLCount
356364
@@ -408,7 +416,6 @@ func (h *AnalyticsHandlers) DashboardSummary(w http.ResponseWriter, r *http.Requ
408416 result ["recent_blocks" ] = recentBlocks
409417
410418 // WAF stats
411- wafClient := & http.Client {Timeout : 2 * time .Second }
412419 if resp , err := wafClient .Get (h .cfg .WAFURL + "/stats" ); err == nil {
413420 var wafData any
414421 json .NewDecoder (resp .Body ).Decode (& wafData ) //nolint:errcheck
@@ -502,14 +509,9 @@ func (h *AnalyticsHandlers) ShadowIT(w http.ResponseWriter, r *http.Request) {
502509 for _ , e := range services {
503510 result = append (result , e )
504511 }
505- // Simple sort by requests descending.
506- for i := 0 ; i < len (result ); i ++ {
507- for j := i + 1 ; j < len (result ); j ++ {
508- if result [j ].Requests > result [i ].Requests {
509- result [i ], result [j ] = result [j ], result [i ]
510- }
511- }
512- }
512+ sort .Slice (result , func (i , j int ) bool {
513+ return result [i ].Requests > result [j ].Requests
514+ })
513515 if result == nil {
514516 result = []* entry {}
515517 }
@@ -640,8 +642,7 @@ func (h *AnalyticsHandlers) WAFCategories(w http.ResponseWriter, r *http.Request
640642 writeError (w , http .StatusServiceUnavailable , "WAF circuit open" )
641643 return
642644 }
643- client := & http.Client {Timeout : 3 * time .Second }
644- resp , err := client .Get (h .cfg .WAFURL + "/categories" )
645+ resp , err := wafClient .Get (h .cfg .WAFURL + "/categories" )
645646 if err != nil {
646647 wafBreaker .RecordFailure ()
647648 writeError (w , http .StatusBadGateway , "WAF unreachable" )
@@ -660,8 +661,7 @@ func (h *AnalyticsHandlers) WAFCategoryToggle(w http.ResponseWriter, r *http.Req
660661 writeError (w , http .StatusServiceUnavailable , "WAF circuit open" )
661662 return
662663 }
663- client := & http.Client {Timeout : 3 * time .Second }
664- resp , err := client .Post (h .cfg .WAFURL + "/categories/toggle" , "application/json" , r .Body )
664+ resp , err := wafClient .Post (h .cfg .WAFURL + "/categories/toggle" , "application/json" , r .Body )
665665 if err != nil {
666666 wafBreaker .RecordFailure ()
667667 writeError (w , http .StatusBadGateway , "WAF unreachable" )
0 commit comments