Customizable gateway endpoints

This commit is contained in:
2026-01-25 01:27:17 +08:00
parent b851b9f6e2
commit db3a6ea9c5
6 changed files with 160 additions and 7 deletions

View File

@@ -0,0 +1,56 @@
using System.ComponentModel.DataAnnotations;
namespace DysonNetwork.Gateway.Configuration;
public class GatewayEndpointsOptions
{
public const string SectionName = "Endpoints";
/// <summary>
/// List of all services that the gateway should manage.
/// If not specified, defaults to the built-in service list.
/// </summary>
public List<string>? ServiceNames { get; set; }
/// <summary>
/// List of core services that are essential for the application to function.
/// If not specified, defaults to the built-in core service list.
/// </summary>
public List<string>? CoreServiceNames { get; set; }
/// <summary>
/// Default service names used when no configuration is provided.
/// </summary>
public static readonly string[] DefaultServiceNames =
[
"ring",
"pass",
"drive",
"sphere",
"develop",
"insight",
"zone",
"messager"
];
/// <summary>
/// Default core service names used when no configuration is provided.
/// </summary>
public static readonly string[] DefaultCoreServiceNames =
[
"ring",
"pass",
"drive",
"sphere"
];
/// <summary>
/// Gets the effective service names, using configuration if available, otherwise defaults.
/// </summary>
public string[] GetServiceNames() => ServiceNames?.ToArray() ?? DefaultServiceNames;
/// <summary>
/// Gets the effective core service names, using configuration if available, otherwise defaults.
/// </summary>
public string[] GetCoreServiceNames() => CoreServiceNames?.ToArray() ?? DefaultCoreServiceNames;
}

View File

@@ -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"
];
}
// 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;
/// <summary>
/// Initializes the service names from configuration options.
/// This method should be called during application startup.
/// </summary>
/// <param name="options">The gateway endpoints options containing configuration</param>
public static void InitializeFromConfiguration(DysonNetwork.Gateway.Configuration.GatewayEndpointsOptions options)
{
ServiceNames = options.GetServiceNames();
CoreServiceNames = options.GetCoreServiceNames();
}
/// <summary>
/// Resets the service names to their default values.
/// Useful for testing or when configuration is not available.
/// </summary>
public static void ResetToDefaults()
{
ServiceNames = DefaultServiceNames;
CoreServiceNames = DefaultCoreServiceNames;
}
}

View File

@@ -57,4 +57,4 @@ public class GatewayHealthAggregator(IHttpClientFactory httpClientFactory, Gatew
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}
}

View File

@@ -34,6 +34,42 @@ public class GatewayReadinessStore
InitializeServices(GatewayConstant.ServiceNames);
}
/// <summary>
/// Reinitializes the store with new service names from configuration.
/// This method should be called when configuration changes.
/// </summary>
/// <param name="serviceNames">The new service names to track</param>
public void ReinitializeServices(string[] serviceNames)
{
lock (_lock)
{
// Preserve existing health states for services that still exist
var existingStates = new Dictionary<string, ServiceHealthState>(_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<string> serviceNames)
{
lock (_lock)

View File

@@ -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<GatewayReadinessStore>();
builder.Services.AddHostedService<GatewayHealthAggregator>();
// Add configuration options for gateway endpoints
builder.Services.Configure<DysonNetwork.Gateway.Configuration.GatewayEndpointsOptions>(
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<IOptions<GatewayEndpointsOptions>>().Value;
GatewayConstant.InitializeFromConfiguration(gatewayEndpointsOptions);
// Reinitialize the readiness store with configured service names
var readinessStore = app.Services.GetRequiredService<GatewayReadinessStore>();
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();

View File

@@ -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"
]
}
}
}