using System.Diagnostics; namespace PrivaPub.Infrastructure.Statistics { public static class TrafficMeter { public static IApplicationBuilder UseTrafficMeter(this IApplicationBuilder app) => app.Use(async (context, next) => { var started = Stopwatch.GetTimestamp(); try { await next(context); } finally { Count(context, (int)Stopwatch.GetElapsedTime(started).TotalMilliseconds); } }); static void Count(HttpContext context, int latencyMs) { var ledger = context.RequestServices?.GetService(); if (ledger == default) return; var path = context.Request.Path; var template = (context.GetEndpoint() as RouteEndpoint)?.RoutePattern.RawText; var method = context.Request.Method; var status = context.Response.StatusCode; if (path.StartsWithSegments("/api") || path.StartsWithSegments("/oauth") || path.StartsWithSegments("/clientapi")) { ledger.CountServer(ServerSections.Client, $"{Group(template)}:{method}:{status / 100}xx", latencyMs); return; } if (method != HttpMethods.Get && method != HttpMethods.Head) return; var served = Served(template, path); if (served != default) ledger.CountServer(ServerSections.Served, $"{served}:{status}:{(context.Request.Headers.ContainsKey("Signature") || context.Request.Headers.ContainsKey("Signature-Input") ? "signed" : "unsigned")}"); } public static string Group(string template) { if (template == default) return "unmatched"; var literals = template.Trim('/').Split('/', StringSplitOptions.RemoveEmptyEntries) .TakeWhile(segment => !segment.Contains('{')) .Take(3); var group = string.Join('/', literals); return group.Length == 0 ? "unmatched" : group; } public static string Served(string template, PathString path) { if (path.StartsWithSegments("/.well-known/webfinger")) return "webfinger"; if (path.StartsWithSegments("/.well-known/nodeinfo") || path.StartsWithSegments("/nodeinfo")) return "nodeinfo"; if (!path.StartsWithSegments("/peasants") && !path.StartsWithSegments("/users")) return default; var segments = (template ?? path.Value ?? string.Empty).Trim('/').Split('/'); return segments.Length switch { <= 2 => "actor", _ => segments[2] switch { "anus" => "outbox", "scribbles" or "whispers" => "object", "grunts" => "activity", "parrot-licences" => "licence", "mouth" or "human-centipede" => default, _ => "collection" } }; } } }