✨ Customizable gateway endpoints
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
@@ -2,7 +2,8 @@ namespace DysonNetwork.Gateway.Health;
|
|||||||
|
|
||||||
public abstract class GatewayConstant
|
public abstract class GatewayConstant
|
||||||
{
|
{
|
||||||
public static readonly string[] ServiceNames =
|
// Default service names used when no configuration is provided
|
||||||
|
private static readonly string[] DefaultServiceNames =
|
||||||
[
|
[
|
||||||
"ring",
|
"ring",
|
||||||
"pass",
|
"pass",
|
||||||
@@ -14,12 +15,39 @@ public abstract class GatewayConstant
|
|||||||
"messager"
|
"messager"
|
||||||
];
|
];
|
||||||
|
|
||||||
// Core services stands with w/o these services the functional of entire app will broke.
|
// Default core service names used when no configuration is provided
|
||||||
public static readonly string[] CoreServiceNames =
|
private static readonly string[] DefaultCoreServiceNames =
|
||||||
[
|
[
|
||||||
"ring",
|
"ring",
|
||||||
"pass",
|
"pass",
|
||||||
"drive",
|
"drive",
|
||||||
"sphere"
|
"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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,4 +57,4 @@ public class GatewayHealthAggregator(IHttpClientFactory httpClientFactory, Gatew
|
|||||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,42 @@ public class GatewayReadinessStore
|
|||||||
InitializeServices(GatewayConstant.ServiceNames);
|
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)
|
private void InitializeServices(IEnumerable<string> serviceNames)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
|
using DysonNetwork.Gateway.Configuration;
|
||||||
using DysonNetwork.Gateway.Health;
|
using DysonNetwork.Gateway.Health;
|
||||||
using DysonNetwork.Shared.Networking;
|
using DysonNetwork.Shared.Networking;
|
||||||
using Yarp.ReverseProxy.Configuration;
|
using Yarp.ReverseProxy.Configuration;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using NodaTime.Serialization.SystemTextJson;
|
using NodaTime.Serialization.SystemTextJson;
|
||||||
|
|
||||||
@@ -17,6 +19,10 @@ builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxV
|
|||||||
builder.Services.AddSingleton<GatewayReadinessStore>();
|
builder.Services.AddSingleton<GatewayReadinessStore>();
|
||||||
builder.Services.AddHostedService<GatewayHealthAggregator>();
|
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 =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
options.AddDefaultPolicy(policy =>
|
options.AddDefaultPolicy(policy =>
|
||||||
@@ -170,6 +176,15 @@ builder.Services.AddControllers().AddJsonOptions(options =>
|
|||||||
|
|
||||||
var app = builder.Build();
|
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
|
var forwardedHeadersOptions = new ForwardedHeadersOptions
|
||||||
{
|
{
|
||||||
ForwardedHeaders = ForwardedHeaders.All
|
ForwardedHeaders = ForwardedHeaders.All
|
||||||
@@ -186,4 +201,4 @@ app.MapReverseProxy().RequireRateLimiting("fixed");
|
|||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
@@ -12,5 +12,23 @@
|
|||||||
"SiteUrl": "http://localhost:3000",
|
"SiteUrl": "http://localhost:3000",
|
||||||
"Client": {
|
"Client": {
|
||||||
"SomeSetting": "SomeValue"
|
"SomeSetting": "SomeValue"
|
||||||
|
},
|
||||||
|
"Endpoints": {
|
||||||
|
"ServiceNames": [
|
||||||
|
"ring",
|
||||||
|
"pass",
|
||||||
|
"drive",
|
||||||
|
"sphere",
|
||||||
|
"develop",
|
||||||
|
"insight",
|
||||||
|
"zone",
|
||||||
|
"messager"
|
||||||
|
],
|
||||||
|
"CoreServiceNames": [
|
||||||
|
"ring",
|
||||||
|
"pass",
|
||||||
|
"drive",
|
||||||
|
"sphere"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user