From db3a6ea9c54975fdee0c015155173f4e861174a2 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 25 Jan 2026 01:27:17 +0800 Subject: [PATCH] :sparkles: Customizable gateway endpoints --- .../Configuration/GatewayEndpointsOptions.cs | 56 +++++++++++++++++++ .../Health/GatewayConstant.cs | 36 ++++++++++-- .../Health/GatewayHealthAggregator.cs | 2 +- .../Health/GatewayReadinessStore.cs | 36 ++++++++++++ DysonNetwork.Gateway/Program.cs | 17 +++++- DysonNetwork.Gateway/appsettings.json | 20 ++++++- 6 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 DysonNetwork.Gateway/Configuration/GatewayEndpointsOptions.cs diff --git a/DysonNetwork.Gateway/Configuration/GatewayEndpointsOptions.cs b/DysonNetwork.Gateway/Configuration/GatewayEndpointsOptions.cs new file mode 100644 index 00000000..8213a4e2 --- /dev/null +++ b/DysonNetwork.Gateway/Configuration/GatewayEndpointsOptions.cs @@ -0,0 +1,56 @@ +using System.ComponentModel.DataAnnotations; + +namespace DysonNetwork.Gateway.Configuration; + +public class GatewayEndpointsOptions +{ + public const string SectionName = "Endpoints"; + + /// + /// List of all services that the gateway should manage. + /// If not specified, defaults to the built-in service list. + /// + public List? ServiceNames { get; set; } + + /// + /// List of core services that are essential for the application to function. + /// If not specified, defaults to the built-in core service list. + /// + public List? CoreServiceNames { get; set; } + + /// + /// Default service names used when no configuration is provided. + /// + public static readonly string[] DefaultServiceNames = + [ + "ring", + "pass", + "drive", + "sphere", + "develop", + "insight", + "zone", + "messager" + ]; + + /// + /// Default core service names used when no configuration is provided. + /// + public static readonly string[] DefaultCoreServiceNames = + [ + "ring", + "pass", + "drive", + "sphere" + ]; + + /// + /// Gets the effective service names, using configuration if available, otherwise defaults. + /// + public string[] GetServiceNames() => ServiceNames?.ToArray() ?? DefaultServiceNames; + + /// + /// Gets the effective core service names, using configuration if available, otherwise defaults. + /// + public string[] GetCoreServiceNames() => CoreServiceNames?.ToArray() ?? DefaultCoreServiceNames; +} \ No newline at end of file diff --git a/DysonNetwork.Gateway/Health/GatewayConstant.cs b/DysonNetwork.Gateway/Health/GatewayConstant.cs index 605fddc1..8cb74855 100644 --- a/DysonNetwork.Gateway/Health/GatewayConstant.cs +++ b/DysonNetwork.Gateway/Health/GatewayConstant.cs @@ -2,7 +2,8 @@ namespace DysonNetwork.Gateway.Health; public abstract class GatewayConstant { - public static readonly string[] ServiceNames = + // Default service names used when no configuration is provided + private static readonly string[] DefaultServiceNames = [ "ring", "pass", @@ -14,12 +15,39 @@ public abstract class GatewayConstant "messager" ]; - // Core services stands with w/o these services the functional of entire app will broke. - public static readonly string[] CoreServiceNames = + // Default core service names used when no configuration is provided + private static readonly string[] DefaultCoreServiceNames = [ "ring", "pass", "drive", "sphere" ]; -} \ No newline at end of file + + // Configuration-driven service names with fallback to defaults + public static string[] ServiceNames { get; private set; } = DefaultServiceNames; + + // Configuration-driven core service names with fallback to defaults + public static string[] CoreServiceNames { get; private set; } = DefaultCoreServiceNames; + + /// + /// Initializes the service names from configuration options. + /// This method should be called during application startup. + /// + /// The gateway endpoints options containing configuration + public static void InitializeFromConfiguration(DysonNetwork.Gateway.Configuration.GatewayEndpointsOptions options) + { + ServiceNames = options.GetServiceNames(); + CoreServiceNames = options.GetCoreServiceNames(); + } + + /// + /// Resets the service names to their default values. + /// Useful for testing or when configuration is not available. + /// + public static void ResetToDefaults() + { + ServiceNames = DefaultServiceNames; + CoreServiceNames = DefaultCoreServiceNames; + } +} diff --git a/DysonNetwork.Gateway/Health/GatewayHealthAggregator.cs b/DysonNetwork.Gateway/Health/GatewayHealthAggregator.cs index 727b7f45..e57e3a6d 100644 --- a/DysonNetwork.Gateway/Health/GatewayHealthAggregator.cs +++ b/DysonNetwork.Gateway/Health/GatewayHealthAggregator.cs @@ -57,4 +57,4 @@ public class GatewayHealthAggregator(IHttpClientFactory httpClientFactory, Gatew await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } } -} \ No newline at end of file +} diff --git a/DysonNetwork.Gateway/Health/GatewayReadinessStore.cs b/DysonNetwork.Gateway/Health/GatewayReadinessStore.cs index ca0ce1d4..b45cb48d 100644 --- a/DysonNetwork.Gateway/Health/GatewayReadinessStore.cs +++ b/DysonNetwork.Gateway/Health/GatewayReadinessStore.cs @@ -34,6 +34,42 @@ public class GatewayReadinessStore InitializeServices(GatewayConstant.ServiceNames); } + /// + /// Reinitializes the store with new service names from configuration. + /// This method should be called when configuration changes. + /// + /// The new service names to track + public void ReinitializeServices(string[] serviceNames) + { + lock (_lock) + { + // Preserve existing health states for services that still exist + var existingStates = new Dictionary(_services); + + _services.Clear(); + + foreach (var name in serviceNames) + { + // Use existing state if available, otherwise create new unhealthy state + if (existingStates.TryGetValue(name, out var existingState)) + { + _services[name] = existingState; + } + else + { + _services[name] = new ServiceHealthState( + name, + IsHealthy: false, + LastChecked: SystemClock.Instance.GetCurrentInstant(), + Error: "Not checked yet" + ); + } + } + + RecalculateLocked(); + } + } + private void InitializeServices(IEnumerable serviceNames) { lock (_lock) diff --git a/DysonNetwork.Gateway/Program.cs b/DysonNetwork.Gateway/Program.cs index 6a8ec30e..c82314a6 100644 --- a/DysonNetwork.Gateway/Program.cs +++ b/DysonNetwork.Gateway/Program.cs @@ -1,10 +1,12 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.RateLimiting; +using DysonNetwork.Gateway.Configuration; using DysonNetwork.Gateway.Health; using DysonNetwork.Shared.Networking; using Yarp.ReverseProxy.Configuration; using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.Extensions.Options; using NodaTime; using NodaTime.Serialization.SystemTextJson; @@ -17,6 +19,10 @@ builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxV builder.Services.AddSingleton(); builder.Services.AddHostedService(); +// Add configuration options for gateway endpoints +builder.Services.Configure( + builder.Configuration.GetSection(DysonNetwork.Gateway.Configuration.GatewayEndpointsOptions.SectionName)); + builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => @@ -170,6 +176,15 @@ builder.Services.AddControllers().AddJsonOptions(options => var app = builder.Build(); +// Initialize GatewayConstant with configuration values +var gatewayEndpointsOptions = app.Services + .GetRequiredService>().Value; +GatewayConstant.InitializeFromConfiguration(gatewayEndpointsOptions); + +// Reinitialize the readiness store with configured service names +var readinessStore = app.Services.GetRequiredService(); +readinessStore.ReinitializeServices(GatewayConstant.ServiceNames); + var forwardedHeadersOptions = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.All @@ -186,4 +201,4 @@ app.MapReverseProxy().RequireRateLimiting("fixed"); app.MapControllers(); -app.Run(); +app.Run(); \ No newline at end of file diff --git a/DysonNetwork.Gateway/appsettings.json b/DysonNetwork.Gateway/appsettings.json index 8ccaeb86..4bb2050f 100644 --- a/DysonNetwork.Gateway/appsettings.json +++ b/DysonNetwork.Gateway/appsettings.json @@ -12,5 +12,23 @@ "SiteUrl": "http://localhost:3000", "Client": { "SomeSetting": "SomeValue" + }, + "Endpoints": { + "ServiceNames": [ + "ring", + "pass", + "drive", + "sphere", + "develop", + "insight", + "zone", + "messager" + ], + "CoreServiceNames": [ + "ring", + "pass", + "drive", + "sphere" + ] } -} \ No newline at end of file +}