using NLog; using System.Diagnostics; using Yarp.ReverseProxy.Forwarder; namespace MP.IOC.Services { public class RouteManager { private readonly IHttpForwarder _forwarder; private readonly HttpMessageInvoker _httpClientInvoker; private readonly PreserveBodyTransformer _transformer; private readonly RouteStatsManager _stats; private readonly IWeightProvider _weightProvider; private readonly IConfiguration _config; public RouteManager( IHttpForwarder forwarder, HttpMessageInvoker httpClientInvoker, PreserveBodyTransformer transformer, RouteStatsManager stats, IWeightProvider weightProvider, IConfiguration config) { _forwarder = forwarder; _httpClientInvoker = httpClientInvoker; _transformer = transformer; _stats = stats; _weightProvider = weightProvider; _config = config; _routePath = _config.GetValue("ServerConf:RoutePath") ?? "/api/RIOB"; } private string _routePath = ""; private static Logger Log = LogManager.GetCurrentClassLogger(); public async Task HandleAsync(HttpContext context) { var sw = Stopwatch.StartNew(); var routePrefix = new PathString(_routePath); var fullPrefix = context.Request.PathBase.Add(routePrefix); string relativePath; string query = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : ""; if (context.Request.Path.StartsWithSegments(fullPrefix, out var remaining)) { relativePath = remaining.Value.TrimStart('/'); } else if (context.Request.Path.StartsWithSegments(routePrefix, out remaining)) { relativePath = remaining.Value.TrimStart('/'); } else { var fullPath = (context.Request.PathBase + context.Request.Path).Value ?? ""; var idx = fullPath.IndexOf("/RIOB/", StringComparison.OrdinalIgnoreCase); relativePath = idx >= 0 ? fullPath[(idx + "/RIOB/".Length)..] : fullPath.TrimStart('/'); } Log.Debug($"PathBase={context.Request.PathBase} | Path={context.Request.Path} | relativePath={relativePath}"); // da calcolare metodo... string metodo = "undef"; if (!string.IsNullOrEmpty(relativePath)) { // splitto se ho /... if (relativePath.Contains("/")) { metodo = relativePath.Substring(0, relativePath.IndexOf("/")); } else { metodo = relativePath; } } Log.Debug($"Metodo: {metodo}"); var (oldW, newW) = _weightProvider.GetWeightsFor(metodo); var pickNew = DecideByWeights(oldW, newW); var target = pickNew ? "IOC" : "IO"; // Costruisci destination base in base al target var destBase = pickNew ? _config["ReverseProxy:Clusters:cluster-new:Destinations:new1:Address"] : _config["ReverseProxy:Clusters:cluster-old:Destinations:old1:Address"]; if (string.IsNullOrEmpty(destBase)) { context.Response.StatusCode = 502; await context.Response.WriteAsync("Destination not configured"); return; } if (!destBase.EndsWith("/")) destBase += "/"; // salva path originale per ripristino var originalPath = context.Request.Path; var originalPathBase = context.Request.PathBase; try { string sKey = $"{target}|{metodo}"; // Registra scelta _stats.Record(sKey); // imposta la Path che vogliamo che YARP appenda al destBase context.Request.Path = new PathString("/" + relativePath); // opzionale: se vuoi che PathBase sia vuoto per il backend, impostalo così context.Request.PathBase = PathString.Empty; Log.Info($"Forwarding to base {destBase} with forwarded path {context.Request.Path} | original PathBase:{originalPathBase} | Path={originalPath})"); var requestOptions = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, requestOptions, _transformer, context.RequestAborted); sw.Stop(); _stats.RecordDuration(sKey, sw.Elapsed); if (error != ForwarderError.None) { var feat = context.GetForwarderErrorFeature(); Log.Error(feat?.Exception, "Forwarder error to {DestBase}", destBase); context.Response.StatusCode = 502; await context.Response.WriteAsync($"Forward error: {feat?.Exception?.Message}"); } } finally { // ripristina i path originali per non rompere la pipeline successiva context.Request.Path = originalPath; context.Request.PathBase = originalPathBase; } } private bool DecideByWeights(int oldW, int newW) { bool result = false; // se entrambi zero -> prefer legacy var total = oldW + newW; if (total <= 0) { result = false; } else { var rnd = Random.Shared.NextDouble(); // 0..1 result = rnd < (double)newW / total; } return result; } } }