172 lines
5.2 KiB
C#
172 lines
5.2 KiB
C#
using Grpc.Core;
|
|
using Grpc.Net.Client;
|
|
using Microsoft.Extensions.Logging;
|
|
using MxGateway.Contracts.Proto;
|
|
using Polly;
|
|
|
|
namespace MxGateway.Client;
|
|
|
|
/// <summary>
|
|
/// Provides the .NET client entry point for the public MXAccess Gateway gRPC API.
|
|
/// </summary>
|
|
public sealed class MxGatewayClient : IAsyncDisposable
|
|
{
|
|
private readonly GrpcChannel _channel;
|
|
private readonly IMxGatewayClientTransport _transport;
|
|
private readonly ResiliencePipeline _safeUnaryRetryPipeline;
|
|
private bool _disposed;
|
|
|
|
internal MxGatewayClient(
|
|
MxGatewayClientOptions options,
|
|
IMxGatewayClientTransport transport)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(options);
|
|
options.Validate();
|
|
|
|
Options = options;
|
|
_transport = transport ?? throw new ArgumentNullException(nameof(transport));
|
|
_safeUnaryRetryPipeline = MxGatewayClientRetryPolicy.Create(
|
|
options.Retry,
|
|
options.LoggerFactory?.CreateLogger<MxGatewayClient>());
|
|
_channel = null!;
|
|
}
|
|
|
|
private MxGatewayClient(
|
|
GrpcChannel channel,
|
|
IMxGatewayClientTransport transport)
|
|
{
|
|
_channel = channel;
|
|
_transport = transport;
|
|
Options = transport.Options;
|
|
_safeUnaryRetryPipeline = MxGatewayClientRetryPolicy.Create(
|
|
Options.Retry,
|
|
Options.LoggerFactory?.CreateLogger<MxGatewayClient>());
|
|
}
|
|
|
|
public MxGatewayClientOptions Options { get; }
|
|
|
|
public MxAccessGateway.MxAccessGatewayClient RawClient =>
|
|
_transport.RawClient
|
|
?? throw new InvalidOperationException("The raw generated gRPC client is not available for this client instance.");
|
|
|
|
public static MxGatewayClient Create(MxGatewayClientOptions options)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(options);
|
|
options.Validate();
|
|
|
|
var channel = GrpcChannel.ForAddress(
|
|
options.Endpoint,
|
|
new GrpcChannelOptions
|
|
{
|
|
LoggerFactory = options.LoggerFactory,
|
|
});
|
|
|
|
return new MxGatewayClient(
|
|
channel,
|
|
new GrpcMxGatewayClientTransport(
|
|
options,
|
|
new MxAccessGateway.MxAccessGatewayClient(channel)));
|
|
}
|
|
|
|
public async Task<MxGatewaySession> OpenSessionAsync(
|
|
OpenSessionRequest? request = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
OpenSessionReply reply = await OpenSessionRawAsync(
|
|
request ?? new OpenSessionRequest(),
|
|
cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
return new MxGatewaySession(this, reply);
|
|
}
|
|
|
|
public Task<OpenSessionReply> OpenSessionRawAsync(
|
|
OpenSessionRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
ThrowIfDisposed();
|
|
|
|
return _transport.OpenSessionAsync(request, CreateCallOptions(cancellationToken));
|
|
}
|
|
|
|
public Task<CloseSessionReply> CloseSessionRawAsync(
|
|
CloseSessionRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
ThrowIfDisposed();
|
|
|
|
return ExecuteSafeUnaryAsync(
|
|
token => _transport.CloseSessionAsync(request, CreateCallOptions(token)),
|
|
cancellationToken);
|
|
}
|
|
|
|
public Task<MxCommandReply> InvokeAsync(
|
|
MxCommandRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
ThrowIfDisposed();
|
|
|
|
if (MxGatewayClientRetryPolicy.IsRetryableCommand(request.Command?.Kind ?? MxCommandKind.Unspecified))
|
|
{
|
|
return ExecuteSafeUnaryAsync(
|
|
token => _transport.InvokeAsync(request, CreateCallOptions(token)),
|
|
cancellationToken);
|
|
}
|
|
|
|
return _transport.InvokeAsync(request, CreateCallOptions(cancellationToken));
|
|
}
|
|
|
|
public IAsyncEnumerable<MxEvent> StreamEventsAsync(
|
|
StreamEventsRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
ThrowIfDisposed();
|
|
|
|
return _transport.StreamEventsAsync(request, CreateCallOptions(cancellationToken));
|
|
}
|
|
|
|
public ValueTask DisposeAsync()
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
_disposed = true;
|
|
_channel?.Dispose();
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
internal CallOptions CreateCallOptions(CancellationToken cancellationToken)
|
|
{
|
|
Metadata headers = new()
|
|
{
|
|
{ "authorization", $"Bearer {Options.ApiKey}" },
|
|
};
|
|
|
|
return new CallOptions(
|
|
headers,
|
|
DateTime.UtcNow.Add(Options.DefaultCallTimeout),
|
|
cancellationToken);
|
|
}
|
|
|
|
private Task<T> ExecuteSafeUnaryAsync<T>(
|
|
Func<CancellationToken, Task<T>> call,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
return _safeUnaryRetryPipeline.ExecuteAsync(
|
|
async token => await call(token).ConfigureAwait(false),
|
|
cancellationToken)
|
|
.AsTask();
|
|
}
|
|
|
|
private void ThrowIfDisposed()
|
|
{
|
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
}
|
|
}
|