Issue #13: implement public grpc service
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
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,
|
||||
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 (WorkerEvent workerEvent in sessionManager
|
||||
.ReadEventsAsync(request.SessionId, context.CancellationToken)
|
||||
.WithCancellation(context.CancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
{
|
||||
MxEvent publicEvent = mapper.MapEvent(workerEvent);
|
||||
if (publicEvent.WorkerSequence <= request.AfterWorkerSequence)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
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.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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user