using Microsoft.Extensions.Options; using System.Text.RegularExpressions; using YarpGateway.Services; namespace YarpGateway.Middleware; public class TenantRoutingMiddleware { private readonly RequestDelegate _next; private readonly IRouteCache _routeCache; private readonly ILogger _logger; public TenantRoutingMiddleware( RequestDelegate next, IRouteCache routeCache, ILogger logger) { _next = next; _routeCache = routeCache; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var tenantId = context.Request.Headers["X-Tenant-Id"].FirstOrDefault(); if (string.IsNullOrEmpty(tenantId)) { await _next(context); return; } var path = context.Request.Path.Value ?? string.Empty; var serviceName = ExtractServiceName(path); if (string.IsNullOrEmpty(serviceName)) { await _next(context); return; } var route = _routeCache.GetRoute(tenantId, serviceName); if (route == null) { _logger.LogWarning("Route not found - Tenant: {Tenant}, Service: {Service}", tenantId, serviceName); await _next(context); return; } context.Items["DynamicClusterId"] = route.ClusterId; var routeType = route.IsGlobal ? "global" : "tenant-specific"; _logger.LogInformation("Tenant routing - Tenant: {Tenant}, Service: {Service}, Cluster: {Cluster}, Type: {Type}", tenantId, serviceName, route.ClusterId, routeType); await _next(context); } private string ExtractServiceName(string path) { var match = Regex.Match(path, @"/api/(\w+)/?"); return match.Success ? match.Groups[1].Value : string.Empty; } }