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
Umami.Net och Bot-detektor
Thursday, 12 September 2024
Inleiding
Så postade en LITT i det förflutna om att använda Umami för analytik i en självstyrd miljö och till och med publicerade Umami.Net Nuget pacakge. Men jag hade ett problem där jag ville följa användarna av min RSS-ström.
Problemet
Problemet är att RSS-feed-reader försöker gå igenom användbar Användaragenter när man frågar om flödet. Det låter följande för att spåra antalet användare och typen av användare som konsumerar flödet robot begäran. Det här är ett problem för mig eftersom det leder till att begäran ignoreras och inte spåras
Feedbins användaragent ser ut så här:
Feedbin feed-id:1234 - 21 subscribers
Så ganska användbar rätt, den skickar några användbara detaljer om vad din feed-id är , antalet användare och användaragenten . Men också detta är ett problem, eftersom det betyder att Umami kommer att ignorera begäran {"beep": "boop"} vilket betyder att det är identifierat som en bot-ansökning.
När vi tittar på Umami-dashboarden efter att ha gjort den här förändringen så kan jag nu se de ursprungliga UserAgents som bad om flödet

Så vi kan se där en grupp som skulle ha aktiverat robotdetektorn men nu kan vi se dessa och följa upp begäran.
Lösningen
Så vad är lösningen på detta? ? Jag kan, ' jag kan inte manuell analysera alla dessa begäran och upptäcka om Umami kommer att upptäcka dem som en bot. ; det använder IsBot. ( https :// www . npmjs . com / paket / isbot ) för att upptäcke om en begäran är en bot eller ej . Det finns inget C- # likvärdigt och det är en ändrad lista så jag kan ' inte ens använda den listan. Så jag måste interceptera begäran innan den kommer till Umami och ändra användaragenten till något som Umami kommer att acceptera för specifika begäran.
Så nu har jag lagt till några extra parametrar till mina spårningsmetoder i Umami.NetM SK1 De låter dig specifikera den nya 'Default User AgentMska3 som kommer skickas till Umami istället för den ursprungliga User AgentenM Ska4 Det låter mig specifikera att användaragenten ska ändras till en specifik värde för specifika begäran.
Metoderna
På min UmamiBackgroundSender Jag lade till följande:
public async Task TrackPageView(string url, string title, UmamiPayload? payload = null,
UmamiEventData? eventData = null, bool useDefaultUserAgent = false)
Detta existerar på alla spårningsmetoder där och bara sätter en parameter på UmamiPayload objekt.
På UmamiClient dessa kan sättas så här::
[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);
}
I denna test använder jag den nya TrackPageViewAndDecode metod som återvänder en UmamiDataResponse objekt. Det här objektet innehåller avkodad JWT-token | ( | som är ovärderligt om det är en robot, så det är användbart att kontrollera |
PayloadService
Det här är allt hanterat i Payload tjänst som är ansvarig för att fylla i payload-objektet. Det här är där UseDefaultUserAgent är ställt.
Standardmässigt lägger jag upp payloaden från HttpContext så du usually får den här uppsättningen korrekt.
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;
}
Då har jag en bit kod som heter PopulateFromPayload som är där applikationsobjektet får det
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);
}
Du kommer att se att detta definierar en ny Useragent längst upp i filet ( som jag har bekräftat är i dagsläget upptäckt som en bot). Sen i metoden upptäcker den om antingen UserAgent är ofull ( vilket inte skulle kunna hända, om det inte är ' som kallas från kod utan HttpContext UseDefaultUserAgent är ställt. Om det är så ställer den UserAgent till standard och lägger till den ursprungliga UserAgenten i dataobjektet
Detta registreras så att du kan se vad UserAgent används.
Dekodering av svaret.
I Umami added jag ett antal nya 'AndDecode' metoder som återvänder UmamiDataResponse objekt. Det här objektet innehåller den dekoderade JWT-tokenen
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;
}
}
Ni kan se att det här låter in i det normala TrackPageView metoden kallar sedan en metod som kallas DecodeResponse som kontrollerar svaret på beep och boop strängar ( för botdetektorn ). Om den hittar dem så registrerar den en varning BotDetected status. Om den inte hittar dem, så avkoder den JWT-token och ger tillbaka payloadet
JWT-token i sig är bara en koderad String med basen, 64, som innehåller data som Umami har sparat. UmamiDataResponse objekt.
Den kompletta källan för detta är under
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
};
}
}
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; }
I slutsats
Så bara en kort artikel som innefattar några nya funktioner i Umami.Net | 0.4.0 | som tillåter dig att speciera en standardanvändare agent för specifika begäran |. | Detta är användbart för att spåra begärningar som Umami annars skulle ignorera