116 lines
4.0 KiB
C#
116 lines
4.0 KiB
C#
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),
|
|
RouteDefaultTimeout = context.Items.TryGetValue(RouterHttpContextKeys.RouteDefaultTimeout, out var routeTimeoutObj) &&
|
|
routeTimeoutObj is TimeSpan routeTimeout
|
|
? routeTimeout
|
|
: null,
|
|
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;
|
|
}
|
|
}
|