Back to "Umami.Detezione di rete e bot"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Umami

Umami.Detezione di rete e bot

Thursday, 12 September 2024

Introduzione

Quindi ha pubblicato un lot in passato sull'uso di Umami per l'analisi in un ambiente gestito da lui stesso - e ha persino pubblicato Umami.Net Nuget pacakge. Tuttavia avevo un problema dove volevo tracciare gli utenti del mio feed RSS; questo post va a spiegare perché e come l'ho risolto.

Il problema

Il problema è che i lettori di RSS cercano di passare. utile. Agenti dell'utente quando si chiede il feed. Questo permette conforme. i provider per tracciare il numero di utenti e il tipo di utenti che consumano il feed. TuttaviaM SK1 questo significa anche che Umami identificherà queste richieste come Bot. requests. Questo è un problema per il mio uso, il risultato è che la richiesta viene ignorata e non viene tracciata.

L'agente utilizzatore di Feedbin appare così:

Feedbin feed-id:1234 - 21 subscribers

Quindi è abbastanza utile giusto, passa alcuni dettagli utili su quale sia il vostro id di alimentazione, il numero di utenti e l'agente di utenteM SK2 Tuttavia , questo è anche un problema perché significa che Umami ignorerà la richiestaMSC4 in effetti saràMST5 restituirà uno status Mst6 ma il contenuto contiene {"beep": "boop"} Questo significa che questo è identificato come una richiesta di bot. È fastidioso perché non riesco a gestirlo attraverso il normale trattamento degli errori M SK2it's un 200, non dire un \403 ecc.

Quando guardiamo la lavagna Umami dopo aver fatto questo cambiamento, vedo ora gli originali UserAgents che hanno chiesto il feed:

Dashboard Umami

Quindi possiamo vedere qui un gruppo di ' che avrebbe attivato la rilevazione dei robot ma ora possiamo vedere questi e seguire le richieste.

La soluzione

