Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Gateway.Configuration;
|
||||
|
||||
namespace StellaOps.Router.Gateway.Middleware;
|
||||
|
||||
/// <summary>
|
||||
/// Makes routing decisions for resolved endpoints.
|
||||
/// </summary>
|
||||
public sealed class RoutingDecisionMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutingDecisionMiddleware"/> class.
|
||||
/// </summary>
|
||||
public RoutingDecisionMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the middleware.
|
||||
/// </summary>
|
||||
public async Task Invoke(
|
||||
HttpContext context,
|
||||
IRoutingPlugin routingPlugin,
|
||||
IGlobalRoutingState routingState,
|
||||
IOptions<RouterNodeConfig> gatewayConfig,
|
||||
IOptions<RoutingOptions> routingOptions)
|
||||
{
|
||||
var endpoint = context.Items[RouterHttpContextKeys.EndpointDescriptor] as EndpointDescriptor;
|
||||
if (endpoint is null)
|
||||
{
|
||||
await RouterErrorWriter.WriteAsync(
|
||||
context,
|
||||
statusCode: StatusCodes.Status500InternalServerError,
|
||||
error: "Endpoint descriptor missing",
|
||||
cancellationToken: context.RequestAborted);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build routing context
|
||||
var availableConnections = routingState.GetConnectionsFor(
|
||||
endpoint.ServiceName,
|
||||
endpoint.Version,
|
||||
endpoint.Method,
|
||||
endpoint.Path);
|
||||
|
||||
var headers = context.Request.Headers
|
||||
.ToDictionary(h => h.Key, h => h.Value.ToString());
|
||||
|
||||
var routingContext = new RoutingContext
|
||||
{
|
||||
Method = context.Request.Method,
|
||||
Path = context.Request.Path.ToString(),
|
||||
Headers = headers,
|
||||
Endpoint = endpoint,
|
||||
AvailableConnections = availableConnections,
|
||||
GatewayRegion = gatewayConfig.Value.Region,
|
||||
RequestedVersion = ExtractVersionFromRequest(context, routingOptions.Value),
|
||||
CancellationToken = context.RequestAborted
|
||||
};
|
||||
|
||||
var decision = await routingPlugin.ChooseInstanceAsync(
|
||||
routingContext,
|
||||
context.RequestAborted);
|
||||
|
||||
if (decision is null)
|
||||
{
|
||||
await RouterErrorWriter.WriteAsync(
|
||||
context,
|
||||
statusCode: StatusCodes.Status503ServiceUnavailable,
|
||||
error: "No instances available",
|
||||
service: endpoint.ServiceName,
|
||||
version: endpoint.Version,
|
||||
cancellationToken: context.RequestAborted);
|
||||
return;
|
||||
}
|
||||
|
||||
context.Items[RouterHttpContextKeys.RoutingDecision] = decision;
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
private static string? ExtractVersionFromRequest(HttpContext context, RoutingOptions options)
|
||||
{
|
||||
// Check for version in Accept header: Accept: application/vnd.stellaops.v1+json
|
||||
var acceptHeader = context.Request.Headers.Accept.FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(acceptHeader))
|
||||
{
|
||||
var versionMatch = System.Text.RegularExpressions.Regex.Match(
|
||||
acceptHeader,
|
||||
@"application/vnd\.stellaops\.v(\d+(?:\.\d+)*)\+json");
|
||||
if (versionMatch.Success)
|
||||
{
|
||||
return versionMatch.Groups[1].Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for X-Api-Version header
|
||||
var versionHeader = context.Request.Headers["X-Api-Version"].FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(versionHeader))
|
||||
{
|
||||
return versionHeader;
|
||||
}
|
||||
|
||||
// Fall back to default version from options
|
||||
return options.DefaultVersion;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user