177 lines
6.5 KiB
C#
177 lines
6.5 KiB
C#
using Grpc.Core;
|
|
using MxGateway.Contracts;
|
|
using MxGateway.Contracts.Proto;
|
|
using MxGateway.Server.Security.Authorization;
|
|
using MxGateway.Server.Sessions;
|
|
using MxGateway.Server.Workers;
|
|
|
|
namespace MxGateway.Server.Grpc;
|
|
|
|
public sealed class MxAccessGatewayService(
|
|
ISessionManager sessionManager,
|
|
IGatewayRequestIdentityAccessor identityAccessor,
|
|
MxAccessGrpcRequestValidator requestValidator,
|
|
MxAccessGrpcMapper mapper,
|
|
IEventStreamService eventStreamService,
|
|
ILogger<MxAccessGatewayService> logger) : MxAccessGateway.MxAccessGatewayBase
|
|
{
|
|
public override async Task<OpenSessionReply> OpenSession(
|
|
OpenSessionRequest request,
|
|
ServerCallContext context)
|
|
{
|
|
try
|
|
{
|
|
requestValidator.ValidateOpenSession(request);
|
|
GatewaySession session = await sessionManager
|
|
.OpenSessionAsync(
|
|
SessionOpenRequest.FromContract(request),
|
|
ResolveClientIdentity(),
|
|
context.CancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
OpenSessionReply reply = new()
|
|
{
|
|
SessionId = session.SessionId,
|
|
BackendName = session.BackendName,
|
|
WorkerProcessId = session.WorkerProcessId ?? 0,
|
|
WorkerProtocolVersion = GatewayContractInfo.WorkerProtocolVersion,
|
|
DefaultCommandTimeout = Google.Protobuf.WellKnownTypes.Duration.FromTimeSpan(session.CommandTimeout),
|
|
ProtocolStatus = MxAccessGrpcMapper.Ok(),
|
|
};
|
|
reply.Capabilities.Add("unary-open-session");
|
|
reply.Capabilities.Add("unary-close-session");
|
|
reply.Capabilities.Add("unary-invoke");
|
|
reply.Capabilities.Add("server-stream-events");
|
|
|
|
return reply;
|
|
}
|
|
catch (Exception exception) when (exception is not RpcException)
|
|
{
|
|
throw MapException(exception);
|
|
}
|
|
}
|
|
|
|
public override async Task<CloseSessionReply> CloseSession(
|
|
CloseSessionRequest request,
|
|
ServerCallContext context)
|
|
{
|
|
try
|
|
{
|
|
requestValidator.ValidateCloseSession(request);
|
|
SessionCloseResult result = await sessionManager
|
|
.CloseSessionAsync(request.SessionId, context.CancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
return new CloseSessionReply
|
|
{
|
|
SessionId = result.SessionId,
|
|
FinalState = result.FinalState,
|
|
ProtocolStatus = MxAccessGrpcMapper.Ok(result.AlreadyClosed ? "Session was already closed." : "Session closed."),
|
|
};
|
|
}
|
|
catch (Exception exception) when (exception is not RpcException)
|
|
{
|
|
throw MapException(exception);
|
|
}
|
|
}
|
|
|
|
public override async Task<MxCommandReply> Invoke(
|
|
MxCommandRequest request,
|
|
ServerCallContext context)
|
|
{
|
|
try
|
|
{
|
|
requestValidator.ValidateInvoke(request);
|
|
WorkerCommand workerCommand = mapper.MapCommand(request);
|
|
WorkerCommandReply workerReply = await sessionManager
|
|
.InvokeAsync(request.SessionId, workerCommand, context.CancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
return mapper.MapCommandReply(workerReply);
|
|
}
|
|
catch (Exception exception) when (exception is not RpcException)
|
|
{
|
|
throw MapException(exception);
|
|
}
|
|
}
|
|
|
|
public override async Task StreamEvents(
|
|
StreamEventsRequest request,
|
|
IServerStreamWriter<MxEvent> responseStream,
|
|
ServerCallContext context)
|
|
{
|
|
try
|
|
{
|
|
requestValidator.ValidateStreamEvents(request);
|
|
await foreach (MxEvent publicEvent in eventStreamService
|
|
.StreamEventsAsync(request, context.CancellationToken)
|
|
.WithCancellation(context.CancellationToken)
|
|
.ConfigureAwait(false))
|
|
{
|
|
await responseStream.WriteAsync(publicEvent).ConfigureAwait(false);
|
|
}
|
|
}
|
|
catch (Exception exception) when (exception is not RpcException)
|
|
{
|
|
throw MapException(exception);
|
|
}
|
|
}
|
|
|
|
private string? ResolveClientIdentity()
|
|
{
|
|
return identityAccessor.Current?.DisplayName ?? identityAccessor.Current?.KeyId;
|
|
}
|
|
|
|
private RpcException MapException(Exception exception)
|
|
{
|
|
if (exception is OperationCanceledException)
|
|
{
|
|
return new RpcException(new Status(StatusCode.Cancelled, "gRPC request was canceled."));
|
|
}
|
|
|
|
if (exception is SessionManagerException sessionException)
|
|
{
|
|
return MapSessionException(sessionException);
|
|
}
|
|
|
|
if (exception is WorkerClientException workerClientException)
|
|
{
|
|
return MapWorkerClientException(workerClientException);
|
|
}
|
|
|
|
logger.LogWarning(exception, "Public gRPC request failed.");
|
|
return new RpcException(new Status(StatusCode.Unavailable, "Gateway request failed before an MXAccess reply was available."));
|
|
}
|
|
|
|
private static RpcException MapSessionException(SessionManagerException exception)
|
|
{
|
|
StatusCode statusCode = exception.ErrorCode switch
|
|
{
|
|
SessionManagerErrorCode.SessionNotFound => StatusCode.NotFound,
|
|
SessionManagerErrorCode.SessionNotReady => StatusCode.FailedPrecondition,
|
|
SessionManagerErrorCode.EventSubscriberAlreadyActive => StatusCode.ResourceExhausted,
|
|
SessionManagerErrorCode.EventQueueOverflow => StatusCode.ResourceExhausted,
|
|
SessionManagerErrorCode.SessionLimitExceeded => StatusCode.ResourceExhausted,
|
|
SessionManagerErrorCode.OpenFailed => StatusCode.Unavailable,
|
|
SessionManagerErrorCode.CloseFailed => StatusCode.Unavailable,
|
|
_ => StatusCode.Unavailable,
|
|
};
|
|
|
|
return new RpcException(new Status(statusCode, exception.Message));
|
|
}
|
|
|
|
private static RpcException MapWorkerClientException(WorkerClientException exception)
|
|
{
|
|
StatusCode statusCode = exception.ErrorCode switch
|
|
{
|
|
WorkerClientErrorCode.CommandTimeout => StatusCode.DeadlineExceeded,
|
|
WorkerClientErrorCode.GatewayShutdown => StatusCode.Cancelled,
|
|
WorkerClientErrorCode.InvalidState => StatusCode.FailedPrecondition,
|
|
WorkerClientErrorCode.ProtocolViolation => StatusCode.Internal,
|
|
_ => StatusCode.Unavailable,
|
|
};
|
|
|
|
return new RpcException(new Status(statusCode, exception.Message));
|
|
}
|
|
}
|