diff options
| author | William Hergès <william@herges.fr> | 2025-12-30 18:59:30 +0100 |
|---|---|---|
| committer | William Hergès <william@herges.fr> | 2025-12-30 18:59:30 +0100 |
| commit | 1c4ec0e4fe4a765dad1db9ab332ebfdcc8c2b11e (patch) | |
| tree | cced72df8b1db7827ce2fa28361fb18130e1304f | |
| parent | 87b27187b93912fc22e61fd298606af7d7bce710 (diff) | |
feat(storage): scope stats per ip instead of target
| -rw-r--r-- | backend/admin.go | 10 | ||||
| -rw-r--r-- | backend/router.go | 11 | ||||
| -rw-r--r-- | backend/storage/stats.go | 48 |
3 files changed, 40 insertions, 29 deletions
diff --git a/backend/admin.go b/backend/admin.go index 84b5c0f..b5cd695 100644 --- a/backend/admin.go +++ b/backend/admin.go @@ -5,7 +5,6 @@ import ( "math" "net/http" "strconv" - "strings" "sync" "time" @@ -34,9 +33,7 @@ type tos struct { var timeouts = tos{tos: make(map[string]*to)} func handleTimeout(ctx context.Context) bool { - ip := ctx.Value(ipAdressKey).(string) - parsed := strings.Split(ip, ":") - ip = parsed[0] + ip := ctx.Value(storage.IPAddressKey).(string) timeouts.mu.Lock() defer timeouts.mu.Unlock() @@ -69,12 +66,11 @@ func handleTimeout(ctx context.Context) bool { } func resetTimeout(ctx context.Context) { - ip := ctx.Value(ipAdressKey).(string) - parsed := strings.Split(ip, ":") - ip = parsed[0] + ip := ctx.Value(storage.IPAddressKey).(string) timeouts.mu.Lock() defer timeouts.mu.Unlock() + delete(timeouts.tos, ip) } diff --git a/backend/router.go b/backend/router.go index 0a58b26..e11db94 100644 --- a/backend/router.go +++ b/backend/router.go @@ -25,7 +25,6 @@ const ( assetsFSKey = "assets_fs" debugKey = "debug" loginKey = "login" - ipAdressKey = "ip_adress" ) //go:embed templates @@ -80,7 +79,10 @@ func NewRouter(debug bool, cfg *Config, db *sql.DB, assets fs.FS) *chi.Mux { if ip == "" { ip = r.RemoteAddr } - ctx = context.WithValue(ctx, ipAdressKey, ip) + if strings.Contains(ip, ":") { + ip = strings.Split(ip, ":")[0] + } + ctx = context.WithValue(ctx, storage.IPAddressKey, ip) ctx = context.WithValue(ctx, configKey, cfg) ctx = context.WithValue(ctx, assetsFSKey, assets) ctx = context.WithValue(ctx, debugKey, debug) @@ -119,9 +121,12 @@ func NewRouter(debug bool, cfg *Config, db *sql.DB, assets fs.FS) *chi.Mux { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) go func(ctx context.Context, r *http.Request) { + if strings.HasPrefix(r.RequestURI, "/static") { + return + } statusCode := GetStatusCode(ctx)() logger := GetLogger(ctx) - if statusCode >= 299 { + if statusCode >= 299 && r.RequestURI != storage.HumanPageLoad { logger.Debug("not updating stats for status code above 299", "status", statusCode) return } diff --git a/backend/storage/stats.go b/backend/storage/stats.go index b30cb8c..76d1c82 100644 --- a/backend/storage/stats.go +++ b/backend/storage/stats.go @@ -12,42 +12,52 @@ import ( "time" ) +const IPAddressKey = "ip_address" + type loaded struct { - data map[string]struct{} + data map[string]string mu *sync.RWMutex } -func (l *loaded) Has(k string) bool { +func (l *loaded) Has(k string, v string) bool { l.mu.RLock() defer l.mu.RUnlock() - _, ok := l.data[k] - return ok + val, ok := l.data[k] + if !ok { + return false + } + return val == v } -func (l *loaded) Add(k string) { +func (l *loaded) Add(k string, v string) { l.mu.Lock() defer l.mu.Unlock() - l.data[k] = struct{}{} + l.data[k] = v } -func (l *loaded) Remove(k string) { - l.mu.Lock() - defer l.mu.Unlock() - delete(l.data, k) +func (l *loaded) Remove(k string, v string) { + if l.Has(k, v) { + l.mu.Lock() + defer l.mu.Unlock() + delete(l.data, k) + } } func newLoaded() *loaded { return &loaded{ - data: make(map[string]struct{}), + data: make(map[string]string), mu: new(sync.RWMutex), } } var load = newLoaded() +// using /assets/styles.css to detect if a page is loaded → majority of bots will not load this +const HumanPageLoad = "/assets/styles.css" + func UpdateStats(ctx context.Context, r *http.Request, domain string) error { target := r.URL.Path - if strings.HasPrefix(target, "/static") || strings.HasPrefix(target, "/admin") { + if strings.HasPrefix(target, "/admin") { return nil } ref := r.Header.Get("Referer") @@ -65,12 +75,12 @@ func UpdateStats(ctx context.Context, r *http.Request, domain string) error { return nil } } - // using /assets/styles.css to detect if a page is loaded → majority of bots will not load this - if target == "/assets/styles.css" { + if target == HumanPageLoad { target = ref ref = "?" } - if load.Has(target) { + ip := ctx.Value(IPAddressKey).(string) + if load.Has(ip, target) { return nil } db := getDB(ctx) @@ -81,11 +91,11 @@ func UpdateStats(ctx context.Context, r *http.Request, domain string) error { defer func() { if err == nil { slog.Debug("stats updated") - load.Add(target) - go func(target string) { + load.Add(ip, target) + go func(ip, target string) { time.Sleep(5 * time.Second) - load.Remove(target) - }(target) + load.Remove(ip, target) + }(ip, target) } }() if !rows.Next() { |
