Switch to GrpcClientIdentificationService and abolish Redis for Idents (#12)
* add GrpcClientIdentificationService * remove unnecessary gauges * set to no retry policy * initialize metrics Co-authored-by: Stanley Dimant <root.darkarchon@outlook.com>
This commit is contained in:
@@ -8,9 +8,7 @@ using MareSynchronosServer.Utils;
|
||||
using MareSynchronosShared.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Models;
|
||||
using MareSynchronosShared.Protos;
|
||||
using MareSynchronosShared.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
@@ -27,7 +25,7 @@ public partial class MareHub : Hub
|
||||
private readonly FileService.FileServiceClient _fileServiceClient;
|
||||
private readonly SystemInfoService _systemInfoService;
|
||||
private readonly IHttpContextAccessor _contextAccessor;
|
||||
private readonly IClientIdentificationService _clientIdentService;
|
||||
private readonly GrpcClientIdentificationService _clientIdentService;
|
||||
private readonly MareHubLogger _logger;
|
||||
private readonly MareDbContext _dbContext;
|
||||
private readonly Uri _cdnFullUri;
|
||||
@@ -38,7 +36,7 @@ public partial class MareHub : Hub
|
||||
|
||||
public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient,
|
||||
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor,
|
||||
IClientIdentificationService clientIdentService)
|
||||
GrpcClientIdentificationService clientIdentService)
|
||||
{
|
||||
_mareMetrics = mareMetrics;
|
||||
_authServiceClient = authServiceClient;
|
||||
@@ -118,9 +116,11 @@ public partial class MareHub : Hub
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
public async Task<bool> CheckClientHealth()
|
||||
{
|
||||
var needsReconnect = string.IsNullOrEmpty(await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false));
|
||||
if (needsReconnect)
|
||||
var serverId = await _clientIdentService.GetServerForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
bool needsReconnect = false;
|
||||
if (string.IsNullOrEmpty(serverId) || !string.Equals(serverId, _shardName, StringComparison.Ordinal))
|
||||
{
|
||||
needsReconnect = true;
|
||||
_logger.LogCallWarning(Api.InvokeCheckClientHealth, needsReconnect);
|
||||
}
|
||||
return needsReconnect;
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
using Grpc.Core;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Protos;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServer.Services;
|
||||
|
||||
public class GrpcClientIdentificationService : IHostedService
|
||||
{
|
||||
private readonly string _shardName;
|
||||
private readonly ILogger<GrpcClientIdentificationService> _logger;
|
||||
private readonly IdentificationService.IdentificationServiceClient _grpcIdentClient;
|
||||
private readonly MareMetrics _metrics;
|
||||
protected ConcurrentDictionary<string, string> OnlineClients = new(StringComparer.Ordinal);
|
||||
private bool _grpcIsFaulty = false;
|
||||
|
||||
public GrpcClientIdentificationService(ILogger<GrpcClientIdentificationService> logger, IdentificationService.IdentificationServiceClient gprcIdentClient, MareMetrics metrics, IConfiguration configuration)
|
||||
{
|
||||
var config = configuration.GetSection("MareSynchronos");
|
||||
_shardName = config.GetValue("ServerName", "Main");
|
||||
_logger = logger;
|
||||
_grpcIdentClient = gprcIdentClient;
|
||||
_metrics = metrics;
|
||||
}
|
||||
|
||||
public async Task<string?> GetCharacterIdentForUid(string uid)
|
||||
{
|
||||
if (OnlineClients.TryGetValue(uid, out string ident))
|
||||
{
|
||||
return ident;
|
||||
}
|
||||
|
||||
var result = await InvokeOnGrpc(_grpcIdentClient.GetIdentForUidAsync(new UidMessage { Uid = uid })).ConfigureAwait(false);
|
||||
if (result == default(CharacterIdentMessage)) return null;
|
||||
return result.Ident;
|
||||
}
|
||||
|
||||
public async Task<string?> GetServerForUid(string uid)
|
||||
{
|
||||
if (OnlineClients.ContainsKey(uid))
|
||||
{
|
||||
return _shardName;
|
||||
}
|
||||
|
||||
var result = await InvokeOnGrpc(_grpcIdentClient.GetIdentForUidAsync(new UidMessage { Uid = uid })).ConfigureAwait(false);
|
||||
if (result == default(CharacterIdentMessage)) return null;
|
||||
return result.ServerId;
|
||||
}
|
||||
|
||||
public async Task<long> GetOnlineUsers()
|
||||
{
|
||||
var result = await InvokeOnGrpc(_grpcIdentClient.GetOnlineUserCountAsync(new ServerMessage())).ConfigureAwait(false);
|
||||
if (result == default(OnlineUserCountResponse)) return OnlineClients.Count;
|
||||
return result.Count;
|
||||
}
|
||||
|
||||
public async Task<string?> GetUidForCharacterIdent(string characterIdent)
|
||||
{
|
||||
bool existsLocal = OnlineClients.Any(o => string.Equals(o.Value, characterIdent, StringComparison.Ordinal));
|
||||
if (existsLocal)
|
||||
{
|
||||
return OnlineClients.First(c => string.Equals(c.Value, characterIdent, StringComparison.Ordinal)).Key;
|
||||
}
|
||||
|
||||
var result = await InvokeOnGrpc(_grpcIdentClient.GetUidForCharacterIdentAsync(new CharacterIdentMessage { Ident = characterIdent, ServerId = string.Empty })).ConfigureAwait(false);
|
||||
if (result == default(UidMessage)) return null;
|
||||
return result.Uid;
|
||||
}
|
||||
|
||||
public async Task MarkUserOffline(string uid)
|
||||
{
|
||||
OnlineClients.TryRemove(uid, out _);
|
||||
_metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||
await ExecuteOnGrpc(_grpcIdentClient.RemoveIdentForUidAsync(new RemoveIdentMessage() { ServerId = _shardName, Uid = uid })).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task MarkUserOnline(string uid, string charaIdent)
|
||||
{
|
||||
OnlineClients[uid] = charaIdent;
|
||||
_metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||
await ExecuteOnGrpc(_grpcIdentClient.SetIdentForUidAsync(new SetIdentMessage() { Ident = charaIdent, ServerId = _shardName, Uid = uid })).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await ExecuteOnGrpc(_grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName })).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<T> InvokeOnGrpc<T>(AsyncUnaryCall<T> toExecute)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await toExecute.ConfigureAwait(false);
|
||||
|
||||
await CheckFaultStateAndResend().ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
SetGrpcFaulty();
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteOnGrpc<T>(AsyncUnaryCall<T> toExecute)
|
||||
{
|
||||
try
|
||||
{
|
||||
await toExecute.ConfigureAwait(false);
|
||||
await CheckFaultStateAndResend().ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
SetGrpcFaulty();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckFaultStateAndResend()
|
||||
{
|
||||
if (_grpcIsFaulty)
|
||||
{
|
||||
_logger.LogInformation("GRPC connection is restored, sending current server idents");
|
||||
await _grpcIdentClient.ClearIdentsForServerAsync(new ServerMessage() { ServerId = _shardName }).ConfigureAwait(false);
|
||||
var msg = new ServerIdentMessage();
|
||||
msg.Idents.AddRange(OnlineClients.Select(c => new SetIdentMessage()
|
||||
{
|
||||
Ident = c.Value,
|
||||
Uid = c.Key,
|
||||
ServerId = _shardName
|
||||
}));
|
||||
await _grpcIdentClient.RecreateServerIdentsAsync(msg).ConfigureAwait(false);
|
||||
_grpcIsFaulty = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetGrpcFaulty()
|
||||
{
|
||||
if (!_grpcIsFaulty)
|
||||
{
|
||||
_grpcIsFaulty = true;
|
||||
_logger.LogWarning("GRPC connection is faulty");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ using MareSynchronos.API;
|
||||
using MareSynchronosServer.Hubs;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Services;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -19,14 +18,14 @@ public class SystemInfoService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly MareMetrics _mareMetrics;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IClientIdentificationService _clientIdentService;
|
||||
private readonly GrpcClientIdentificationService _clientIdentService;
|
||||
private readonly ILogger<SystemInfoService> _logger;
|
||||
private readonly IHubContext<MareHub> _hubContext;
|
||||
private Timer _timer;
|
||||
private string _shardName;
|
||||
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
||||
|
||||
public SystemInfoService(MareMetrics mareMetrics, IConfiguration configuration, IServiceProvider services, IClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext)
|
||||
public SystemInfoService(MareMetrics mareMetrics, IConfiguration configuration, IServiceProvider services, GrpcClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext)
|
||||
{
|
||||
_mareMetrics = mareMetrics;
|
||||
_services = services;
|
||||
@@ -56,7 +55,7 @@ public class SystemInfoService : IHostedService, IDisposable
|
||||
{
|
||||
SystemInfoDto = new SystemInfoDto()
|
||||
{
|
||||
OnlineUsers = _clientIdentService.GetOnlineUsers().Result,
|
||||
OnlineUsers = (int)_clientIdentService.GetOnlineUsers().Result,
|
||||
};
|
||||
|
||||
_hubContext.Clients.All.SendAsync(Api.OnUpdateSystemInfo, SystemInfoDto);
|
||||
|
||||
@@ -20,7 +20,6 @@ using Prometheus;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using System.Collections.Generic;
|
||||
using MareSynchronosServer.Services;
|
||||
using MareSynchronosShared.Services;
|
||||
using System.Net.Http;
|
||||
using MareSynchronosServer.Utils;
|
||||
|
||||
@@ -64,6 +63,12 @@ public class Startup
|
||||
}
|
||||
};
|
||||
|
||||
var identMethodConfig = new MethodConfig
|
||||
{
|
||||
Names = { MethodName.Default },
|
||||
RetryPolicy = null
|
||||
};
|
||||
|
||||
services.AddSingleton(new MareMetrics(new List<string>
|
||||
{
|
||||
MetricsAPI.CounterInitializedConnections,
|
||||
@@ -77,7 +82,10 @@ public class Startup
|
||||
MetricsAPI.GaugePairs,
|
||||
MetricsAPI.GaugePairsPaused,
|
||||
MetricsAPI.GaugeAvailableIOWorkerThreads,
|
||||
MetricsAPI.GaugeAvailableWorkerThreads
|
||||
MetricsAPI.GaugeAvailableWorkerThreads,
|
||||
MetricsAPI.GaugeGroups,
|
||||
MetricsAPI.GaugeGroupPairs,
|
||||
MetricsAPI.GaugeGroupPairsPaused
|
||||
}));
|
||||
|
||||
services.AddGrpcClient<AuthService.AuthServiceClient>(c =>
|
||||
@@ -98,6 +106,20 @@ public class Startup
|
||||
{
|
||||
c.ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } };
|
||||
});
|
||||
services.AddGrpcClient<IdentificationService.IdentificationServiceClient>(c =>
|
||||
{
|
||||
c.Address = new Uri(mareConfig.GetValue<string>("ServiceAddress"));
|
||||
}).ConfigureChannel(c =>
|
||||
{
|
||||
c.ServiceConfig = new ServiceConfig { MethodConfigs = { identMethodConfig } };
|
||||
c.HttpHandler = new SocketsHttpHandler()
|
||||
{
|
||||
EnableMultipleHttp2Connections = true
|
||||
};
|
||||
});
|
||||
|
||||
services.AddSingleton<GrpcClientIdentificationService>();
|
||||
services.AddHostedService(p => p.GetService<GrpcClientIdentificationService>());
|
||||
|
||||
services.AddDbContextPool<MareDbContext>(options =>
|
||||
{
|
||||
@@ -135,19 +157,6 @@ public class Startup
|
||||
{
|
||||
options.Configuration.ChannelPrefix = "MareSynchronos";
|
||||
});
|
||||
|
||||
services.AddStackExchangeRedisCache(opt =>
|
||||
{
|
||||
opt.Configuration = redis;
|
||||
opt.InstanceName = "MareSynchronosCache:";
|
||||
});
|
||||
services.AddSingleton<IClientIdentificationService, DistributedClientIdentificationService>();
|
||||
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddSingleton<IClientIdentificationService, LocalClientIdentificationService>();
|
||||
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
|
||||
}
|
||||
|
||||
services.AddHostedService(provider => provider.GetService<SystemInfoService>());
|
||||
|
||||
@@ -12,10 +12,10 @@ using Discord;
|
||||
using Discord.Rest;
|
||||
using Discord.WebSocket;
|
||||
using MareSynchronosServices.Authentication;
|
||||
using MareSynchronosServices.Identity;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Models;
|
||||
using MareSynchronosShared.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -24,11 +24,11 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronosServices.Discord;
|
||||
|
||||
public class DiscordBot : IHostedService
|
||||
internal class DiscordBot : IHostedService
|
||||
{
|
||||
private readonly CleanupService cleanupService;
|
||||
private readonly MareMetrics metrics;
|
||||
private readonly IClientIdentificationService clientService;
|
||||
private readonly IdentityHandler identityHandler;
|
||||
private readonly IServiceProvider services;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<DiscordBot> logger;
|
||||
@@ -44,16 +44,15 @@ public class DiscordBot : IHostedService
|
||||
private ConcurrentDictionary<ulong, DateTime> LastVanityChange = new();
|
||||
private ConcurrentDictionary<string, DateTime> LastVanityGidChange = new();
|
||||
private ulong vanityCommandId;
|
||||
private ulong vanityGidCommandId;
|
||||
private Task cleanUpUserTask = null;
|
||||
|
||||
private SemaphoreSlim semaphore;
|
||||
|
||||
public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IClientIdentificationService clientService, IServiceProvider services, IConfiguration configuration, ILogger<DiscordBot> logger)
|
||||
public DiscordBot(CleanupService cleanupService, MareMetrics metrics, IdentityHandler identityHandler, IServiceProvider services, IConfiguration configuration, ILogger<DiscordBot> logger)
|
||||
{
|
||||
this.cleanupService = cleanupService;
|
||||
this.metrics = metrics;
|
||||
this.clientService = clientService;
|
||||
this.identityHandler = identityHandler;
|
||||
this.services = services;
|
||||
_configuration = configuration.GetRequiredSection("MareSynchronos");
|
||||
this.logger = logger;
|
||||
@@ -804,7 +803,7 @@ public class DiscordBot : IHostedService
|
||||
updateStatusCts = new();
|
||||
while (!updateStatusCts.IsCancellationRequested)
|
||||
{
|
||||
var onlineUsers = await clientService.GetOnlineUsers();
|
||||
var onlineUsers = identityHandler.GetOnlineUsers(string.Empty);
|
||||
logger.LogInformation("Users online: " + onlineUsers);
|
||||
await discordClient.SetActivityAsync(new Game("Mare for " + onlineUsers + " Users")).ConfigureAwait(false);
|
||||
await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServices.Identity;
|
||||
|
||||
internal class IdentityHandler
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, ServerIdentity> cachedIdentities = new();
|
||||
|
||||
internal Task<string> GetUidForCharacterIdent(string ident, string serverId)
|
||||
{
|
||||
var exists = cachedIdentities.Any(f => f.Value.CharacterIdent == ident && f.Value.ServerId == serverId);
|
||||
return Task.FromResult(exists ? cachedIdentities.FirstOrDefault(f => f.Value.CharacterIdent == ident && f.Value.ServerId == serverId).Key : string.Empty);
|
||||
}
|
||||
|
||||
internal Task<ServerIdentity> GetIdentForuid(string uid)
|
||||
{
|
||||
ServerIdentity result;
|
||||
if (!cachedIdentities.TryGetValue(uid, out result))
|
||||
{
|
||||
result = new ServerIdentity();
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
internal void SetIdent(string uid, string serverId, string ident)
|
||||
{
|
||||
cachedIdentities[uid] = new ServerIdentity() { ServerId = serverId, CharacterIdent = ident };
|
||||
}
|
||||
|
||||
internal void RemoveIdent(string uid, string serverId)
|
||||
{
|
||||
if (cachedIdentities.ContainsKey(uid) && cachedIdentities[uid].ServerId == serverId)
|
||||
{
|
||||
cachedIdentities.TryRemove(uid, out _);
|
||||
}
|
||||
}
|
||||
|
||||
internal int GetOnlineUsers(string serverId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(serverId))
|
||||
return cachedIdentities.Count;
|
||||
return cachedIdentities.Count(c => c.Value.ServerId == serverId);
|
||||
}
|
||||
|
||||
internal void ClearIdentsForServer(string serverId)
|
||||
{
|
||||
var serverIdentities = cachedIdentities.Where(i => i.Value.ServerId == serverId);
|
||||
foreach (var identity in serverIdentities)
|
||||
{
|
||||
cachedIdentities.TryRemove(identity.Key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal record ServerIdentity
|
||||
{
|
||||
public string ServerId { get; set; } = string.Empty;
|
||||
public string CharacterIdent { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using Grpc.Core;
|
||||
using MareSynchronosShared.Protos;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServices.Identity;
|
||||
|
||||
internal class IdentityService : IdentificationService.IdentificationServiceBase
|
||||
{
|
||||
private readonly ILogger<IdentityService> _logger;
|
||||
private readonly IdentityHandler _handler;
|
||||
|
||||
public IdentityService(ILogger<IdentityService> logger, IdentityHandler handler)
|
||||
{
|
||||
_logger = logger;
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
public override Task<Empty> RemoveIdentForUid(RemoveIdentMessage request, ServerCallContext context)
|
||||
{
|
||||
_handler.RemoveIdent(request.Uid, request.ServerId);
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
|
||||
public override Task<Empty> SetIdentForUid(SetIdentMessage request, ServerCallContext context)
|
||||
{
|
||||
_handler.SetIdent(request.Uid, request.ServerId, request.Ident);
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
|
||||
public override async Task<CharacterIdentMessage> GetIdentForUid(UidMessage request, ServerCallContext context)
|
||||
{
|
||||
var result = await _handler.GetIdentForuid(request.Uid);
|
||||
return new CharacterIdentMessage()
|
||||
{
|
||||
Ident = result.CharacterIdent,
|
||||
ServerId = result.ServerId
|
||||
};
|
||||
}
|
||||
|
||||
public override async Task<UidMessage> GetUidForCharacterIdent(CharacterIdentMessage request, ServerCallContext context)
|
||||
{
|
||||
var result = await _handler.GetUidForCharacterIdent(request.Ident, request.ServerId);
|
||||
return new UidMessage()
|
||||
{
|
||||
Uid = result
|
||||
};
|
||||
}
|
||||
|
||||
public override Task<OnlineUserCountResponse> GetOnlineUserCount(ServerMessage request, ServerCallContext context)
|
||||
{
|
||||
return Task.FromResult(new OnlineUserCountResponse() { Count = _handler.GetOnlineUsers(request.ServerId) });
|
||||
}
|
||||
|
||||
public override Task<Empty> ClearIdentsForServer(ServerMessage request, ServerCallContext context)
|
||||
{
|
||||
_handler.ClearIdentsForServer(request.ServerId);
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
|
||||
public override Task<Empty> RecreateServerIdents(ServerIdentMessage request, ServerCallContext context)
|
||||
{
|
||||
foreach (var identMsg in request.Idents)
|
||||
{
|
||||
_handler.SetIdent(identMsg.Uid, identMsg.ServerId, identMsg.Ident);
|
||||
}
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Prometheus;
|
||||
using System.Collections.Generic;
|
||||
using MareSynchronosShared.Services;
|
||||
using MareSynchronosServices.Identity;
|
||||
|
||||
namespace MareSynchronosServices;
|
||||
|
||||
@@ -45,29 +45,12 @@ public class Startup
|
||||
}));
|
||||
|
||||
services.AddSingleton<SecretKeyAuthenticationHandler>();
|
||||
services.AddSingleton<IdentityHandler>();
|
||||
services.AddSingleton<CleanupService>();
|
||||
services.AddTransient(_ => Configuration);
|
||||
services.AddHostedService(provider => provider.GetService<CleanupService>());
|
||||
services.AddHostedService<DiscordBot>();
|
||||
services.AddGrpc();
|
||||
|
||||
// add redis related options
|
||||
var redis = Configuration.GetSection("MareSynchronos").GetValue("RedisConnectionString", string.Empty);
|
||||
if (!string.IsNullOrEmpty(redis))
|
||||
{
|
||||
services.AddStackExchangeRedisCache(opt =>
|
||||
{
|
||||
opt.Configuration = redis;
|
||||
opt.InstanceName = "MareSynchronosCache:";
|
||||
});
|
||||
services.AddSingleton<IClientIdentificationService, DistributedClientIdentificationService>();
|
||||
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddSingleton<IClientIdentificationService, LocalClientIdentificationService>();
|
||||
services.AddHostedService(p => p.GetService<IClientIdentificationService>());
|
||||
}
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
@@ -80,6 +63,7 @@ public class Startup
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapGrpcService<AuthenticationService>();
|
||||
endpoints.MapGrpcService<IdentityService>();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,50 @@ service FileService {
|
||||
rpc DeleteFiles (DeleteFilesRequest) returns (Empty);
|
||||
}
|
||||
|
||||
service IdentificationService {
|
||||
rpc GetOnlineUserCount (ServerMessage) returns (OnlineUserCountResponse);
|
||||
rpc RemoveIdentForUid (RemoveIdentMessage) returns (Empty);
|
||||
rpc SetIdentForUid (SetIdentMessage) returns (Empty);
|
||||
rpc GetUidForCharacterIdent (CharacterIdentMessage) returns (UidMessage);
|
||||
rpc GetIdentForUid (UidMessage) returns (CharacterIdentMessage);
|
||||
rpc ClearIdentsForServer (ServerMessage) returns (Empty);
|
||||
rpc RecreateServerIdents (ServerIdentMessage) returns (Empty);
|
||||
}
|
||||
|
||||
message Empty { }
|
||||
|
||||
message ServerIdentMessage {
|
||||
repeated SetIdentMessage idents = 1;
|
||||
}
|
||||
|
||||
message ServerMessage {
|
||||
string server_id = 1;
|
||||
}
|
||||
|
||||
message OnlineUserCountResponse {
|
||||
int64 count = 1;
|
||||
}
|
||||
|
||||
message RemoveIdentMessage {
|
||||
string uid = 1;
|
||||
string server_id = 2;
|
||||
}
|
||||
|
||||
message SetIdentMessage {
|
||||
string uid = 1;
|
||||
string server_id = 2;
|
||||
string ident = 3;
|
||||
}
|
||||
|
||||
message CharacterIdentMessage {
|
||||
string server_id = 1;
|
||||
string ident = 2;
|
||||
}
|
||||
|
||||
message UidMessage {
|
||||
string uid = 1;
|
||||
}
|
||||
|
||||
message UploadFileRequest {
|
||||
string hash = 1;
|
||||
string uploader = 2;
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using MareSynchronosShared.Metrics;
|
||||
|
||||
namespace MareSynchronosShared.Services;
|
||||
|
||||
public abstract class BaseClientIdentificationService : IClientIdentificationService
|
||||
{
|
||||
private readonly MareMetrics metrics;
|
||||
protected ConcurrentDictionary<string, string> OnlineClients = new();
|
||||
protected BaseClientIdentificationService(MareMetrics metrics)
|
||||
{
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public virtual Task<int> GetOnlineUsers()
|
||||
{
|
||||
return Task.FromResult(OnlineClients.Count);
|
||||
}
|
||||
|
||||
public Task<string?> GetUidForCharacterIdent(string characterIdent)
|
||||
{
|
||||
var result = OnlineClients.SingleOrDefault(u =>
|
||||
string.Compare(u.Value, characterIdent, StringComparison.InvariantCultureIgnoreCase) == 0);
|
||||
return Task.FromResult(result.Equals(new KeyValuePair<string, string>()) ? null : result.Key);
|
||||
}
|
||||
|
||||
public virtual Task<string?> GetCharacterIdentForUid(string uid)
|
||||
{
|
||||
if (!OnlineClients.TryGetValue(uid, out var result))
|
||||
{
|
||||
return Task.FromResult((string?)null);
|
||||
}
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public virtual Task MarkUserOnline(string uid, string charaIdent)
|
||||
{
|
||||
OnlineClients[uid] = charaIdent;
|
||||
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task MarkUserOffline(string uid)
|
||||
{
|
||||
if (OnlineClients.TryRemove(uid, out _))
|
||||
{
|
||||
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, 0);
|
||||
OnlineClients = new();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using System.Text;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace MareSynchronosShared.Services;
|
||||
|
||||
public class DistributedClientIdentificationService : BaseClientIdentificationService
|
||||
{
|
||||
private readonly IDistributedCache distributedCache;
|
||||
private readonly ILogger<DistributedClientIdentificationService> logger;
|
||||
private readonly IConfiguration configuration;
|
||||
private const string RedisPrefix = "uidcache:";
|
||||
|
||||
public DistributedClientIdentificationService(MareMetrics metrics, IDistributedCache distributedCache, IConfiguration configuration, ILogger<DistributedClientIdentificationService> logger) : base(metrics)
|
||||
{
|
||||
this.distributedCache = distributedCache;
|
||||
this.logger = logger;
|
||||
this.configuration = configuration.GetSection("MareSynchronos");
|
||||
}
|
||||
|
||||
public override async Task<int> GetOnlineUsers()
|
||||
{
|
||||
try
|
||||
{
|
||||
var redis = configuration.GetValue<string>("RedisConnectionString");
|
||||
var conn = await ConnectionMultiplexer.ConnectAsync(redis).ConfigureAwait(false);
|
||||
var endpoint = conn.GetEndPoints().First();
|
||||
return await conn.GetServer(endpoint).KeysAsync(pattern: "*" + RedisPrefix + "*").CountAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error during GetOnlineUsers");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task<string?> GetCharacterIdentForUid(string uid)
|
||||
{
|
||||
var localIdent = await base.GetCharacterIdentForUid(uid).ConfigureAwait(false);
|
||||
if (localIdent != null) return localIdent;
|
||||
var cachedIdent = await distributedCache.GetStringAsync(RedisPrefix + uid).ConfigureAwait(false);
|
||||
return cachedIdent ?? null;
|
||||
}
|
||||
|
||||
public override async Task MarkUserOffline(string uid)
|
||||
{
|
||||
await base.MarkUserOffline(uid).ConfigureAwait(false);
|
||||
await distributedCache.RemoveAsync(RedisPrefix + uid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override async Task MarkUserOnline(string uid, string charaIdent)
|
||||
{
|
||||
await base.MarkUserOnline(uid, charaIdent).ConfigureAwait(false);
|
||||
await distributedCache.SetAsync(RedisPrefix + uid, Encoding.UTF8.GetBytes(charaIdent), new DistributedCacheEntryOptions()
|
||||
{
|
||||
AbsoluteExpiration = DateTime.Now.AddDays(7)
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var uid in OnlineClients)
|
||||
{
|
||||
distributedCache.Remove(RedisPrefix + uid.Key);
|
||||
}
|
||||
return base.StopAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace MareSynchronosShared.Services;
|
||||
|
||||
public interface IClientIdentificationService : IHostedService
|
||||
{
|
||||
Task<int> GetOnlineUsers();
|
||||
Task<string?> GetUidForCharacterIdent(string characterIdent);
|
||||
Task<string?> GetCharacterIdentForUid(string uid);
|
||||
Task MarkUserOnline(string uid, string charaIdent);
|
||||
Task MarkUserOffline(string uid);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using MareSynchronosShared.Metrics;
|
||||
|
||||
namespace MareSynchronosShared.Services;
|
||||
|
||||
public class LocalClientIdentificationService : BaseClientIdentificationService
|
||||
{
|
||||
public LocalClientIdentificationService(MareMetrics metrics) : base(metrics)
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user