Renamed 'Sessions' to 'Connections'

This commit is contained in:
flash 2023-02-16 22:25:41 +01:00
parent c8a589c1c1
commit 06af94e94f
12 changed files with 134 additions and 135 deletions

View File

@ -7,14 +7,14 @@ namespace SharpChat {
public string[] Args { get; }
public ChatContext Chat { get; }
public ChatUser User { get; }
public ChatUserSession Session { get; }
public ChatConnection Connection { get; }
public ChatChannel Channel { get; }
public ChatCommandContext(
string text,
ChatContext chat,
ChatUser user,
ChatUserSession session,
ChatConnection connection,
ChatChannel channel
) {
if(text == null)
@ -22,7 +22,7 @@ namespace SharpChat {
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
User = user ?? throw new ArgumentNullException(nameof(user));
Session = session ?? throw new ArgumentNullException(nameof(session));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
string[] parts = text[1..].Split(' ');
@ -35,14 +35,14 @@ namespace SharpChat {
string[] args,
ChatContext chat,
ChatUser user,
ChatUserSession session,
ChatConnection connection,
ChatChannel channel
) {
Name = name ?? throw new ArgumentNullException(nameof(name));
Args = args ?? throw new ArgumentNullException(nameof(args));
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
User = user ?? throw new ArgumentNullException(nameof(user));
Session = session ?? throw new ArgumentNullException(nameof(session));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
}

View File

@ -4,8 +4,8 @@ using System.Collections.Generic;
using System.Net;
namespace SharpChat {
public class ChatUserSession : IDisposable {
public const int ID_LENGTH = 32;
public class ChatConnection : IDisposable {
public const int ID_LENGTH = 20;
#if DEBUG
public static TimeSpan SessionTimeOut { get; } = TimeSpan.FromMinutes(1);
@ -13,7 +13,7 @@ namespace SharpChat {
public static TimeSpan SessionTimeOut { get; } = TimeSpan.FromMinutes(5);
#endif
public IWebSocketConnection Connection { get; }
public IWebSocketConnection Socket { get; }
public string Id { get; private set; }
public bool IsDisposed { get; private set; }
@ -27,11 +27,11 @@ namespace SharpChat {
public IPAddress RemoteAddress {
get {
if(_RemoteAddress == null) {
if((Connection.ConnectionInfo.ClientIpAddress == "127.0.0.1" || Connection.ConnectionInfo.ClientIpAddress == "::1")
&& Connection.ConnectionInfo.Headers.ContainsKey("X-Real-IP"))
_RemoteAddress = IPAddress.Parse(Connection.ConnectionInfo.Headers["X-Real-IP"]);
if((Socket.ConnectionInfo.ClientIpAddress == "127.0.0.1" || Socket.ConnectionInfo.ClientIpAddress == "::1")
&& Socket.ConnectionInfo.Headers.ContainsKey("X-Real-IP"))
_RemoteAddress = IPAddress.Parse(Socket.ConnectionInfo.Headers["X-Real-IP"]);
else
_RemoteAddress = IPAddress.Parse(Connection.ConnectionInfo.ClientIpAddress);
_RemoteAddress = IPAddress.Parse(Socket.ConnectionInfo.ClientIpAddress);
}
return _RemoteAddress;
@ -39,13 +39,13 @@ namespace SharpChat {
}
}
public ChatUserSession(IWebSocketConnection ws) {
Connection = ws;
public ChatConnection(IWebSocketConnection sock) {
Socket = sock;
Id = RNG.SecureRandomString(ID_LENGTH);
}
public void Send(IServerPacket packet) {
if(!Connection.IsAvailable)
if(!Socket.IsAvailable)
return;
IEnumerable<string> data = packet.Pack();
@ -53,7 +53,7 @@ namespace SharpChat {
if(data != null)
foreach(string line in data)
if(!string.IsNullOrWhiteSpace(line))
Connection.Send(line);
Socket.Send(line);
}
public void BumpPing() {
@ -67,7 +67,7 @@ namespace SharpChat {
CloseCode = 1012;
}
~ChatUserSession() {
~ChatConnection() {
DoDispose();
}
@ -81,7 +81,7 @@ namespace SharpChat {
return;
IsDisposed = true;
Connection.Close(CloseCode);
Socket.Close(CloseCode);
}
public override int GetHashCode() {

View File

@ -11,8 +11,8 @@ namespace SharpChat {
public HashSet<ChatChannel> Channels { get; } = new();
public readonly object ChannelsAccess = new();
public HashSet<ChatUserSession> Sessions { get; } = new();
public readonly object SessionsAccess = new();
public HashSet<ChatConnection> Connections { get; } = new();
public readonly object ConnectionsAccess = new();
public HashSet<ChatUser> Users { get; } = new();
public readonly object UsersAccess = new();
@ -27,21 +27,21 @@ namespace SharpChat {
public void Update() {
lock(UsersAccess)
foreach(ChatUser user in Users) {
IEnumerable<ChatUserSession> timedOut = user.GetDeadSessions();
IEnumerable<ChatConnection> timedOut = user.GetDeadConnections();
foreach(ChatUserSession sess in timedOut) {
user.RemoveSession(sess);
sess.Dispose();
Logger.Write($"Nuked session {sess.Id} from {user.Username} (timeout)");
foreach(ChatConnection conn in timedOut) {
user.RemoveConnection(conn);
conn.Dispose();
Logger.Write($"Nuked session {conn.Id} from {user.Username} (timeout)");
}
if(!user.HasSessions)
if(!user.HasConnections)
UserLeave(null, user, UserDisconnectReason.TimeOut);
}
}
public ChatUserSession GetSession(IWebSocketConnection conn) {
return Sessions.FirstOrDefault(s => s.Connection == conn);
public ChatConnection GetConnection(IWebSocketConnection sock) {
return Connections.FirstOrDefault(s => s.Socket == sock);
}
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
@ -54,21 +54,21 @@ namespace SharpChat {
UserLeave(user.Channel, user, reason);
}
public void HandleJoin(ChatUser user, ChatChannel chan, ChatUserSession sess, int maxMsgLength) {
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) {
lock(EventsAccess) {
if(!chan.HasUser(user)) {
chan.Send(new UserConnectPacket(DateTimeOffset.Now, user));
Events.AddEvent(new UserConnectEvent(DateTimeOffset.Now, user, chan));
}
sess.Send(new AuthSuccessPacket(user, chan, sess, maxMsgLength));
sess.Send(new ContextUsersPacket(chan.GetUsers(new[] { user })));
conn.Send(new AuthSuccessPacket(user, chan, conn, maxMsgLength));
conn.Send(new ContextUsersPacket(chan.GetUsers(new[] { user })));
foreach(IChatEvent msg in Events.GetTargetEventLog(chan.Name))
sess.Send(new ContextMessagePacket(msg));
conn.Send(new ContextMessagePacket(msg));
lock(ChannelsAccess)
sess.Send(new ContextChannelsPacket(Channels.Where(c => c.Rank <= user.Rank)));
conn.Send(new ContextChannelsPacket(Channels.Where(c => c.Rank <= user.Rank)));
if(!chan.HasUser(user))
chan.UserJoin(user);

View File

@ -4,16 +4,16 @@ namespace SharpChat {
public class ChatPacketHandlerContext {
public string Text { get; }
public ChatContext Chat { get; }
public ChatUserSession Session { get; }
public ChatConnection Connection { get; }
public ChatPacketHandlerContext(
string text,
ChatContext chat,
ChatUserSession session
ChatConnection connection
) {
Text = text ?? throw new ArgumentNullException(nameof(text));
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
Session = session ?? throw new ArgumentNullException(nameof(session));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
}
public bool CheckPacketId(string packetId) {

View File

@ -83,7 +83,7 @@ namespace SharpChat {
public class ChatUser : BasicUser, IPacketTarget {
public DateTimeOffset SilencedUntil { get; set; }
private readonly List<ChatUserSession> Sessions = new();
private readonly List<ChatConnection> Connections = new();
private readonly List<ChatChannel> Channels = new();
public readonly ChatRateLimiter RateLimiter = new();
@ -98,14 +98,13 @@ namespace SharpChat {
public bool IsSilenced
=> DateTimeOffset.UtcNow - SilencedUntil <= TimeSpan.Zero;
public bool HasSessions => Sessions.Where(c => !c.HasTimedOut && !c.IsDisposed).Any();
public bool HasConnections => Connections.Where(c => !c.HasTimedOut && !c.IsDisposed).Any();
public int SessionCount => Sessions.Where(c => !c.HasTimedOut && !c.IsDisposed).Count();
public int ConnectionCount => Connections.Where(c => !c.HasTimedOut && !c.IsDisposed).Count();
public IEnumerable<IPAddress> RemoteAddresses => Sessions.Select(c => c.RemoteAddress);
public IEnumerable<IPAddress> RemoteAddresses => Connections.Select(c => c.RemoteAddress);
public ChatUser() {
}
public ChatUser() {}
public ChatUser(MisuzuAuthInfo auth) {
UserId = auth.UserId;
@ -127,14 +126,14 @@ namespace SharpChat {
}
public void Send(IServerPacket packet) {
foreach(ChatUserSession conn in Sessions)
foreach(ChatConnection conn in Connections)
conn.Send(packet);
}
public void Close() {
foreach(ChatUserSession conn in Sessions)
foreach(ChatConnection conn in Connections)
conn.Dispose();
Sessions.Clear();
Connections.Clear();
}
public void ForceChannel(ChatChannel chan = null) {
@ -166,25 +165,25 @@ namespace SharpChat {
return Channels.ToList();
}
public void AddSession(ChatUserSession sess) {
if(sess == null)
public void AddConnection(ChatConnection conn) {
if(conn == null)
return;
sess.User = this;
conn.User = this;
Sessions.Add(sess);
Connections.Add(conn);
}
public void RemoveSession(ChatUserSession sess) {
if(sess == null)
public void RemoveConnection(ChatConnection conn) {
if(conn == null)
return;
if(!sess.IsDisposed) // this could be possible
sess.User = null;
if(!conn.IsDisposed) // this could be possible
conn.User = null;
Sessions.Remove(sess);
Connections.Remove(conn);
}
public IEnumerable<ChatUserSession> GetDeadSessions() {
return Sessions.Where(x => x.HasTimedOut || x.IsDisposed).ToList();
public IEnumerable<ChatConnection> GetDeadConnections() {
return Connections.Where(x => x.HasTimedOut || x.IsDisposed).ToList();
}
public bool NameEquals(string name) {

View File

@ -72,7 +72,7 @@ namespace SharpChat.Commands {
await Misuzu.CreateBanAsync(
banUser.UserId.ToString(), banUser.RemoteAddresses.First().ToString(),
ctx.User.UserId.ToString(), ctx.Session.RemoteAddress.ToString(),
ctx.User.UserId.ToString(), ctx.Connection.RemoteAddress.ToString(),
duration, banReason
);

View File

@ -27,9 +27,9 @@ namespace SharpChat.Commands {
return;
if(ctx.NameEquals("restart"))
lock(ctx.Chat.SessionsAccess)
foreach(ChatUserSession sess in ctx.Chat.Sessions)
sess.PrepareForRestart();
lock(ctx.Chat.ConnectionsAccess)
foreach(ChatConnection conn in ctx.Chat.Connections)
conn.PrepareForRestart();
ctx.Chat.Update();
WaitHandle?.Set();

View File

@ -6,18 +6,18 @@ namespace SharpChat.Packet {
public class AuthSuccessPacket : ServerPacket {
public ChatUser User { get; private set; }
public ChatChannel Channel { get; private set; }
public ChatUserSession Session { get; private set; }
public ChatConnection Connection { get; private set; }
public int MaxMessageLength { get; private set; }
public AuthSuccessPacket(
ChatUser user,
ChatChannel channel,
ChatUserSession sess,
ChatConnection connection,
int maxMsgLength
) {
User = user ?? throw new ArgumentNullException(nameof(user));
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
Session = sess ?? throw new ArgumentNullException(nameof(channel));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
MaxMessageLength = maxMsgLength;
}

View File

@ -35,15 +35,15 @@ namespace SharpChat.PacketHandlers {
string authMethod = args.ElementAtOrDefault(1);
if(string.IsNullOrWhiteSpace(authMethod)) {
ctx.Session.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Session.Dispose();
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Connection.Dispose();
return;
}
string authToken = args.ElementAtOrDefault(2);
if(string.IsNullOrWhiteSpace(authToken)) {
ctx.Session.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Session.Dispose();
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Connection.Dispose();
return;
}
@ -55,14 +55,14 @@ namespace SharpChat.PacketHandlers {
Task.Run(async () => {
MisuzuAuthInfo fai;
string ipAddr = ctx.Session.RemoteAddress.ToString();
string ipAddr = ctx.Connection.RemoteAddress.ToString();
try {
fai = await Misuzu.AuthVerifyAsync(authMethod, authToken, ipAddr);
} catch(Exception ex) {
Logger.Write($"<{ctx.Session.Id}> Failed to authenticate: {ex}");
ctx.Session.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Session.Dispose();
Logger.Write($"<{ctx.Connection.Id}> Failed to authenticate: {ex}");
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Connection.Dispose();
#if DEBUG
throw;
#else
@ -71,9 +71,9 @@ namespace SharpChat.PacketHandlers {
}
if(!fai.Success) {
Logger.Debug($"<{ctx.Session.Id}> Auth fail: {fai.Reason}");
ctx.Session.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Session.Dispose();
Logger.Debug($"<{ctx.Connection.Id}> Auth fail: {fai.Reason}");
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Connection.Dispose();
return;
}
@ -81,9 +81,9 @@ namespace SharpChat.PacketHandlers {
try {
fbi = await Misuzu.CheckBanAsync(fai.UserId.ToString(), ipAddr);
} catch(Exception ex) {
Logger.Write($"<{ctx.Session.Id}> Failed auth ban check: {ex}");
ctx.Session.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Session.Dispose();
Logger.Write($"<{ctx.Connection.Id}> Failed auth ban check: {ex}");
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.AuthInvalid));
ctx.Connection.Dispose();
#if DEBUG
throw;
#else
@ -92,9 +92,9 @@ namespace SharpChat.PacketHandlers {
}
if(fbi.IsBanned && !fbi.HasExpired) {
Logger.Write($"<{ctx.Session.Id}> User is banned.");
ctx.Session.Send(new AuthFailPacket(AuthFailReason.Banned, fbi));
ctx.Session.Dispose();
Logger.Write($"<{ctx.Connection.Id}> User is banned.");
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.Banned, fbi));
ctx.Connection.Dispose();
return;
}
@ -109,28 +109,28 @@ namespace SharpChat.PacketHandlers {
}
// Enforce a maximum amount of connections per user
if(aUser.SessionCount >= MaxConnections) {
ctx.Session.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
ctx.Session.Dispose();
if(aUser.ConnectionCount >= MaxConnections) {
ctx.Connection.Send(new AuthFailPacket(AuthFailReason.MaxSessions));
ctx.Connection.Dispose();
return;
}
// Bumping the ping to prevent upgrading
ctx.Session.BumpPing();
ctx.Connection.BumpPing();
aUser.AddSession(ctx.Session);
aUser.AddConnection(ctx.Connection);
ctx.Session.Send(new LegacyCommandResponse(LCR.WELCOME, false, $"Welcome to Flashii Chat, {aUser.Username}!"));
ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, $"Welcome to Flashii Chat, {aUser.Username}!"));
if(File.Exists("welcome.txt")) {
IEnumerable<string> lines = File.ReadAllLines("welcome.txt").Where(x => !string.IsNullOrWhiteSpace(x));
string line = lines.ElementAtOrDefault(RNG.Next(lines.Count()));
if(!string.IsNullOrWhiteSpace(line))
ctx.Session.Send(new LegacyCommandResponse(LCR.WELCOME, false, line));
ctx.Connection.Send(new LegacyCommandResponse(LCR.WELCOME, false, line));
}
ctx.Chat.HandleJoin(aUser, DefaultChannel, ctx.Session, MaxMessageLength);
ctx.Chat.HandleJoin(aUser, DefaultChannel, ctx.Connection, MaxMessageLength);
}
}).Wait();
}

View File

@ -26,15 +26,15 @@ namespace SharpChat.PacketHandlers {
if(!int.TryParse(parts.FirstOrDefault(), out int pTime))
return;
ctx.Session.BumpPing();
ctx.Session.Send(new PongPacket(ctx.Session.LastPing));
ctx.Connection.BumpPing();
ctx.Connection.Send(new PongPacket(ctx.Connection.LastPing));
lock(BumpAccess) {
if(LastBump < DateTimeOffset.UtcNow - BumpInterval) {
(string, string)[] bumpList;
lock(ctx.Chat.UsersAccess)
bumpList = ctx.Chat.Users
.Where(u => u.HasSessions && u.Status == ChatUserStatus.Online)
.Where(u => u.HasConnections && u.Status == ChatUserStatus.Online)
.Select(u => (u.UserId.ToString(), u.RemoteAddresses.FirstOrDefault()?.ToString() ?? string.Empty))
.ToArray();

View File

@ -31,7 +31,7 @@ namespace SharpChat.PacketHandlers {
public void Handle(ChatPacketHandlerContext ctx) {
string[] args = ctx.SplitText(3);
ChatUser user = ctx.Session.User;
ChatUser user = ctx.Connection.User;
// No longer concats everything after index 1 with \t, no previous implementation did that either
string messageText = args.ElementAtOrDefault(2);
@ -61,13 +61,13 @@ namespace SharpChat.PacketHandlers {
messageText = messageText.Trim();
#if DEBUG
Logger.Write($"<{ctx.Session.Id} {user.Username}> {messageText}");
Logger.Write($"<{ctx.Connection.Id} {user.Username}> {messageText}");
#endif
IChatMessage message = null;
if(messageText.StartsWith("/")) {
ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Session, channel);
ChatCommandContext context = new(messageText, ctx.Chat, user, ctx.Connection, channel);
IChatCommand command = null;

View File

@ -129,32 +129,32 @@ namespace SharpChat {
Logger.Write("Listening...");
}
private void OnOpen(IWebSocketConnection conn) {
Logger.Write($"Connection opened from {conn.ConnectionInfo.ClientIpAddress}:{conn.ConnectionInfo.ClientPort}");
private void OnOpen(IWebSocketConnection sock) {
Logger.Write($"Connection opened from {sock.ConnectionInfo.ClientIpAddress}:{sock.ConnectionInfo.ClientPort}");
lock(Context.SessionsAccess) {
if(!Context.Sessions.Any(x => x.Connection == conn))
Context.Sessions.Add(new ChatUserSession(conn));
lock(Context.ConnectionsAccess) {
if(!Context.Connections.Any(x => x.Socket == sock))
Context.Connections.Add(new ChatConnection(sock));
}
Context.Update();
}
private void OnClose(IWebSocketConnection conn) {
Logger.Write($"Connection closed from {conn.ConnectionInfo.ClientIpAddress}:{conn.ConnectionInfo.ClientPort}");
private void OnClose(IWebSocketConnection sock) {
Logger.Write($"Connection closed from {sock.ConnectionInfo.ClientIpAddress}:{sock.ConnectionInfo.ClientPort}");
ChatUserSession sess;
lock(Context.SessionsAccess)
sess = Context.GetSession(conn);
ChatConnection conn;
lock(Context.ConnectionsAccess)
conn = Context.GetConnection(sock);
// Remove connection from user
if(sess?.User != null) {
if(conn?.User != null) {
// RemoveConnection sets conn.User to null so we must grab a local copy.
ChatUser user = sess.User;
ChatUser user = conn.User;
user.RemoveSession(sess);
user.RemoveConnection(conn);
if(!user.HasSessions)
if(!user.HasConnections)
Context.UserLeave(null, user);
}
@ -162,59 +162,59 @@ namespace SharpChat {
Context.Update();
// Remove connection from server
lock(Context.SessionsAccess)
Context.Sessions.Remove(sess);
lock(Context.ConnectionsAccess)
Context.Connections.Remove(conn);
sess?.Dispose();
conn?.Dispose();
}
private void OnError(IWebSocketConnection conn, Exception ex) {
string sessId;
lock(Context.SessionsAccess) {
ChatUserSession sess = Context.GetSession(conn);
sessId = sess?.Id ?? new string('0', ChatUserSession.ID_LENGTH);
private void OnError(IWebSocketConnection sock, Exception ex) {
string connId;
lock(Context.ConnectionsAccess) {
ChatConnection conn = Context.GetConnection(sock);
connId = conn?.Id ?? new string('0', ChatConnection.ID_LENGTH);
}
Logger.Write($"[{sessId} {conn.ConnectionInfo.ClientIpAddress}] {ex}");
Logger.Write($"[{connId} {sock.ConnectionInfo.ClientIpAddress}] {ex}");
Context.Update();
}
private void OnMessage(IWebSocketConnection conn, string msg) {
private void OnMessage(IWebSocketConnection sock, string msg) {
Context.Update();
ChatUserSession sess;
lock(Context.SessionsAccess)
sess = Context.GetSession(conn);
ChatConnection conn;
lock(Context.ConnectionsAccess)
conn = Context.GetConnection(sock);
if(sess == null) {
conn.Close();
if(conn == null) {
sock.Close();
return;
}
// this doesn't affect non-authed connections?????
if(sess.User is not null && sess.User.HasFloodProtection) {
sess.User.RateLimiter.AddTimePoint();
if(conn.User is not null && conn.User.HasFloodProtection) {
conn.User.RateLimiter.AddTimePoint();
if(sess.User.RateLimiter.State == ChatRateLimitState.Kick) {
if(conn.User.RateLimiter.State == ChatRateLimitState.Kick) {
Task.Run(async () => {
TimeSpan duration = TimeSpan.FromSeconds(FloodKickLength);
await Misuzu.CreateBanAsync(
sess.User.UserId.ToString(), sess.RemoteAddress.ToString(),
conn.User.UserId.ToString(), conn.RemoteAddress.ToString(),
string.Empty, "::1",
duration,
"Kicked from chat for flood protection."
);
Context.BanUser(sess.User, duration, UserDisconnectReason.Flood);
Context.BanUser(conn.User, duration, UserDisconnectReason.Flood);
}).Wait();
return;
} else if(sess.User.RateLimiter.State == ChatRateLimitState.Warning)
sess.User.Send(new FloodWarningPacket());
} else if(conn.User.RateLimiter.State == ChatRateLimitState.Warning)
conn.User.Send(new FloodWarningPacket());
}
ChatPacketHandlerContext context = new(msg, Context, sess);
IChatPacketHandler handler = sess.User is null
ChatPacketHandlerContext context = new(msg, Context, conn);
IChatPacketHandler handler = conn.User is null
? GuestHandlers.FirstOrDefault(h => h.IsMatch(context))
: AuthedHandlers.FirstOrDefault(h => h.IsMatch(context));
@ -235,9 +235,9 @@ namespace SharpChat {
return;
IsDisposed = true;
lock(Context.SessionsAccess)
foreach(ChatUserSession sess in Context.Sessions)
sess.Dispose();
lock(Context.ConnectionsAccess)
foreach(ChatConnection conn in Context.Connections)
conn.Dispose();
Server?.Dispose();
HttpClient?.Dispose();