Add Canonical JSON serialization library with tests and documentation
- Implemented CanonJson class for deterministic JSON serialization and hashing. - Added unit tests for CanonJson functionality, covering various scenarios including key sorting, handling of nested objects, arrays, and special characters. - Created project files for the Canonical JSON library and its tests, including necessary package references. - Added README.md for library usage and API reference. - Introduced RabbitMqIntegrationFactAttribute for conditional RabbitMQ integration tests.
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Router.Common.Abstractions;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Frames;
|
||||
using StellaOps.Router.Common.Models;
|
||||
|
||||
namespace StellaOps.Microservice;
|
||||
@@ -14,6 +16,7 @@ public sealed class RouterConnectionManager : IRouterConnectionManager, IDisposa
|
||||
{
|
||||
private readonly StellaMicroserviceOptions _options;
|
||||
private readonly IEndpointDiscoveryProvider _endpointDiscovery;
|
||||
private readonly RequestDispatcher _requestDispatcher;
|
||||
private readonly IMicroserviceTransport? _microserviceTransport;
|
||||
private readonly IGeneratedEndpointProvider? _generatedProvider;
|
||||
private readonly ILogger<RouterConnectionManager> _logger;
|
||||
@@ -37,12 +40,14 @@ public sealed class RouterConnectionManager : IRouterConnectionManager, IDisposa
|
||||
public RouterConnectionManager(
|
||||
IOptions<StellaMicroserviceOptions> options,
|
||||
IEndpointDiscoveryProvider endpointDiscovery,
|
||||
RequestDispatcher requestDispatcher,
|
||||
IMicroserviceTransport? microserviceTransport,
|
||||
IGeneratedEndpointProvider? generatedProvider,
|
||||
ILogger<RouterConnectionManager> logger)
|
||||
ILogger<RouterConnectionManager> logger,
|
||||
IGeneratedEndpointProvider? generatedProvider = null)
|
||||
{
|
||||
_options = options.Value;
|
||||
_endpointDiscovery = endpointDiscovery;
|
||||
_requestDispatcher = requestDispatcher;
|
||||
_microserviceTransport = microserviceTransport;
|
||||
_generatedProvider = generatedProvider;
|
||||
_logger = logger;
|
||||
@@ -91,6 +96,12 @@ public sealed class RouterConnectionManager : IRouterConnectionManager, IDisposa
|
||||
_endpoints = _endpointDiscovery.DiscoverEndpoints();
|
||||
_logger.LogInformation("Discovered {EndpointCount} endpoints", _endpoints.Count);
|
||||
|
||||
// Wire request handling before transport connect to avoid a race after HELLO.
|
||||
if (_microserviceTransport is not null)
|
||||
{
|
||||
_microserviceTransport.OnRequestReceived += HandleRequestReceivedAsync;
|
||||
}
|
||||
|
||||
// Get schema definitions from generated provider
|
||||
_schemas = _generatedProvider?.GetSchemaDefinitions()
|
||||
?? new Dictionary<string, SchemaDefinition>();
|
||||
@@ -110,6 +121,24 @@ public sealed class RouterConnectionManager : IRouterConnectionManager, IDisposa
|
||||
await ConnectToRouterAsync(router, cancellationToken);
|
||||
}
|
||||
|
||||
// Establish transport connection to the gateway (InMemory/TCP/RabbitMQ/etc).
|
||||
if (_microserviceTransport is not null)
|
||||
{
|
||||
var instance = new InstanceDescriptor
|
||||
{
|
||||
InstanceId = _options.InstanceId,
|
||||
ServiceName = _options.ServiceName,
|
||||
Version = _options.Version,
|
||||
Region = _options.Region
|
||||
};
|
||||
|
||||
await _microserviceTransport.ConnectAsync(instance, _endpoints, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("No microservice transport configured; skipping transport connection.");
|
||||
}
|
||||
|
||||
// Start heartbeat task
|
||||
_heartbeatTask = Task.Run(() => HeartbeatLoopAsync(_cts.Token), CancellationToken.None);
|
||||
}
|
||||
@@ -121,6 +150,22 @@ public sealed class RouterConnectionManager : IRouterConnectionManager, IDisposa
|
||||
|
||||
await _cts.CancelAsync();
|
||||
|
||||
if (_microserviceTransport is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _microserviceTransport.DisconnectAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to disconnect transport");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_microserviceTransport.OnRequestReceived -= HandleRequestReceivedAsync;
|
||||
}
|
||||
}
|
||||
|
||||
if (_heartbeatTask is not null)
|
||||
{
|
||||
try
|
||||
@@ -136,6 +181,42 @@ public sealed class RouterConnectionManager : IRouterConnectionManager, IDisposa
|
||||
_connections.Clear();
|
||||
}
|
||||
|
||||
private async Task<Frame> HandleRequestReceivedAsync(Frame frame, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = FrameConverter.ToRequestFrame(frame);
|
||||
if (request is null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Received invalid request frame: type={FrameType}, correlationId={CorrelationId}",
|
||||
frame.Type,
|
||||
frame.CorrelationId ?? "(null)");
|
||||
|
||||
var error = new ResponseFrame
|
||||
{
|
||||
RequestId = frame.CorrelationId ?? Guid.NewGuid().ToString("N"),
|
||||
StatusCode = 400,
|
||||
Headers = new Dictionary<string, string>
|
||||
{
|
||||
["Content-Type"] = "text/plain; charset=utf-8"
|
||||
},
|
||||
Payload = Encoding.UTF8.GetBytes("Invalid request frame")
|
||||
};
|
||||
|
||||
var errorFrame = FrameConverter.ToFrame(error);
|
||||
return frame.CorrelationId is null
|
||||
? errorFrame
|
||||
: errorFrame with { CorrelationId = frame.CorrelationId };
|
||||
}
|
||||
|
||||
var response = await _requestDispatcher.DispatchAsync(request, cancellationToken);
|
||||
var responseFrame = FrameConverter.ToFrame(response);
|
||||
|
||||
// Ensure correlation ID matches the incoming request for transport-level matching.
|
||||
return frame.CorrelationId is null
|
||||
? responseFrame
|
||||
: responseFrame with { CorrelationId = frame.CorrelationId };
|
||||
}
|
||||
|
||||
private async Task ConnectToRouterAsync(RouterEndpointConfig router, CancellationToken cancellationToken)
|
||||
{
|
||||
var connectionId = $"{router.Host}:{router.Port}";
|
||||
|
||||
Reference in New Issue
Block a user