Files
mapo-core/MP.IOC/Services/RouteManager.cs
T
2026-04-03 18:33:51 +02:00

235 lines
8.9 KiB
C#

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;
}
private static Logger Log = LogManager.GetCurrentClassLogger();
#if false
public async Task HandleAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
// Estrapola il catch-all relativo: /MP/IOC/api/RIOB/enabled/SIMUL_01 -> enabled/SIMUL_01
var fullPath = context.Request.Path.Value ?? "";
var prefix = "/api/RIOB/";
//var prefix = "/MP/IOC/api/RIOB/";
var relativePath = fullPath.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
? fullPath[prefix.Length..]
: fullPath.TrimStart('/');
// Estrai "metodo" come primo segmento (es. enabled)
var segments = relativePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
var metodo = segments.Length > 0 ? segments[0] : "unknown";
// Weighted decision
var (oldW, newW) = _weightProvider.GetWeightsFor(metodo);
// se entrambi zero -> prefer legacy
var total = oldW + newW;
var pickNew = false;
if (total <= 0)
{
pickNew = false;
}
else
{
var rnd = Random.Shared.NextDouble(); // 0..1
pickNew = rnd < (double)newW / total;
}
var target = pickNew ? "new" : "old";
// Costruisci destination base in base al target
var destBase = target == "new"
? _config.GetValue<string>("ReverseProxy:Clusters:cluster-new:Destinations:new1:Address")
: _config.GetValue<string>("ReverseProxy:Clusters:cluster-old:Destinations:old1:Address");
if (string.IsNullOrEmpty(destBase))
{
context.Response.StatusCode = 502;
await context.Response.WriteAsync("Destination not configured");
return;
}
// Assicuriamoci che la destinazione termini con slash e componiamo l'URL finale
if (!destBase.EndsWith("/")) destBase += "/";
var destination = new Uri(new Uri(destBase), relativePath).ToString();
// Registra scelta
_stats.Record(metodo, target);
// Forward
var requestOptions = new ForwarderRequestConfig
{
ActivityTimeout = TimeSpan.FromSeconds(100)
};
var error = await _forwarder.SendAsync(context, destination, _httpClientInvoker, requestOptions, _transformer, context.RequestAborted);
sw.Stop();
_stats.RecordDuration(metodo, sw.Elapsed);
if (error != ForwarderError.None)
{
var feat = context.GetForwarderErrorFeature();
context.Response.StatusCode = 502;
await context.Response.WriteAsync(feat?.Exception?.Message ?? "Forward error");
}
}
#endif
public async Task HandleAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
var routePrefix = new PathString("/api/RIOB");
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.Info($"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.Info($"Metodo: {metodo}");
var (oldW, newW) = _weightProvider.GetWeightsFor(metodo);
var pickNew = DecideByWeights(oldW, newW);
var target = pickNew ? "new" : "old";
var destBase = pickNew
? _config["ReverseProxy:Clusters:cluster-new:Destinations:new1:Address"]
: _config["ReverseProxy:Clusters:cluster-old:Destinations:old1:Address"];
if (!destBase.EndsWith("/")) destBase += "/";
//var destination = destBase + relativePath + query;
//Log.Info("Forwarding to {Destination}", destination);
//var requestOptions = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) };
//var error = await _forwarder.SendAsync(context, destination, _httpClientInvoker, requestOptions, _transformer, context.RequestAborted);
//sw.Stop();
//_stats.RecordDuration(metodo, sw.Elapsed);
//if (error != ForwarderError.None)
//{
// var feat = context.GetForwarderErrorFeature();
// Log.Error(feat?.Exception, "Forwarder error to {Destination}", destination);
// context.Response.StatusCode = 502;
// await context.Response.WriteAsync($"Forward error: {feat?.Exception?.Message}");
//}
// salva path originale per ripristino
var originalPath = context.Request.Path;
var originalPathBase = context.Request.PathBase;
try
{
// 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 {ForwardedPath} (original PathBase={PathBase} Path={Path})",
destBase, context.Request.Path, originalPathBase, originalPath);
var requestOptions = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) };
var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, requestOptions, _transformer, context.RequestAborted);
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;
}
}
}