Quindi, qual'è la soluzione a questo??Io posso'Io non posso analizzare manualmente tutte queste richieste e scoprire se Umami le rileva come un bot.;Io usa IsBot.(https://www.npmjs.com/package/isbot) per scoprire se una richiesta è un bot o no.E ci sono 'e non c'è C'è l'equivalente di #e 'e' una lista che cambia così posso'e' neanche usare quella lista(In futuro potrei diventare intelligente e usare la lista per capire se una domanda è un robot o meno). Quindi devo intercettare la richiesta prima che arrivi a Umami e cambiare l'Agent User in qualcosa che Umami accetterà per richieste specifiche.

Perciò ho aggiunto alcuni altri parametri ai miei metodi di tracciamento in Umami.Net. Questi vi permettono di specificare che il nuovo M SK2Agente d'Userone Defato ' sarà inviato a Umami invece dell'agente di Userone originarioMSC4 Questo mi permette di especificare che l'Agente di User dovrebbe essere cambiato ad un valore specifico per richieste specificheMNK5

I metodi

Sul mio. UmamiBackgroundSender Ho aggiunto il seguente:

   public async Task TrackPageView(string url, string title, UmamiPayload? payload = null,
        UmamiEventData? eventData = null, bool useDefaultUserAgent = false)

Questo esiste su tutti i metodi di tracciamento e semplicemente stabilisce un parametro sul UmamiPayload oggetto.

Sulla UmamiClient Si possono stabilire come follows:

    [Fact]
    public async Task TrackPageView_WithUrl()
    {
        var handler = JwtResponseHandler.Create();
        var umamiClient = GetServices(handler);
        var response = await umamiClient.TrackPageViewAndDecode("https://example.com", "Example Page",
            new UmamiPayload { UseDefaultUserAgent = true });
        Assert.NotNull(response);
        Assert.Equal(UmamiDataResponse.ResponseStatus.Success, response.Status);
    }

In questo test uso il nuovo. TrackPageViewAndDecode Il metodo che restituisce un UmamiDataResponse oggetto. Questo oggetto contiene il token JWT decodificato ( che è invalido se è un bot, quindi è utile per controllareM SK3 e lo stato della richiesta .

PayloadService

Questo è tutto gestito in Payload Il servizio che è responsabile di riempire l'oggetto di carico. Qui si trova il UseDefaultUserAgent è stato stabilito.

In modo predefinito, popula la carica da HttpContext Quindi di solito si ottiene questo set correttamente; IM SK1Possò mostrarvi più tardi dove viene riportato da Umami.

    private UmamiPayload GetPayload(string? url = null, UmamiEventData? data = null)
    {
        var httpContext = httpContextAccessor.HttpContext;
        var request = httpContext?.Request;

        var payload = new UmamiPayload
        {
            Website = settings.WebsiteId,
            Data = data,
            Url = url ?? httpContext?.Request?.Path.Value,
            IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString(),
            UserAgent = request?.Headers["User-Agent"].FirstOrDefault(),
            Referrer = request?.Headers["Referer"].FirstOrDefault(),
            Hostname = request?.Host.Host
        };

        return payload;
    }

Poi ho un pezzo di codice chiamato PopulateFromPayload che è dove l'oggetto della richiesta lo riceve'la configurazione dei dati:

    public static string DefaultUserAgent =>
        $"Mozilla/5.0 (Windows 11)  Umami.Net/{Assembly.GetAssembly(typeof(UmamiClient))!.GetName().Version}";

    public UmamiPayload PopulateFromPayload(UmamiPayload? payload, UmamiEventData? data)
    {
        var newPayload = GetPayload(data: data);
        ...
        
        newPayload.UserAgent = payload.UserAgent ?? DefaultUserAgent;

        if (payload.UseDefaultUserAgent)
        {
            var userData = newPayload.Data ?? new UmamiEventData();
            userData.TryAdd("OriginalUserAgent", newPayload.UserAgent ?? "");
            newPayload.UserAgent = DefaultUserAgent;
            newPayload.Data = userData;
        }


        logger.LogInformation("Using UserAgent: {UserAgent}", newPayload.UserAgent);
     }        
        

Vedrete che questo definisce un nuovo agente d'utente in cima al file (che io ho confermato non sia al momento. viene rilevato come un bot). Poi nel metodo rileva se l'UserAgent è nullo (che non dovrebbe accadere, a meno che non sia chiamato dal codice senza un HttpContextM SK4 o se UseDefaultUserAgent è stabilito. Se lo è, allora mette l'UserAgent al default e aggiunge l'originale UserAgent all'oggetto di dati.

Questo è poi registrato per vedere quale UserAgent viene usato.

Decodificare la risposta.

In Umami.Net M SK1 ho aggiunto un numero di nuovi metodi 'AndDecode' che restituiscono una UmamiDataResponse oggetto. Questo oggetto contiene il token JWT decodificatoM SK1

    public async Task<UmamiDataResponse?> TrackPageViewAndDecode(
        string? url = "",
        string? title = "",
        UmamiPayload? payload = null,
        UmamiEventData? eventData = null)
    {
        var response = await TrackPageView(url, title, payload, eventData);
        return await DecodeResponse(response);
    }
    
        private async Task<UmamiDataResponse?> DecodeResponse(HttpResponseMessage responseMessage)
    {
        var responseString = await responseMessage.Content.ReadAsStringAsync();

        switch (responseMessage.IsSuccessStatusCode)
        {
            case false:
                return new UmamiDataResponse(UmamiDataResponse.ResponseStatus.Failed);
            case true when responseString.Contains("beep") && responseString.Contains("boop"):
                logger.LogWarning("Bot detected data not stored in Umami");
                return new UmamiDataResponse(UmamiDataResponse.ResponseStatus.BotDetected);

            case true:
                var decoded = await jwtDecoder.DecodeResponse(responseString);
                if (decoded == null)
                {
                    logger.LogError("Failed to decode response from Umami");
                    return null;
                }

                var payload = UmamiDataResponse.Decode(decoded);

                return payload;
        }
    }

Potete vedere che questo chiama al normale. TrackPageView il metodo poi chiama un metodo chiamato DecodeResponse che controlla la risposta per il beep e boop strings (for bot detection). Se li trova, registra un avvertimento e restituisce una BotDetected status. Se non li trova, il token JWT viene decodificato e restituisce la carico.

Il token JWT stesso è solo una sequenza codificata Base64 che contiene i dati che Umami ha conservato. Questa viene decodificata e rimessa come un UmamiDataResponse oggetto.

La fonte completa di questo è sotto:

Response Decoder
using System.IdentityModel.Tokens.Jwt;

namespace Umami.Net.Models;

public class UmamiDataResponse
{
    public enum ResponseStatus
    {
        Failed,
        BotDetected,
        Success
    }

    public UmamiDataResponse(ResponseStatus status)
    {
        Status = status;
    }

    public ResponseStatus Status { get; set; }

    public Guid Id { get; set; }
    public Guid WebsiteId { get; set; }
    public string? Hostname { get; set; }
    public string? Browser { get; set; }
    public string? Os { get; set; }
    public string? Device { get; set; }
    public string? Screen { get; set; }
    public string? Language { get; set; }
    public string? Country { get; set; }
    public string? Subdivision1 { get; set; }
    public string? Subdivision2 { get; set; }
    public string? City { get; set; }
    public DateTime CreatedAt { get; set; }
    public Guid VisitId { get; set; }
    public long Iat { get; set; }

    public static UmamiDataResponse Decode(JwtPayload? payload)
    {
        if (payload == null) return new UmamiDataResponse(ResponseStatus.Failed);
        payload.TryGetValue("visitId", out var visitIdObj);
        payload.TryGetValue("iat", out var iatObj);
        //This should only happen then the payload is dummy.
        if (payload.Count == 2)
        {
            var visitId = visitIdObj != null ? Guid.Parse(visitIdObj.ToString()!) : Guid.Empty;
            var iat = iatObj != null ? long.Parse(iatObj.ToString()!) : 0;

            return new UmamiDataResponse(ResponseStatus.Success)
            {
                VisitId = visitId,
                Iat = iat
            };
        }

        payload.TryGetValue("id", out var idObj);
        payload.TryGetValue("websiteId", out var websiteIdObj);
        payload.TryGetValue("hostname", out var hostnameObj);
        payload.TryGetValue("browser", out var browserObj);
        payload.TryGetValue("os", out var osObj);
        payload.TryGetValue("device", out var deviceObj);
        payload.TryGetValue("screen", out var screenObj);
        payload.TryGetValue("language", out var languageObj);
        payload.TryGetValue("country", out var countryObj);
        payload.TryGetValue("subdivision1", out var subdivision1Obj);
        payload.TryGetValue("subdivision2", out var subdivision2Obj);
        payload.TryGetValue("city", out var cityObj);
        payload.TryGetValue("createdAt", out var createdAtObj);

        return new UmamiDataResponse(ResponseStatus.Success)
        {
            Id = idObj != null ? Guid.Parse(idObj.ToString()!) : Guid.Empty,
            WebsiteId = websiteIdObj != null ? Guid.Parse(websiteIdObj.ToString()!) : Guid.Empty,
            Hostname = hostnameObj?.ToString(),
            Browser = browserObj?.ToString(),
            Os = osObj?.ToString(),
            Device = deviceObj?.ToString(),
            Screen = screenObj?.ToString(),
            Language = languageObj?.ToString(),
            Country = countryObj?.ToString(),
            Subdivision1 = subdivision1Obj?.ToString(),
            Subdivision2 = subdivision2Obj?.ToString(),
            City = cityObj?.ToString(),
            CreatedAt = createdAtObj != null ? DateTime.Parse(createdAtObj.ToString()!) : DateTime.MinValue,
            VisitId = visitIdObj != null ? Guid.Parse(visitIdObj.ToString()!) : Guid.Empty,
            Iat = iatObj != null ? long.Parse(iatObj.ToString()!) : 0
        };
    }
}
Potete vedere che contiene un sacco di informazioni utili sulla richiesta che Umami ha conservato. Se volete per esempio mostrare diversi contenuti basati su localeM SK1 linguaggio, browser ecc. questo vi permette di farloMSC3
    public Guid Id { get; set; }
    public Guid WebsiteId { get; set; }
    public string? Hostname { get; set; }
    public string? Browser { get; set; }
    public string? Os { get; set; }
    public string? Device { get; set; }
    public string? Screen { get; set; }
    public string? Language { get; set; }
    public string? Country { get; set; }
    public string? Subdivision1 { get; set; }
    public string? Subdivision2 { get; set; }
    public string? City { get; set; }
    public DateTime CreatedAt { get; set; }
    public Guid VisitId { get; set; }
    public long Iat { get; set; }

Conclusione

Quindi solo un breve post che copre alcune nuove funzioni di Umami.Net 0.4.0 che vi permette di specificare un agente di Usuario predefinito per specifiche richiesteM SK2 Questo è utile per tracciare le richieste che Umami altrimenti ignorarebbe.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.