5 changed files with 264 additions and 1 deletions
@ -0,0 +1,11 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace EShopOnAbp.WebGateway.Aggregations; |
|||
|
|||
public interface ILocalizationAggregation : ITransientDependency |
|||
{ |
|||
public string LocalizationEndpoint { get; } |
|||
public Task<ApplicationLocalizationDto> GetLocalizationAsync(LocalizationRequest input); |
|||
} |
|||
@ -0,0 +1,152 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Net.Http; |
|||
using System.Text.Json; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; |
|||
using Volo.Abp.Caching; |
|||
|
|||
namespace EShopOnAbp.WebGateway.Aggregations; |
|||
|
|||
public class LocalizationAggregation : ILocalizationAggregation |
|||
{ |
|||
//Yarp localization route name
|
|||
public string LocalizationEndpoint { get; } = "EshopOnAbpLocalization"; |
|||
private readonly ILogger<LocalizationAggregation> _logger; |
|||
private readonly IDistributedCache<LocalizationResponse> _localizationResponseCache; |
|||
|
|||
public LocalizationAggregation(ILogger<LocalizationAggregation> logger, IDistributedCache<LocalizationResponse> localizationResponseCache) |
|||
{ |
|||
_logger = logger; |
|||
_localizationResponseCache = localizationResponseCache; |
|||
} |
|||
|
|||
public async Task<ApplicationLocalizationDto> GetLocalizationAsync(LocalizationRequest input) |
|||
{ |
|||
//Temp list
|
|||
Dictionary<string, string> services = new Dictionary<string, string>() |
|||
{ |
|||
{ "Administration", "https://localhost:44353/api/abp/application-localization?CultureName=en" }, |
|||
{ "Catalog", "https://localhost:44354/api/abp/application-localization?CultureName=en" } |
|||
}; |
|||
|
|||
// Check the cache
|
|||
|
|||
|
|||
|
|||
Dictionary<string, ApplicationLocalizationDto> localizationResults = await GetCompletedLocalizationResultsAsync(services); |
|||
ApplicationLocalizationDto mergedResult = MergeLocalizations(localizationResults); |
|||
|
|||
return mergedResult; |
|||
} |
|||
|
|||
private ApplicationLocalizationDto MergeLocalizations(Dictionary<string, ApplicationLocalizationDto> localizationResults) |
|||
{ |
|||
var localizationDto = new ApplicationLocalizationDto(); |
|||
foreach (var localizationResult in localizationResults) |
|||
{ |
|||
localizationDto.Resources.AddIfNotContains(localizationResult.Value.Resources); |
|||
} |
|||
|
|||
return localizationDto; |
|||
} |
|||
|
|||
private async Task<Dictionary<string, ApplicationLocalizationDto>> GetCompletedLocalizationResultsAsync(Dictionary<string, string> services) |
|||
{ |
|||
Dictionary<string, Task<ApplicationLocalizationDto>> completedTasks = new Dictionary<string, Task<ApplicationLocalizationDto>>(); |
|||
Dictionary<string, Task<ApplicationLocalizationDto>> runningTasks = new Dictionary<string, Task<ApplicationLocalizationDto>>(); |
|||
Dictionary<string, ApplicationLocalizationDto> completedResult = new Dictionary<string, ApplicationLocalizationDto>(); |
|||
|
|||
using (HttpClient httpClient = new HttpClient()) |
|||
{ |
|||
foreach (var service in services) |
|||
{ |
|||
Task<ApplicationLocalizationDto> requestTask = MakeRequestAsync<ApplicationLocalizationDto>(httpClient, service.Value); |
|||
runningTasks.Add(service.Key, requestTask); |
|||
} |
|||
|
|||
while (runningTasks.Count > 0) |
|||
{ |
|||
KeyValuePair<string, Task<ApplicationLocalizationDto>> completedTask = await WaitForAnyTaskAsync(runningTasks); |
|||
|
|||
runningTasks.Remove(completedTask.Key); |
|||
|
|||
try |
|||
{ |
|||
ApplicationLocalizationDto result = await completedTask.Value; |
|||
|
|||
completedTasks.Add(completedTask.Key, completedTask.Value); |
|||
completedResult.Add(completedTask.Key, result); |
|||
|
|||
_logger.LogInformation($"Key: {completedTask.Key}, Value: {result}"); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_logger.LogInformation($"Error for the {completedTask.Key}: {ex.Message}"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return completedResult; |
|||
} |
|||
|
|||
private async Task<KeyValuePair<string, Task<ApplicationLocalizationDto>>> WaitForAnyTaskAsync( |
|||
Dictionary<string, Task<ApplicationLocalizationDto>> tasks) |
|||
{ |
|||
Task<Task<ApplicationLocalizationDto>> completedTask = Task.WhenAny(tasks.Values); |
|||
Task<ApplicationLocalizationDto> result = await completedTask; |
|||
|
|||
// Find the key corresponding to the completed task
|
|||
KeyValuePair<string, Task<ApplicationLocalizationDto>> |
|||
completedTaskPair = tasks.First(kv => kv.Value == result); |
|||
|
|||
return completedTaskPair; |
|||
} |
|||
|
|||
private async Task<T> MakeRequestAsync<T>(HttpClient httpClient, string url) |
|||
{ |
|||
try |
|||
{ |
|||
HttpResponseMessage response = await httpClient.GetAsync(url); |
|||
response.EnsureSuccessStatusCode(); |
|||
|
|||
string content = await response.Content.ReadAsStringAsync(); |
|||
var options = new JsonSerializerOptions |
|||
{ |
|||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, |
|||
}; |
|||
return JsonSerializer.Deserialize<T>(content, options); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
// Log or handle the exception, and return a default value or throw it again if needed
|
|||
Console.WriteLine($"Error making request to {url}: {ex.Message}"); |
|||
throw; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class LocalizationRequest |
|||
{ |
|||
// Localization clusterName - localization endpoint pair
|
|||
public Dictionary<string, string> LocalizationEndpoints { get; } |
|||
public string CultureName { get; set; } |
|||
public bool? OnlyDynamics { get; set; } |
|||
|
|||
public LocalizationRequest(string cultureName) |
|||
{ |
|||
CultureName = cultureName; |
|||
LocalizationEndpoints = new Dictionary<string, string>(); |
|||
} |
|||
} |
|||
|
|||
public class LocalizationResponse |
|||
{ |
|||
// Localization url - localization resource pair
|
|||
public Dictionary<string, ApplicationLocalizationDto> Localization { get; } |
|||
public string CultureName { get; set; } |
|||
public bool? OnlyDynamics { get; set; } |
|||
public string ServiceName { get; set; } |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Text.Json; |
|||
using EShopOnAbp.WebGateway.Aggregations; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Routing; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Yarp.ReverseProxy.Configuration; |
|||
|
|||
namespace EShopOnAbp.WebGateway; |
|||
|
|||
public static class ReverseProxyBuilderExtensions |
|||
{ |
|||
public static string LocalizationEndpoint = "api/abp/application-localization"; |
|||
|
|||
public static ReverseProxyConventionBuilder MapReverseProxyWithLocalization(this IEndpointRouteBuilder endpoints) |
|||
{ |
|||
return endpoints.MapReverseProxy(proxyBuilder => |
|||
{ |
|||
proxyBuilder.Use(async (context, next) => |
|||
{ |
|||
var endpoint = context.GetEndpoint(); |
|||
|
|||
var localizationAggregation = context.RequestServices.GetRequiredService<ILocalizationAggregation>(); |
|||
if (localizationAggregation.LocalizationEndpoint == endpoint?.DisplayName) |
|||
{ |
|||
LocalizationRequest input = CreateLocalizationRequestInput(context); |
|||
|
|||
var result = await localizationAggregation.GetLocalizationAsync(input); |
|||
await context.Response.WriteAsync(JsonSerializer.Serialize(result, new JsonSerializerOptions |
|||
{ |
|||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, |
|||
WriteIndented = true |
|||
})); |
|||
return; |
|||
} |
|||
|
|||
await next(); |
|||
}); |
|||
|
|||
proxyBuilder.UseLoadBalancing(); |
|||
}); |
|||
} |
|||
|
|||
private static LocalizationRequest CreateLocalizationRequestInput(HttpContext context) |
|||
{ |
|||
var proxyConfig = context.RequestServices.GetRequiredService<IProxyConfigProvider>(); |
|||
|
|||
context.Request.Query.TryGetValue("CultureName", out var cultureName); |
|||
context.Request.Query.TryGetValue("OnlyDynamics", out var onlyDynamics); |
|||
|
|||
var input = new LocalizationRequest(cultureName); |
|||
StringBuilder stringBuilder = new StringBuilder(LocalizationEndpoint + $"?CultureName={cultureName}"); |
|||
|
|||
if (!string.IsNullOrEmpty(onlyDynamics)) |
|||
{ |
|||
input.OnlyDynamics = bool.Parse(onlyDynamics); |
|||
stringBuilder.Append($"&OnlyDynamics={onlyDynamics}"); |
|||
} |
|||
|
|||
var clusterList = GetClusters(proxyConfig); |
|||
foreach (var cluster in clusterList) |
|||
{ |
|||
var hostUrl = new Uri(cluster.Value.Address) + $"{stringBuilder}"; |
|||
input.LocalizationEndpoints.Add(cluster.Key, hostUrl); |
|||
} |
|||
|
|||
return input; |
|||
} |
|||
|
|||
private static Dictionary<string, DestinationConfig> GetClusters(IProxyConfigProvider proxyConfig) |
|||
{ |
|||
var yarpConfig = proxyConfig.GetConfig(); |
|||
|
|||
var routedClusters = yarpConfig.Clusters |
|||
.SelectMany(t => t.Destinations, |
|||
(clusterId, destination) => new { clusterId.ClusterId, destination.Value }); |
|||
|
|||
return routedClusters |
|||
.GroupBy(q => q.Value.Address) |
|||
.Select(t => t.First()) |
|||
.Distinct() |
|||
.ToDictionary(k => k.ClusterId, v => v.Value); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue