From 8902f123587030da8467bfac884a2f73852e16aa Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 1 Feb 2025 21:25:28 +0100 Subject: [PATCH 1/7] Remove code that handles user remembering --- .../Contracts/IWebSocketClient.cs | 5 - .../Contracts/IWebSocketServer.cs | 6 - .../PassiveUserExpiredEventHandler.cs | 13 - .../Flows/ClientHandlingFlow.cs | 118 +------- .../PassiveUserExpiredEventArgs.cs | 10 - .../Models/SimpleWebSocketServerOptions.cs | 31 +- Jung.SimpleWebSocket/Models/WebContext.cs | 28 +- Jung.SimpleWebSocket/SimpleWebSocketClient.cs | 13 +- Jung.SimpleWebSocket/SimpleWebSocketServer.cs | 86 +----- .../Utility/ExpiringDictionary.cs | 281 ------------------ .../Wrappers/WebSocketUpgradeHandler.cs | 3 +- .../ClientHandlingFlowTest.cs | 53 +--- .../SimpleWebSocketTest.cs | 60 +--- .../WebSocketUpgradeHandlerTests.cs | 4 +- 14 files changed, 32 insertions(+), 679 deletions(-) delete mode 100644 Jung.SimpleWebSocket/Delegates/PassiveUserExpiredEventHandler.cs delete mode 100644 Jung.SimpleWebSocket/Models/EventArguments/PassiveUserExpiredEventArgs.cs delete mode 100644 Jung.SimpleWebSocket/Utility/ExpiringDictionary.cs diff --git a/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs b/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs index 5bad1b4..1a73240 100644 --- a/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs +++ b/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs @@ -30,11 +30,6 @@ public interface IWebSocketClient : IDisposable /// bool IsConnected { get; } - /// - /// The user id of the client. If not set, the server did not sent a user id at websocket upgrade. - /// - string? UserId { get; } - /// /// Event that is raised when a message is received from a client. /// diff --git a/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs b/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs index 5add6a8..f4973a2 100644 --- a/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs +++ b/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs @@ -4,7 +4,6 @@ using Jung.SimpleWebSocket.Delegates; using Jung.SimpleWebSocket.Models; using Jung.SimpleWebSocket.Models.EventArguments; -using Jung.SimpleWebSocket.Utility; using System.Net; namespace Jung.SimpleWebSocket.Contracts; @@ -59,11 +58,6 @@ public interface IWebSocketServer : IDisposable /// event EventHandler? BinaryMessageReceived; - /// - /// Occurs when an passive user expired. - /// - event EventHandler? PassiveUserExpiredEvent; - /// /// Async Event that is raised when a client upgrade request is received. /// diff --git a/Jung.SimpleWebSocket/Delegates/PassiveUserExpiredEventHandler.cs b/Jung.SimpleWebSocket/Delegates/PassiveUserExpiredEventHandler.cs deleted file mode 100644 index de46577..0000000 --- a/Jung.SimpleWebSocket/Delegates/PassiveUserExpiredEventHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -// This file is part of the Jung SimpleWebSocket project. -// The project is licensed under the MIT license. - -using Jung.SimpleWebSocket.Models.EventArguments; - -namespace Jung.SimpleWebSocket.Delegates; - -/// -/// The event handler for the passive user expired event. -/// -/// The sender of the event. -/// The arguments of the event. -public delegate void PassiveUserExpiredEventHandler(object sender, PassiveUserExpiredArgs e); \ No newline at end of file diff --git a/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs b/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs index ad5e3ab..e41e93e 100644 --- a/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs +++ b/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs @@ -2,14 +2,11 @@ // The project is licensed under the MIT license. using Jung.SimpleWebSocket.Delegates; -using Jung.SimpleWebSocket.Exceptions; using Jung.SimpleWebSocket.Models; using Jung.SimpleWebSocket.Models.EventArguments; using Jung.SimpleWebSocket.Utility; using Microsoft.Extensions.Logging; -using System; using System.Collections.Concurrent; -using System.Net; namespace Jung.SimpleWebSocket.Flows { @@ -44,31 +41,11 @@ internal class ClientHandlingFlow(SimpleWebSocketServer server, WebSocketServerC /// private WebContext _responseContext = null!; - /// - /// Gets a value indicating whether the client was accepted. - /// - private bool _clientAccepted; - - /// - /// Gets a value indicating whether the client was a passive client. - /// - private bool _clientWasPassiveClient; - - /// - /// Gets the options of the server. - /// - private readonly SimpleWebSocketServerOptions _options = server.Options; - /// /// Gets the active clients of the server. /// private readonly ConcurrentDictionary _activeClients = server.ActiveClients; - /// - /// Gets the passive clients of the server. - /// - private readonly IDictionary _passiveClients = server.PassiveClients; - /// /// Gets the logger of the server. /// @@ -94,88 +71,16 @@ internal async Task LoadRequestContext() Request = await _upgradeHandler.AwaitContextAsync(_cancellationToken); } - /// - /// Handles the client identification. - /// - internal void HandleClientIdentification() - { - // Check if disconnected clients are remembered and can be reactivated - if (_options.RememberDisconnectedClients) - { - // Check if the request contains a user id - if (Request.ContainsUserId) - { - _logger?.LogDebug("User id found in request: {userId}", Request.UserId); - - lock (_clientLock) - { - ThrowForUserAlreadyConnected(); - - // Check if the client is an existing passive client - var clientExists = _passiveClients.ContainsKey(Request.UserId); - if (clientExists) - { - _logger?.LogDebug("Passive Client found for user id {userId} - reactivating user.", Request.UserId); - - // Use the existing client - // Update the client with the new connection - // Remove the client from the passive clients - var passiveClient = _passiveClients[Request.UserId]; - passiveClient.UpdateClient(Client.ClientConnection!); - Client = passiveClient; - var clientRemoved = _passiveClients.Remove(Request.UserId); - - // Set the flag that the client was a passive client - // This should only be set if the client was removed in this specific flow - // Otherwise its possible that the client is handled twice - _clientWasPassiveClient = clientRemoved; - } - else - { - // Client is not a passive client - // Update the clients user id - Client.UpdateId(Request.UserId); - } - } - } - } - } - - /// - /// Throws an exception if the user is already connected. - /// - /// - private void ThrowForUserAlreadyConnected() - { - // No passive client found, checking for active clients with the same id - if (_activeClients.ContainsKey(Request.UserId)) - { - _logger?.LogDebug("Active Client found for user id {userId} - rejecting connection.", Request.UserId); - // Reject the connection - - var responseContext = new WebContext - { - StatusCode = HttpStatusCode.Conflict, - BodyContent = "User id already in use" - }; - throw new UserNotHandledException(responseContext); - } - } - /// /// Accepts the web socket connection. /// internal async Task AcceptWebSocketAsync() { // The client is accepted - await _upgradeHandler.AcceptWebSocketAsync(Request, _responseContext, Client.Id, null, _cancellationToken); + await _upgradeHandler.AcceptWebSocketAsync(Request, _responseContext, null, _cancellationToken); // Use the web socket for the client Client.UseWebSocket(_upgradeHandler.CreateWebSocket(isServer: true)); - - // Set the flag that the client was accepted - // This is used to determine if the client should be added to the passive clients after disconnect - _clientAccepted = true; } /// @@ -192,23 +97,12 @@ internal async Task RejectWebSocketAsync(WebContext responseContext) /// internal void HandleDisconnectedClient() { - if (_clientWasPassiveClient || _clientAccepted) + lock (_clientLock) { - lock (_clientLock) - { - _activeClients.TryRemove(Client.Id, out _); - Client.Dispose(); - - if (_options.RememberDisconnectedClients) - { - _logger?.LogDebug("Client {clientId} is now a passive user.", Client.Id); - _passiveClients.Add(Client.Id, Client); - } - else - { - _logger?.LogDebug("Client {clientId} is removed.", Client.Id); - } - } + _activeClients.TryRemove(Client.Id, out _); + Client.Dispose(); + + _logger?.LogDebug("Client {clientId} is removed.", Client.Id); } } diff --git a/Jung.SimpleWebSocket/Models/EventArguments/PassiveUserExpiredEventArgs.cs b/Jung.SimpleWebSocket/Models/EventArguments/PassiveUserExpiredEventArgs.cs deleted file mode 100644 index 19cd99a..0000000 --- a/Jung.SimpleWebSocket/Models/EventArguments/PassiveUserExpiredEventArgs.cs +++ /dev/null @@ -1,10 +0,0 @@ -// This file is part of the Jung SimpleWebSocket project. -// The project is licensed under the MIT license. - -namespace Jung.SimpleWebSocket.Models.EventArguments; - -/// -/// Represents the arguments of the event when a passive user expired. -/// -/// The identifier of the user that expired. -public record PassiveUserExpiredArgs(string ClientId); \ No newline at end of file diff --git a/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs b/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs index 7e5a486..ea58fde 100644 --- a/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs +++ b/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs @@ -1,4 +1,7 @@ -using System.Net; +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +using System.Net; namespace Jung.SimpleWebSocket.Models { @@ -16,29 +19,5 @@ public class SimpleWebSocketServerOptions /// Gets or sets the port of the server. /// public int Port { get; set; } - - /// - /// Switch for remembering disconnected clients. - /// - /// - /// If true the server will put disconnected clients into a passive client list. - /// This clients can reidentify themselves with their user id. - /// - public bool RememberDisconnectedClients { get; set; } = false; - - /// - /// Switch for removing passive clients after the end of the . - /// - public bool RemovePassiveClientsAfterClientExpirationTime { get; set; } = false; - - /// - /// Switch for sending the user id to the client. - /// - public bool SendUserIdToClient { get; set; } = false; - - /// - /// The time after which a passive client is removed from the passive client list. - /// - public TimeSpan PassiveClientLifetime { get; set; } = TimeSpan.FromMinutes(1); } -} \ No newline at end of file +} diff --git a/Jung.SimpleWebSocket/Models/WebContext.cs b/Jung.SimpleWebSocket/Models/WebContext.cs index 3b7626c..589ab61 100644 --- a/Jung.SimpleWebSocket/Models/WebContext.cs +++ b/Jung.SimpleWebSocket/Models/WebContext.cs @@ -238,9 +238,8 @@ private NameValueCollection ParseHeaders() /// The host name of the web request. /// The port of the web request. /// The request path of the web request. - /// The user id of the web request. /// The created web request context. - internal static WebContext CreateRequest(string hostName, int port, string requestPath, string? userId = null) + internal static WebContext CreateRequest(string hostName, int port, string requestPath) { var context = new WebContext() { @@ -249,12 +248,7 @@ internal static WebContext CreateRequest(string hostName, int port, string reque RequestPath = requestPath, }; - if (userId != null) - { - context.Headers.Add("x-user-id", userId); - } - - return context; + return context; } /// @@ -397,24 +391,6 @@ public static string GetStatusDescription(HttpStatusCode statusCode) return string.Join(" ", _splitByUppercaseRegex.Split(enumName)); } - - /// - /// Gets the user id of the web request. - /// - public string UserId - { - get - { - var userId = Headers["x-user-id"] ?? throw new WebSocketUpgradeException("UserId header is missing"); - return userId; - } - } - - /// - /// Gets a value indicating whether the web request contains a user id. - /// - public bool ContainsUserId => Headers["x-user-id"] != null; - /// /// Gets the content lines of the web request. /// diff --git a/Jung.SimpleWebSocket/SimpleWebSocketClient.cs b/Jung.SimpleWebSocket/SimpleWebSocketClient.cs index 2138cd6..6793e3d 100644 --- a/Jung.SimpleWebSocket/SimpleWebSocketClient.cs +++ b/Jung.SimpleWebSocket/SimpleWebSocketClient.cs @@ -22,9 +22,8 @@ namespace Jung.SimpleWebSocket /// The host name to connect to /// The port to connect to /// The web socket request path - /// The user id of the client. This is normally created by the server and sent back to the client /// A logger to write internal log messages - public class SimpleWebSocketClient(string hostName, int port, string requestPath, string? userId = null, ILogger? logger = null) : IWebSocketClient, IDisposable + public class SimpleWebSocketClient(string hostName, int port, string requestPath, ILogger? logger = null) : IWebSocketClient, IDisposable { /// public string HostName { get; } = hostName; @@ -33,9 +32,6 @@ public class SimpleWebSocketClient(string hostName, int port, string requestPath /// public string RequestPath { get; } = requestPath; - /// - public string? UserId { get; private set; } - /// public bool IsConnected => _client?.Connected ?? false; @@ -154,16 +150,11 @@ private async Task HandleWebSocketInitiation(TcpClientWrapper client, Cancellati _stream = client.GetStream(); var socketWrapper = new WebSocketUpgradeHandler(_stream); - var requestContext = WebContext.CreateRequest(HostName, Port, RequestPath, userId); + var requestContext = WebContext.CreateRequest(HostName, Port, RequestPath); await socketWrapper.SendUpgradeRequestAsync(requestContext, cancellationToken); var response = await socketWrapper.AwaitContextAsync(cancellationToken); WebSocketUpgradeHandler.ValidateUpgradeResponse(response, requestContext); - if (response.ContainsUserId) - { - UserId = response.UserId; - } - _webSocket = socketWrapper.CreateWebSocket(isServer: false); } diff --git a/Jung.SimpleWebSocket/SimpleWebSocketServer.cs b/Jung.SimpleWebSocket/SimpleWebSocketServer.cs index 13d28b9..04f6d1a 100644 --- a/Jung.SimpleWebSocket/SimpleWebSocketServer.cs +++ b/Jung.SimpleWebSocket/SimpleWebSocketServer.cs @@ -15,19 +15,24 @@ using System.Net; using System.Net.WebSockets; using System.Text; -using System.Threading; namespace Jung.SimpleWebSocket { /// /// A simple WebSocket server. /// - public class SimpleWebSocketServer : IWebSocketServer, IDisposable + /// + /// Initializes a new instance of the class that listens + /// for incoming connection attempts on the specified local IP address and port number. + /// + /// The options for the server + /// A logger to write internal log messages + public class SimpleWebSocketServer(SimpleWebSocketServerOptions options, ILogger? logger = null) : IWebSocketServer, IDisposable { /// - public IPAddress LocalIpAddress { get; } + public IPAddress LocalIpAddress { get; } = options.LocalIpAddress; /// - public int Port { get; } + public int Port { get; } = options.Port; /// public event EventHandler? ClientConnected; @@ -37,8 +42,6 @@ public class SimpleWebSocketServer : IWebSocketServer, IDisposable public event EventHandler? MessageReceived; /// public event EventHandler? BinaryMessageReceived; - /// - public event EventHandler? PassiveUserExpiredEvent; /// public event AsyncEventHandler? ClientUpgradeRequestReceivedAsync; @@ -48,11 +51,6 @@ public class SimpleWebSocketServer : IWebSocketServer, IDisposable /// internal ConcurrentDictionary ActiveClients { get; } = []; - /// - /// A dictionary of passive clients. - /// - internal IDictionary PassiveClients { get; set; } = null!; - /// public string[] ClientIds => [.. ActiveClients.Keys]; @@ -65,12 +63,12 @@ public class SimpleWebSocketServer : IWebSocketServer, IDisposable /// /// A logger to write internal log messages. /// - internal ILogger? Logger { get; } + internal ILogger? Logger { get; } = logger; /// /// The options for the server. /// - internal SimpleWebSocketServerOptions Options { get; } + internal SimpleWebSocketServerOptions Options { get; } = options; /// /// A flag indicating whether the server is started. @@ -92,48 +90,6 @@ public class SimpleWebSocketServer : IWebSocketServer, IDisposable /// private ITcpListener? _tcpListener; - /// - /// Initializes a new instance of the class that listens - /// for incoming connection attempts on the specified local IP address and port number. - /// - /// The options for the server - /// A logger to write internal log messages - public SimpleWebSocketServer(SimpleWebSocketServerOptions options, ILogger? logger = null) - { - LocalIpAddress = options.LocalIpAddress; - Port = options.Port; - Logger = logger; - Options = options; - InitializePassiveClientDictionary(options); - } - - /// - /// Initializes the passive clients dictionary. - /// - /// - private void InitializePassiveClientDictionary(SimpleWebSocketServerOptions options) - { - if (options.RememberDisconnectedClients) - { - // Initialize the passive clients dictionary - if (options.RemovePassiveClientsAfterClientExpirationTime) - { - var passiveClients = new ExpiringDictionary(options.PassiveClientLifetime, Logger); - passiveClients.ItemExpired += PassiveClients_ItemExpired; - PassiveClients = passiveClients; - } - else - { - PassiveClients = new Dictionary(); - } - } - else - { - // If user handling is not activated, the passive clients are not needed - PassiveClients = null!; - } - } - /// /// Initializes a new instance of the class that listens /// for incoming connection attempts on the specified local IP address and port number. @@ -271,9 +227,6 @@ private async Task HandleClientAsync(WebSocketServerClient client, CancellationT // Load the request context await flow.LoadRequestContext(); - // Handle the client user identification if activated - flow.HandleClientIdentification(); - // raise async client upgrade request received event var eventArgs = await flow.RaiseUpgradeEventAsync(ClientUpgradeRequestReceivedAsync); @@ -372,23 +325,6 @@ private async Task ProcessWebSocketMessagesAsync(WebSocketServerClient client, C } } - /// - /// Handles the event when a passive user expired. - /// - /// - /// Condition: is set to true. - /// - /// The sender of the event () - /// The arguments of the event - private void PassiveClients_ItemExpired(object? sender, ItemExpiredArgs e) - { - Logger?.LogDebug("Passive Client expired: {clientId}", e.Item.Id); - - // Raise the event asynchronously - // We don't want to block the cleanup process - AsyncEventRaiser.RaiseAsyncInNewTask(PassiveUserExpiredEvent, this, new PassiveUserExpiredArgs(e.Item.Id), _cancellationTokenSource.Token); - } - /// public void Dispose() { diff --git a/Jung.SimpleWebSocket/Utility/ExpiringDictionary.cs b/Jung.SimpleWebSocket/Utility/ExpiringDictionary.cs deleted file mode 100644 index 52d3131..0000000 --- a/Jung.SimpleWebSocket/Utility/ExpiringDictionary.cs +++ /dev/null @@ -1,281 +0,0 @@ -// This file is part of the Jung SimpleWebSocket project. -// The project is licensed under the MIT license. - -using Jung.SimpleWebSocket.Models.EventArguments; -using Microsoft.Extensions.Logging; -using System.Collections; -using System.Diagnostics.CodeAnalysis; - -namespace Jung.SimpleWebSocket.Utility; - -/// -/// Creates a dictionary with expiration time for each item. -/// -/// The type of the keys in the dictionary. -/// The type of the values in the dictionary. -/// The expiration time for each item. -/// The logger to log exceptions. -public class ExpiringDictionary(TimeSpan expiration, ILogger? logger = null): IDictionary where TKey : class -{ - - /// - /// Occurs when an item is expired. - /// - public event EventHandler>? ItemExpired; - - private readonly SortedList _expirationQueue = []; - private readonly Dictionary _dictionary = []; - - private bool _cleanupInProgress = false; - - /// - /// Add the specified key and value to the dictionary. - /// - /// - /// - public void Add(TKey key, TValue value) - { - lock (_dictionary) - { - // Add the item to the dictionary - _dictionary[key] = value; - - // Add item with expiration time to the queue - var expirationTime = DateTime.Now.Add(expiration); - - lock (_expirationQueue) - { - _expirationQueue.Add(expirationTime, key); - } - } - - // Trigger cleanup after the Add operation is done - lock (_expirationQueue) - { - if (!_cleanupInProgress) - { - _cleanupInProgress = true; - - // Run cleanup asynchronously - CleanupExpiredItems().ContinueWith(t => - { - if (t.IsFaulted) - { - // Handle exceptions here - logger?.LogError(t.Exception, "An Exception occurred during cleanup expired items."); - } - }); - } - } - } - - /// - /// Determines whether the dictionary contains the specified key. - /// - /// - /// - public bool ContainsKey(TKey key) - { - lock (_dictionary) - { - return _dictionary.ContainsKey(key); - } - } - /// - /// Removes the value with the specified key from the dictionary. - /// - /// The key of the value to remove. - /// Returns true if the element is successfully found and removed; otherwise, false. - public bool Remove(TKey key) - { - lock (_dictionary) - { - if (_dictionary.Remove(key)) - { - lock (_expirationQueue) - { - // Find and remove the expiration time entry for this key - var expirationTime = _expirationQueue.FirstOrDefault(x => x.Value.Equals(key)).Key; - if (expirationTime != default) - { - _expirationQueue.Remove(expirationTime); - } - } - return true; - } - } - return false; - } - - /// - /// Get or set the value associated with the specified key. - /// - /// The key of the value to get or set. - /// The value associated with the specified key. - public TValue this[TKey key] - { - get - { - lock (_dictionary) - { - return _dictionary[key]; - } - } - set - { - lock (_dictionary) - { - _dictionary[key] = value; - var expirationTime = DateTime.Now.Add(expiration); - lock (_expirationQueue) - { - _expirationQueue[expirationTime] = key; - } - } - } - } - - /// - /// Cleans up expired items. - /// - /// A task that represents the asynchronous cleanup operation. - private async Task CleanupExpiredItems() - { - while (true) - { - DateTime nearestExpiration; - TKey expiredKey; - - // Safely lock and retrieve the first item to expire - lock (_expirationQueue) - { - // If there are no items, stop the cleanup process - if (_expirationQueue.Count == 0) - { - _cleanupInProgress = false; - return; - } - - // Get the first key in expiration queue (FIFO order) - var firstItem = _expirationQueue.First(); - nearestExpiration = firstItem.Key; - expiredKey = firstItem.Value; - } - - // Calculate the delay based on the expiration time - TimeSpan delay = nearestExpiration - DateTime.Now; - - if (delay > TimeSpan.Zero) - { - await Task.Delay(delay); - } - - // Remove the expired item from the dictionary - lock (_dictionary) - { - if (_dictionary.TryGetValue(expiredKey, out var expiredItem)) - { - ItemExpired?.Invoke(this, new ItemExpiredArgs(expiredItem)); - _dictionary.Remove(expiredKey); - } - } - - // Remove from expiration queue, but only if it's still the correct key - lock (_expirationQueue) - { - if (_expirationQueue.Count > 0 && _expirationQueue.First().Value.Equals(expiredKey)) - { - _expirationQueue.RemoveAt(0); // Safely remove the correct item - } - } - } - } - - #region NotImplemented - - /// - /// Not implemented. - /// - public ICollection Keys => throw new NotImplementedException(); - - /// - /// Not implemented. - /// - public ICollection Values => throw new NotImplementedException(); - - /// - /// Not implemented. - /// - public int Count => throw new NotImplementedException(); - - /// - /// Not implemented. - /// - public bool IsReadOnly => throw new NotImplementedException(); - - /// - /// Not implemented. - /// - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) - { - throw new NotImplementedException(); - } - - /// - /// Not implemented. - /// - public void Add(KeyValuePair item) - { - throw new NotImplementedException(); - } - - /// - /// Not implemented. - /// - public void Clear() - { - throw new NotImplementedException(); - } - - /// - /// Not implemented. - /// - public bool Contains(KeyValuePair item) - { - throw new NotImplementedException(); - } - - /// - /// Not implemented. - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - /// - /// Not implemented. - /// - public bool Remove(KeyValuePair item) - { - throw new NotImplementedException(); - } - - /// - /// Not implemented. - /// - public IEnumerator> GetEnumerator() - { - throw new NotImplementedException(); - } - - /// - /// Not implemented. - /// - IEnumerator IEnumerable.GetEnumerator() - { - throw new NotImplementedException(); - } - - #endregion -} \ No newline at end of file diff --git a/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs b/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs index 416a4aa..8a8e122 100644 --- a/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs +++ b/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs @@ -63,7 +63,7 @@ public async Task AwaitContextAsync(CancellationToken cancellationTo return context; } - public async Task AcceptWebSocketAsync(WebContext request, WebContext response, string userId, string? subProtocol, CancellationToken cancellationToken) + public async Task AcceptWebSocketAsync(WebContext request, WebContext response, string? subProtocol, CancellationToken cancellationToken) { try { @@ -79,7 +79,6 @@ public async Task AcceptWebSocketAsync(WebContext request, WebContext response, response.Headers.Add("Upgrade", "websocket"); response.Headers.Add("Sec-WebSocket-Accept", secWebSocketAcceptString); response.StatusCode = HttpStatusCode.SwitchingProtocols; - response.Headers.Add(_userIdHeaderName, userId); await SendWebSocketResponseHeaders(response, cancellationToken); _acceptedProtocol = subProtocol; } diff --git a/Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs b/Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs index 0e8916e..c9ba4eb 100644 --- a/Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs +++ b/Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs @@ -1,6 +1,5 @@ using Jung.SimpleWebSocket; using Jung.SimpleWebSocket.Contracts; -using Jung.SimpleWebSocket.Exceptions; using Jung.SimpleWebSocket.Flows; using Jung.SimpleWebSocket.Models; using Jung.SimpleWebSocketTest.Mock; @@ -41,7 +40,7 @@ private ClientHandlingFlow SetupClientHandlingFlow(object serverOptions, List(); - - var client = new WebSocketServerClient(); - client.UpdateId(userId); - activeUsers.Add(client); - - var requestText = CreateUpgradeRequest(userId); - var serverOptions = new SimpleWebSocketServerOptions - { - RememberDisconnectedClients = true, - }; - - // act and assert - var clientHandlingFlow = SetupClientHandlingFlow(serverOptions, activeUsers); - - clientHandlingFlow.Request = new WebContext(requestText); - Assert.That(() => clientHandlingFlow.HandleClientIdentification(), Throws.Exception.TypeOf()); - } } } diff --git a/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs b/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs index e86696c..dc061e7 100644 --- a/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs +++ b/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs @@ -50,7 +50,6 @@ public async Task TestClientServerConnection_ShouldSendAndReceiveHelloWorld() { LocalIpAddress = IPAddress.Any, Port = 8010, - RememberDisconnectedClients = true, }; using var server = new SimpleWebSocketServer(serverOptions, _serverLoggerMockHelper.Logger); @@ -61,7 +60,6 @@ public async Task TestClientServerConnection_ShouldSendAndReceiveHelloWorld() const string ClosingStatusDescription = "closing status test description"; string receivedMessage = string.Empty; string receivedClosingDescription = string.Empty; - string exceptionMessage = string.Empty; var messageResetEvent = new ManualResetEvent(false); var disconnectResetEvent = new ManualResetEvent(false); @@ -123,21 +121,11 @@ public async Task TestClientServerConnection_ShouldSendAndReceiveHelloWorld() WaitForManualResetEventOrThrow(disconnectResetEvent); // test if the server accepts the client again - var client2 = new SimpleWebSocketClient(IPAddress.Loopback.ToString(), 8010, "/", client.UserId, logger: _clientLoggerMockHelper.Logger); + var client2 = new SimpleWebSocketClient(IPAddress.Loopback.ToString(), 8010, "/", logger: _clientLoggerMockHelper.Logger); await client2.ConnectAsync(); await Task.Delay(100); - try - { - // test if two clients with the same user id can connect - var client3 = new SimpleWebSocketClient(IPAddress.Loopback.ToString(), 8010, "/", client.UserId, logger: _clientLoggerMockHelper.Logger); - await client3.ConnectAsync(); - } - catch (Exception exception) - { - exceptionMessage = exception.InnerException!.Message; - } - + await client2.SendMessageAsync("Hello World"); await server.ShutdownServer(CancellationToken.None); @@ -148,7 +136,6 @@ public async Task TestClientServerConnection_ShouldSendAndReceiveHelloWorld() { Assert.That(receivedMessage, Is.EqualTo(Message)); Assert.That(receivedClosingDescription, Is.EqualTo(ClosingStatusDescription)); - Assert.That(exceptionMessage, Does.Contain("User id already in use")); }); } @@ -165,49 +152,6 @@ private static async Task DbContext_IpAddresses_Contains(IPAddress ipAddre return ipAddress.Equals(IPAddress.Loopback); } - - [Test] - [Platform("Windows7,Windows8,Windows8.1,Windows10", Reason = "This test establishes a TCP client-server connection using SimpleWebSocket, which relies on specific networking features and behaviors that are only available and consistent on Windows platforms. Running this test on non-Windows platforms could lead to inconsistent results or failures due to differences in networking stack implementations.")] - public async Task TestClientServerConnection_ShouldRemoveClientFromPassiveClients() - { - // Arrange - string userId = Guid.NewGuid().ToString(); - var serverOptions = new SimpleWebSocketServerOptions - { - LocalIpAddress = IPAddress.Any, - Port = 8010, - RememberDisconnectedClients = true, - RemovePassiveClientsAfterClientExpirationTime = true, - PassiveClientLifetime = TimeSpan.FromSeconds(1) - }; - - using var server = new SimpleWebSocketServer(serverOptions, _serverLoggerMockHelper.Logger); - using var client = new SimpleWebSocketClient(IPAddress.Loopback.ToString(), 8010, "/", userId, _clientLoggerMockHelper.Logger); - - var expiredClientId = string.Empty; - var expiredClientResetEvent = new ManualResetEvent(false); - - server.PassiveUserExpiredEvent += (sender, args) => - { - expiredClientId = args.ClientId; - expiredClientResetEvent.Set(); - }; - - // Act - server.Start(); - await client.ConnectAsync(); - await Task.Delay(100); - await client.DisconnectAsync(); - - WaitForManualResetEventOrThrow(expiredClientResetEvent, 2000); - - await server.ShutdownServer(CancellationToken.None); - Array.ForEach(LoggerMessages.GetMessages(), m => Trace.WriteLine(m)); - - // Assert - Assert.That(expiredClientId, Is.EqualTo(userId)); - } - [Test] [Platform("Windows7,Windows8,Windows8.1,Windows10", Reason = "This test establishes a TCP client-server connection using SimpleWebSocket, which relies on specific networking features and behaviors that are only available and consistent on Windows platforms. Running this test on non-Windows platforms could lead to inconsistent results or failures due to differences in networking stack implementations.")] public async Task TestClientServerConnection_ShouldSendAndReceiveHelloWorld2() diff --git a/Jung.SimpleWebSocketTest/WebSocketUpgradeHandlerTests.cs b/Jung.SimpleWebSocketTest/WebSocketUpgradeHandlerTests.cs index aa69078..158ce22 100644 --- a/Jung.SimpleWebSocketTest/WebSocketUpgradeHandlerTests.cs +++ b/Jung.SimpleWebSocketTest/WebSocketUpgradeHandlerTests.cs @@ -64,7 +64,7 @@ public async Task AcceptWebSocketAsync_ShouldSendUpgradeResponse(string hostname _mockNetworkStream.Setup(ns => ns.WriteAsync(It.IsAny(), It.IsAny())).Callback((buffer, ct) => { response = Encoding.UTF8.GetString(buffer); }); // Act - await _socketWrapper.AcceptWebSocketAsync(request,new WebContext(), Guid.NewGuid().ToString(), null, cancellationToken); + await _socketWrapper.AcceptWebSocketAsync(request, new WebContext(), null, cancellationToken); // Assert Assert.That(response, Does.Contain("HTTP/1.1 101 Switching Protocols")); @@ -90,7 +90,7 @@ public async Task AcceptWebSocketAsync_ShouldSendUpgradeResponseWithCorrectProto _mockNetworkStream.Setup(ns => ns.WriteAsync(It.IsAny(), It.IsAny())).Callback((buffer, ct) => { response = Encoding.UTF8.GetString(buffer); }); // Act - await _socketWrapper.AcceptWebSocketAsync(request, new WebContext(), Guid.NewGuid().ToString(), serverSubprotocol, cancellationToken); + await _socketWrapper.AcceptWebSocketAsync(request, new WebContext(), serverSubprotocol, cancellationToken); // Assert Assert.Multiple(() => From 57c4fb80fd9fb911b40c3c3cb9c0b7da0f73e7c6 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sun, 2 Feb 2025 09:47:59 +0100 Subject: [PATCH 2/7] Fix some spelling issues --- .../Wrappers/WebSocketUpgradeHandler.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs b/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs index 8a8e122..5d9f87b 100644 --- a/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs +++ b/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs @@ -22,11 +22,10 @@ internal partial class WebSocketUpgradeHandler { private const string _supportedVersion = "13"; private const string _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - private const string _userIdHeaderName = "x-user-id"; private string? _acceptedProtocol; private readonly INetworkStream _networkStream; - private readonly WebSocketHelper _websocketHelper; + private readonly WebSocketHelper _webSocketHelper; // Regex for a valid request path: must start with a `/` and can include valid path characters. [GeneratedRegex(@"^\/[a-zA-Z0-9\-._~\/]*$", RegexOptions.Compiled)] @@ -36,13 +35,13 @@ internal partial class WebSocketUpgradeHandler public WebSocketUpgradeHandler(INetworkStream networkStream) { _networkStream = networkStream; - _websocketHelper = new WebSocketHelper(); + _webSocketHelper = new WebSocketHelper(); } - internal WebSocketUpgradeHandler(INetworkStream networkStream, WebSocketHelper websocketHelper) + internal WebSocketUpgradeHandler(INetworkStream networkStream, WebSocketHelper webSocketHelper) { _networkStream = networkStream; - _websocketHelper = websocketHelper; + _webSocketHelper = webSocketHelper; } public async Task AwaitContextAsync(CancellationToken cancellationToken) @@ -88,7 +87,7 @@ public async Task AcceptWebSocketAsync(WebContext request, WebContext response, } catch (Exception message) { - throw new WebSocketException("Error while accepting the websocket", message); + throw new WebSocketException("Error while accepting the web socket", message); } } @@ -295,7 +294,7 @@ private static string ComputeWebSocketAccept(string secWebSocketKey) internal IWebSocket CreateWebSocket(bool isServer, TimeSpan? keepAliveInterval = null) { keepAliveInterval ??= TimeSpan.FromSeconds(30); - return _websocketHelper.CreateFromStream(_networkStream.Stream, isServer, _acceptedProtocol, keepAliveInterval.Value); + return _webSocketHelper.CreateFromStream(_networkStream.Stream, isServer, _acceptedProtocol, keepAliveInterval.Value); } internal async Task RejectWebSocketAsync(WebContext response, CancellationToken cancellationToken) From 8998849e144d58fc9f8785e301abed8192a35b42 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sun, 2 Feb 2025 09:54:49 +0100 Subject: [PATCH 3/7] Add method to change the id of a user to the web socket server --- .../Contracts/IWebSocketServer.cs | 10 +++ .../ClientIdAlreadyExistsException.cs | 13 ++++ .../Exceptions/ClientNotFoundException.cs | 13 ++++ Jung.SimpleWebSocket/SimpleWebSocketServer.cs | 14 ++++ .../SimpleWebSocketTest.cs | 76 ++++++++++++++++++- 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs create mode 100644 Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs diff --git a/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs b/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs index f4973a2..a12093e 100644 --- a/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs +++ b/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs @@ -2,6 +2,7 @@ // The project is licensed under the MIT license. using Jung.SimpleWebSocket.Delegates; +using Jung.SimpleWebSocket.Exceptions; using Jung.SimpleWebSocket.Models; using Jung.SimpleWebSocket.Models.EventArguments; using System.Net; @@ -92,4 +93,13 @@ public interface IWebSocketServer : IDisposable /// The cancellation token. /// A task representing the asynchronous operation. void Start(CancellationToken? cancellationToken = null); + + /// + /// Changes the id of a client. + /// + /// The client to update + /// The new id of the client + /// Throws when the client is not found + /// Throws when the new id is already in use + void ChangeClientId(WebSocketServerClient client, string newId); } diff --git a/Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs b/Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs new file mode 100644 index 0000000..91635d6 --- /dev/null +++ b/Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs @@ -0,0 +1,13 @@ +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +namespace Jung.SimpleWebSocket.Exceptions +{ + /// + /// Exception thrown when a client with the same id already exists in the client list. + /// + /// The message to display when the exception is thrown. + public class ClientIdAlreadyExistsException(string message) : Exception(message) + { + } +} diff --git a/Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs b/Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs new file mode 100644 index 0000000..58d4a95 --- /dev/null +++ b/Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs @@ -0,0 +1,13 @@ +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +namespace Jung.SimpleWebSocket.Exceptions +{ + /// + /// Exception thrown when a client with the given id is not found in the client list. + /// + /// The message to display when the exception is thrown. + public class ClientNotFoundException(string message) : Exception(message) + { + } +} diff --git a/Jung.SimpleWebSocket/SimpleWebSocketServer.cs b/Jung.SimpleWebSocket/SimpleWebSocketServer.cs index 04f6d1a..c2cd853 100644 --- a/Jung.SimpleWebSocket/SimpleWebSocketServer.cs +++ b/Jung.SimpleWebSocket/SimpleWebSocketServer.cs @@ -213,6 +213,20 @@ public WebSocketServerClient GetClientById(string clientId) return client; } + /// + public void ChangeClientId(WebSocketServerClient client, string newId) + { + // if the client is not found or the new id is already in use, throw an exception + if (!ActiveClients.TryGetValue(client.Id, out var _)) throw new ClientNotFoundException(message: "A client with the given id was not found"); + if (ActiveClients.ContainsKey(newId)) throw new ClientIdAlreadyExistsException(message: "A client with the new id already exists"); + + // because the id is used as a key in the dictionary, + // we have to remove the client and add it again with the new id + ActiveClients.TryRemove(client.Id, out _); + client.UpdateId(newId); + ActiveClients.TryAdd(newId, client); + } + /// /// Handles the client connection. /// diff --git a/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs b/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs index dc061e7..8f9bcb3 100644 --- a/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs +++ b/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs @@ -2,6 +2,7 @@ // The project is licensed under the MIT license. using Jung.SimpleWebSocket; +using Jung.SimpleWebSocket.Exceptions; using Jung.SimpleWebSocket.Models; using Jung.SimpleWebSocketTest.Mock; using NUnit.Framework; @@ -39,7 +40,80 @@ public void EndTest() Trace.Flush(); } + [Test] + public void ChangeClientId_UserIdUnique_ShouldUpdateId() + { + // Arrange + var serverOptions = new SimpleWebSocketServerOptions + { + LocalIpAddress = IPAddress.Any, + Port = 8010, + }; + + var connectedClient1 = new WebSocketServerClient(); + var connectedClient2 = new WebSocketServerClient(); + var oldId = connectedClient1.Id; + + using var server = new SimpleWebSocketServer(serverOptions, _serverLoggerMockHelper.Logger); + if (!server.ActiveClients.TryAdd(connectedClient1.Id, connectedClient1) || + !server.ActiveClients.TryAdd(connectedClient2.Id, connectedClient2)) + { + throw new Exception("Could not add clients to the server."); + } + // Act + var newId = Guid.NewGuid().ToString(); + server.ChangeClientId(connectedClient1, newId); + + // Assert + Assert.Multiple(() => + { + Assert.That(connectedClient1.Id, Is.EqualTo(newId)); + Assert.That(server.ActiveClients.ContainsKey(oldId), Is.False); + Assert.That(server.ActiveClients.ContainsKey(newId), Is.True); + }); + } + + [Test] + public void ChangeClientId_UserIdDuplicated_ShouldThrowException() + { + // Arrange + var serverOptions = new SimpleWebSocketServerOptions + { + LocalIpAddress = IPAddress.Any, + Port = 8010, + }; + + var connectedClient1 = new WebSocketServerClient(); + var connectedClient2 = new WebSocketServerClient(); + + + using var server = new SimpleWebSocketServer(serverOptions, _serverLoggerMockHelper.Logger); + if (!server.ActiveClients.TryAdd(connectedClient1.Id, connectedClient1) || + !server.ActiveClients.TryAdd(connectedClient2.Id, connectedClient2)) + { + throw new Exception("Could not add clients to the server."); + } + + // Act & Assert + Assert.That(() => server.ChangeClientId(connectedClient1, connectedClient2.Id), Throws.Exception.TypeOf()); + } + + [Test] + public void ChangeClientId_TargetUserNotExisting_ShouldThrowException() + { + // Arrange + var serverOptions = new SimpleWebSocketServerOptions + { + LocalIpAddress = IPAddress.Any, + Port = 8010, + }; + + using var server = new SimpleWebSocketServer(serverOptions, _serverLoggerMockHelper.Logger); + + // Act & Assert + Assert.That(() => server.ChangeClientId(new WebSocketServerClient(), Guid.NewGuid().ToString()), Throws.Exception.TypeOf()); + } [Test] [Platform("Windows7,Windows8,Windows8.1,Windows10", Reason = "This test establishes a TCP client-server connection using SimpleWebSocket, which relies on specific networking features and behaviors that are only available and consistent on Windows platforms. Running this test on non-Windows platforms could lead to inconsistent results or failures due to differences in networking stack implementations.")] @@ -125,7 +199,7 @@ public async Task TestClientServerConnection_ShouldSendAndReceiveHelloWorld() await client2.ConnectAsync(); await Task.Delay(100); - + await client2.SendMessageAsync("Hello World"); await server.ShutdownServer(CancellationToken.None); From e30b32ab5275863d03cf7388ffecc04048ab4df5 Mon Sep 17 00:00:00 2001 From: Christoph Jung Date: Sat, 8 Feb 2025 19:00:10 +0100 Subject: [PATCH 4/7] Improve handling flow --- .../Flows/ClientHandlingFlow.cs | 58 +++++++++++++------ Jung.SimpleWebSocket/SimpleWebSocketServer.cs | 5 +- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs b/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs index e41e93e..5519676 100644 --- a/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs +++ b/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs @@ -7,6 +7,7 @@ using Jung.SimpleWebSocket.Utility; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; namespace Jung.SimpleWebSocket.Flows { @@ -29,17 +30,17 @@ internal class ClientHandlingFlow(SimpleWebSocketServer server, WebSocketServerC /// /// Gets the request context of the client. /// - internal WebContext Request { get; set; } = null!; + internal WebContext? Request { get; set; } = null!; /// /// Gets the upgrade handler for the client. /// - private WebSocketUpgradeHandler _upgradeHandler = null!; + private WebSocketUpgradeHandler? _upgradeHandler = null; /// /// Gets the response context that is being use to response to the client. /// - private WebContext _responseContext = null!; + private WebContext? _responseContext = null; /// /// Gets the active clients of the server. @@ -56,11 +57,6 @@ internal class ClientHandlingFlow(SimpleWebSocketServer server, WebSocketServerC /// private readonly CancellationToken _cancellationToken = cancellationToken; - /// - /// The lock object for the client dictionaries. - /// - private static readonly object _clientLock = new(); - /// /// Loads the request context. /// @@ -76,11 +72,15 @@ internal async Task LoadRequestContext() /// internal async Task AcceptWebSocketAsync() { + // Check if the response context are initialized + ThrowForResponseContextNotInitialized(_responseContext); + // The client is accepted - await _upgradeHandler.AcceptWebSocketAsync(Request, _responseContext, null, _cancellationToken); + await _upgradeHandler!.AcceptWebSocketAsync(Request!, _responseContext, null, _cancellationToken); // Use the web socket for the client Client.UseWebSocket(_upgradeHandler.CreateWebSocket(isServer: true)); + Cleanup(); } /// @@ -89,7 +89,9 @@ internal async Task AcceptWebSocketAsync() /// The response context to send to the client. internal async Task RejectWebSocketAsync(WebContext responseContext) { - await _upgradeHandler.RejectWebSocketAsync(responseContext, _cancellationToken); + // The client is rejected + await _upgradeHandler!.RejectWebSocketAsync(responseContext, _cancellationToken); + Cleanup(); } /// @@ -97,13 +99,10 @@ internal async Task RejectWebSocketAsync(WebContext responseContext) /// internal void HandleDisconnectedClient() { - lock (_clientLock) - { - _activeClients.TryRemove(Client.Id, out _); - Client.Dispose(); + _activeClients.TryRemove(Client.Id, out _); + Client.Dispose(); - _logger?.LogDebug("Client {clientId} is removed.", Client.Id); - } + _logger?.LogDebug("Client {clientId} is removed.", Client.Id); } /// @@ -113,7 +112,7 @@ internal void HandleDisconnectedClient() /// The event arguments of the upgrade request. internal async Task RaiseUpgradeEventAsync(AsyncEventHandler? clientUpgradeRequestReceivedAsync) { - var eventArgs = new ClientUpgradeRequestReceivedArgs(Client, Request, _logger); + var eventArgs = new ClientUpgradeRequestReceivedArgs(Client, Request!, _logger); await AsyncEventRaiser.RaiseAsync(clientUpgradeRequestReceivedAsync, server, eventArgs, _cancellationToken); _responseContext = eventArgs.ResponseContext; return eventArgs; @@ -127,5 +126,28 @@ internal bool TryAddClientToActiveUserList() { return _activeClients.TryAdd(Client.Id, Client); } + + /// + /// Throws an exception if the response context is not initialized. + /// + /// The response context to check. + /// + private static void ThrowForResponseContextNotInitialized([NotNull] WebContext? responseContext) + { + if (responseContext is null) + { + throw new InvalidOperationException("The response context is not initialized."); + } + } + + /// + /// Disposes the upgrade handler. + /// + private void Cleanup() + { + _upgradeHandler = null; + _responseContext = null; + Request = null; + } } -} \ No newline at end of file +} diff --git a/Jung.SimpleWebSocket/SimpleWebSocketServer.cs b/Jung.SimpleWebSocket/SimpleWebSocketServer.cs index c2cd853..4b298cf 100644 --- a/Jung.SimpleWebSocket/SimpleWebSocketServer.cs +++ b/Jung.SimpleWebSocket/SimpleWebSocketServer.cs @@ -241,7 +241,7 @@ private async Task HandleClientAsync(WebSocketServerClient client, CancellationT // Load the request context await flow.LoadRequestContext(); - // raise async client upgrade request received event + // Raise async client upgrade request received event var eventArgs = await flow.RaiseUpgradeEventAsync(ClientUpgradeRequestReceivedAsync); // Respond to the upgrade request @@ -249,6 +249,7 @@ private async Task HandleClientAsync(WebSocketServerClient client, CancellationT { // Accept the WebSocket connection await flow.AcceptWebSocketAsync(); + if (flow.TryAddClientToActiveUserList()) { Logger?.LogDebug("Connection upgraded, now listening on Client {clientId}", flow.Client.Id); @@ -258,7 +259,7 @@ private async Task HandleClientAsync(WebSocketServerClient client, CancellationT } else { - Logger?.LogDebug("Connection upgraded, now listening on Client {clientId}", flow.Client.Id); + Logger?.LogDebug("Error while adding Client {clientId} to active clients", flow.Client.Id); } } else From d26cbca23d62b38f5620d59c01d642562dd8d6dd Mon Sep 17 00:00:00 2001 From: Christoph Jung Date: Tue, 11 Feb 2025 20:05:53 +0100 Subject: [PATCH 5/7] Move projects in src and tests directories --- Jung.SimpleWebSocket.sln | 10 +++- .../ClientHandlingFlowTest.cs | 57 ------------------- .../Jung.SimpleWebSocket}/AssemblyInfo.cs | 2 +- .../Contracts/INetworkStream.cs | 0 .../Contracts/ITcpClient.cs | 0 .../Contracts/ITcpListener.cs | 0 .../Contracts/IWebSocket.cs | 0 .../Contracts/IWebSocketClient.cs | 0 .../Contracts/IWebSocketServer.cs | 0 .../Delegates/AsyncEventHandler.cs | 0 .../BinaryMessageReceivedEventHandler.cs | 0 ...ClientBinaryMessageReceivedEventHandler.cs | 0 .../Delegates/ClientConnectedEventHandler.cs | 0 .../ClientDisconnectedEventHandler.cs | 0 .../ClientMessageReceivedEventHandler.cs | 0 .../Delegates/DisconnectedEventHandler.cs | 0 .../Delegates/MessageReceivedEventHandler.cs | 0 .../ClientIdAlreadyExistsException.cs | 0 .../Exceptions/ClientNotFoundException.cs | 0 .../Exceptions/SimpleWebSocketException.cs | 0 .../Exceptions/UserNotHandledException.cs | 0 .../Exceptions/WebContextException.cs | 0 .../Exceptions/WebSocketClientException.cs | 0 .../Exceptions/WebSocketServerException.cs | 0 .../Exceptions/WebSocketUpgradeException.cs | 0 .../Flows/ClientHandlingFlow.cs | 0 .../Helpers/WebSocketHelper.cs | 0 .../Jung.SimpleWebSocket.csproj | 0 .../BinaryMessageReceivedArgs.cs | 0 .../ClientBinaryMessageReceivedArgs.cs | 0 .../EventArguments/ClientConnectedArgs.cs | 0 .../EventArguments/ClientDisconnectedArgs.cs | 0 .../ClientMessageReceivedArgs.cs | 0 .../ClientUpgradeRequestReceivedArgs.cs | 0 .../Models/EventArguments/DisconnectedArgs.cs | 0 .../Models/EventArguments/ItemExpiredArgs.cs | 0 .../EventArguments/MessageReceivedArgs.cs | 0 .../Models/SimpleWebSocketServerOptions.cs | 0 .../Models/WebContext.cs | 0 .../Models/WebSocketServerClient.cs | 0 .../SimpleWebSocketClient.cs | 0 .../SimpleWebSocketServer.cs | 0 .../Utility/AsyncEventRaiser.cs | 0 .../Wrappers/NetworkStreamWrapper.cs | 0 .../Wrappers/TcpClientWrapper.cs | 0 .../Wrappers/TcpListenerWrapper.cs | 0 .../Wrappers/WebSocketUpgradeHandler.cs | 0 .../Wrappers/WebSocketWrapper.cs | 0 .../Jung.SimpleWebSocket}/docs/README.md | 0 .../Jung.SimpleWebSocket.UnitTests.csproj | 2 +- .../Mock/ILoggerMockHelper.cs | 7 ++- .../Mock/LoggerMessages.cs | 6 +- .../SimpleWebSocketTest.cs | 7 +-- .../WebContextTest.cs | 5 +- .../WebSocketUpgradeHandlerTests.cs | 6 +- 55 files changed, 30 insertions(+), 72 deletions(-) delete mode 100644 Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/AssemblyInfo.cs (94%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Contracts/INetworkStream.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Contracts/ITcpClient.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Contracts/ITcpListener.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Contracts/IWebSocket.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Contracts/IWebSocketClient.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Contracts/IWebSocketServer.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Delegates/AsyncEventHandler.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Delegates/BinaryMessageReceivedEventHandler.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Delegates/ClientBinaryMessageReceivedEventHandler.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Delegates/ClientConnectedEventHandler.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Delegates/ClientDisconnectedEventHandler.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Delegates/ClientMessageReceivedEventHandler.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Delegates/DisconnectedEventHandler.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Delegates/MessageReceivedEventHandler.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Exceptions/ClientIdAlreadyExistsException.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Exceptions/ClientNotFoundException.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Exceptions/SimpleWebSocketException.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Exceptions/UserNotHandledException.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Exceptions/WebContextException.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Exceptions/WebSocketClientException.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Exceptions/WebSocketServerException.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Exceptions/WebSocketUpgradeException.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Flows/ClientHandlingFlow.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Helpers/WebSocketHelper.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Jung.SimpleWebSocket.csproj (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/EventArguments/BinaryMessageReceivedArgs.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/EventArguments/ClientBinaryMessageReceivedArgs.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/EventArguments/ClientConnectedArgs.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/EventArguments/ClientDisconnectedArgs.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/EventArguments/ClientMessageReceivedArgs.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/EventArguments/ClientUpgradeRequestReceivedArgs.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/EventArguments/DisconnectedArgs.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/EventArguments/ItemExpiredArgs.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/EventArguments/MessageReceivedArgs.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/SimpleWebSocketServerOptions.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/WebContext.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Models/WebSocketServerClient.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/SimpleWebSocketClient.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/SimpleWebSocketServer.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Utility/AsyncEventRaiser.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Wrappers/NetworkStreamWrapper.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Wrappers/TcpClientWrapper.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Wrappers/TcpListenerWrapper.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Wrappers/WebSocketUpgradeHandler.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/Wrappers/WebSocketWrapper.cs (100%) rename {Jung.SimpleWebSocket => src/Jung.SimpleWebSocket}/docs/README.md (100%) rename Jung.SimpleWebSocketTest/Jung.SimpleWebSocketTest.csproj => tests/Jung.SimpleWebSocket.UnitTests/Jung.SimpleWebSocket.UnitTests.csproj (88%) rename {Jung.SimpleWebSocketTest => tests/Jung.SimpleWebSocket.UnitTests}/Mock/ILoggerMockHelper.cs (87%) rename {Jung.SimpleWebSocketTest => tests/Jung.SimpleWebSocket.UnitTests}/Mock/LoggerMessages.cs (76%) rename {Jung.SimpleWebSocketTest => tests/Jung.SimpleWebSocket.UnitTests}/SimpleWebSocketTest.cs (98%) rename {Jung.SimpleWebSocketTest => tests/Jung.SimpleWebSocket.UnitTests}/WebContextTest.cs (97%) rename {Jung.SimpleWebSocketTest => tests/Jung.SimpleWebSocket.UnitTests}/WebSocketUpgradeHandlerTests.cs (98%) diff --git a/Jung.SimpleWebSocket.sln b/Jung.SimpleWebSocket.sln index ed5da37..b5af771 100644 --- a/Jung.SimpleWebSocket.sln +++ b/Jung.SimpleWebSocket.sln @@ -3,9 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.11.35222.181 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jung.SimpleWebSocket", "Jung.SimpleWebSocket\Jung.SimpleWebSocket.csproj", "{793B04E9-6326-425A-A29C-A736CFD1E0C0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jung.SimpleWebSocket", "src\Jung.SimpleWebSocket\Jung.SimpleWebSocket.csproj", "{793B04E9-6326-425A-A29C-A736CFD1E0C0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jung.SimpleWebSocketTest", "Jung.SimpleWebSocketTest\Jung.SimpleWebSocketTest.csproj", "{26725C3C-8E90-49AC-9EE4-2A77ADB2229D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jung.SimpleWebSocket.UnitTests", "tests\Jung.SimpleWebSocket.UnitTests\Jung.SimpleWebSocket.UnitTests.csproj", "{26725C3C-8E90-49AC-9EE4-2A77ADB2229D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jung.SimpleWebSocket.IntegrationTests", "tests\Jung.SimpleWebSocket.IntegrationTests\Jung.SimpleWebSocket.IntegrationTests.csproj", "{144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +23,10 @@ Global {26725C3C-8E90-49AC-9EE4-2A77ADB2229D}.Debug|Any CPU.Build.0 = Debug|Any CPU {26725C3C-8E90-49AC-9EE4-2A77ADB2229D}.Release|Any CPU.ActiveCfg = Release|Any CPU {26725C3C-8E90-49AC-9EE4-2A77ADB2229D}.Release|Any CPU.Build.0 = Release|Any CPU + {144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs b/Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs deleted file mode 100644 index c9ba4eb..0000000 --- a/Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Jung.SimpleWebSocket; -using Jung.SimpleWebSocket.Contracts; -using Jung.SimpleWebSocket.Flows; -using Jung.SimpleWebSocket.Models; -using Jung.SimpleWebSocketTest.Mock; -using Microsoft.Extensions.Logging; -using Moq; -using NUnit.Framework; -using System.Text; - -namespace Jung.SimpleWebSocketTest -{ - [TestFixture] - internal class ClientHandlingFlowTest - { - private ILogger _logger; - - [SetUp] - public void SetUp() - { - var loggerHelper = new ILoggerMockHelper("Server"); - _logger = loggerHelper.Logger; - } - - private ClientHandlingFlow SetupClientHandlingFlow(object serverOptions, List? activeClients = null) - { - var tcpListener = new Mock(); - var serverMoq = new Mock(serverOptions, tcpListener.Object, _logger); - if (activeClients != null) - { - foreach (var client in activeClients) - { - serverMoq.Object.ActiveClients.TryAdd(client.Id, client); - } - } - - var tcpClientMoq = new Mock(); - var serverClientMoq = new WebSocketServerClient(tcpClientMoq.Object); - - return new ClientHandlingFlow(serverMoq.Object, serverClientMoq, CancellationToken.None); - } - - private string CreateUpgradeRequest() - { - var sb = new StringBuilder(); - sb.Append("GET /chat HTTP/1.1\r\n" + - "Host: localhost:8080\r\n" + - "Upgrade: websocket\r\n" + - "Connection: Upgrade\r\n" + - "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n" + - "Sec-WebSocket-Version: 13\r\n"); - - sb.Append("\r\n\r\n"); - return sb.ToString(); - } - } -} diff --git a/Jung.SimpleWebSocket/AssemblyInfo.cs b/src/Jung.SimpleWebSocket/AssemblyInfo.cs similarity index 94% rename from Jung.SimpleWebSocket/AssemblyInfo.cs rename to src/Jung.SimpleWebSocket/AssemblyInfo.cs index dddbe37..44a7198 100644 --- a/Jung.SimpleWebSocket/AssemblyInfo.cs +++ b/src/Jung.SimpleWebSocket/AssemblyInfo.cs @@ -21,5 +21,5 @@ [assembly: Guid("ca34219d-7a2e-4993-ad9d-f27fda1bb9dc")] // Make internals visible to the test project and the dynamic proxy assembly (moq) -[assembly: InternalsVisibleTo("Jung.SimpleWebSocketTest")] +[assembly: InternalsVisibleTo("Jung.SimpleWebSocket.UnitTests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/Jung.SimpleWebSocket/Contracts/INetworkStream.cs b/src/Jung.SimpleWebSocket/Contracts/INetworkStream.cs similarity index 100% rename from Jung.SimpleWebSocket/Contracts/INetworkStream.cs rename to src/Jung.SimpleWebSocket/Contracts/INetworkStream.cs diff --git a/Jung.SimpleWebSocket/Contracts/ITcpClient.cs b/src/Jung.SimpleWebSocket/Contracts/ITcpClient.cs similarity index 100% rename from Jung.SimpleWebSocket/Contracts/ITcpClient.cs rename to src/Jung.SimpleWebSocket/Contracts/ITcpClient.cs diff --git a/Jung.SimpleWebSocket/Contracts/ITcpListener.cs b/src/Jung.SimpleWebSocket/Contracts/ITcpListener.cs similarity index 100% rename from Jung.SimpleWebSocket/Contracts/ITcpListener.cs rename to src/Jung.SimpleWebSocket/Contracts/ITcpListener.cs diff --git a/Jung.SimpleWebSocket/Contracts/IWebSocket.cs b/src/Jung.SimpleWebSocket/Contracts/IWebSocket.cs similarity index 100% rename from Jung.SimpleWebSocket/Contracts/IWebSocket.cs rename to src/Jung.SimpleWebSocket/Contracts/IWebSocket.cs diff --git a/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs b/src/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs similarity index 100% rename from Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs rename to src/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs diff --git a/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs b/src/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs similarity index 100% rename from Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs rename to src/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs diff --git a/Jung.SimpleWebSocket/Delegates/AsyncEventHandler.cs b/src/Jung.SimpleWebSocket/Delegates/AsyncEventHandler.cs similarity index 100% rename from Jung.SimpleWebSocket/Delegates/AsyncEventHandler.cs rename to src/Jung.SimpleWebSocket/Delegates/AsyncEventHandler.cs diff --git a/Jung.SimpleWebSocket/Delegates/BinaryMessageReceivedEventHandler.cs b/src/Jung.SimpleWebSocket/Delegates/BinaryMessageReceivedEventHandler.cs similarity index 100% rename from Jung.SimpleWebSocket/Delegates/BinaryMessageReceivedEventHandler.cs rename to src/Jung.SimpleWebSocket/Delegates/BinaryMessageReceivedEventHandler.cs diff --git a/Jung.SimpleWebSocket/Delegates/ClientBinaryMessageReceivedEventHandler.cs b/src/Jung.SimpleWebSocket/Delegates/ClientBinaryMessageReceivedEventHandler.cs similarity index 100% rename from Jung.SimpleWebSocket/Delegates/ClientBinaryMessageReceivedEventHandler.cs rename to src/Jung.SimpleWebSocket/Delegates/ClientBinaryMessageReceivedEventHandler.cs diff --git a/Jung.SimpleWebSocket/Delegates/ClientConnectedEventHandler.cs b/src/Jung.SimpleWebSocket/Delegates/ClientConnectedEventHandler.cs similarity index 100% rename from Jung.SimpleWebSocket/Delegates/ClientConnectedEventHandler.cs rename to src/Jung.SimpleWebSocket/Delegates/ClientConnectedEventHandler.cs diff --git a/Jung.SimpleWebSocket/Delegates/ClientDisconnectedEventHandler.cs b/src/Jung.SimpleWebSocket/Delegates/ClientDisconnectedEventHandler.cs similarity index 100% rename from Jung.SimpleWebSocket/Delegates/ClientDisconnectedEventHandler.cs rename to src/Jung.SimpleWebSocket/Delegates/ClientDisconnectedEventHandler.cs diff --git a/Jung.SimpleWebSocket/Delegates/ClientMessageReceivedEventHandler.cs b/src/Jung.SimpleWebSocket/Delegates/ClientMessageReceivedEventHandler.cs similarity index 100% rename from Jung.SimpleWebSocket/Delegates/ClientMessageReceivedEventHandler.cs rename to src/Jung.SimpleWebSocket/Delegates/ClientMessageReceivedEventHandler.cs diff --git a/Jung.SimpleWebSocket/Delegates/DisconnectedEventHandler.cs b/src/Jung.SimpleWebSocket/Delegates/DisconnectedEventHandler.cs similarity index 100% rename from Jung.SimpleWebSocket/Delegates/DisconnectedEventHandler.cs rename to src/Jung.SimpleWebSocket/Delegates/DisconnectedEventHandler.cs diff --git a/Jung.SimpleWebSocket/Delegates/MessageReceivedEventHandler.cs b/src/Jung.SimpleWebSocket/Delegates/MessageReceivedEventHandler.cs similarity index 100% rename from Jung.SimpleWebSocket/Delegates/MessageReceivedEventHandler.cs rename to src/Jung.SimpleWebSocket/Delegates/MessageReceivedEventHandler.cs diff --git a/Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs b/src/Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs similarity index 100% rename from Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs rename to src/Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs diff --git a/Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs b/src/Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs similarity index 100% rename from Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs rename to src/Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs diff --git a/Jung.SimpleWebSocket/Exceptions/SimpleWebSocketException.cs b/src/Jung.SimpleWebSocket/Exceptions/SimpleWebSocketException.cs similarity index 100% rename from Jung.SimpleWebSocket/Exceptions/SimpleWebSocketException.cs rename to src/Jung.SimpleWebSocket/Exceptions/SimpleWebSocketException.cs diff --git a/Jung.SimpleWebSocket/Exceptions/UserNotHandledException.cs b/src/Jung.SimpleWebSocket/Exceptions/UserNotHandledException.cs similarity index 100% rename from Jung.SimpleWebSocket/Exceptions/UserNotHandledException.cs rename to src/Jung.SimpleWebSocket/Exceptions/UserNotHandledException.cs diff --git a/Jung.SimpleWebSocket/Exceptions/WebContextException.cs b/src/Jung.SimpleWebSocket/Exceptions/WebContextException.cs similarity index 100% rename from Jung.SimpleWebSocket/Exceptions/WebContextException.cs rename to src/Jung.SimpleWebSocket/Exceptions/WebContextException.cs diff --git a/Jung.SimpleWebSocket/Exceptions/WebSocketClientException.cs b/src/Jung.SimpleWebSocket/Exceptions/WebSocketClientException.cs similarity index 100% rename from Jung.SimpleWebSocket/Exceptions/WebSocketClientException.cs rename to src/Jung.SimpleWebSocket/Exceptions/WebSocketClientException.cs diff --git a/Jung.SimpleWebSocket/Exceptions/WebSocketServerException.cs b/src/Jung.SimpleWebSocket/Exceptions/WebSocketServerException.cs similarity index 100% rename from Jung.SimpleWebSocket/Exceptions/WebSocketServerException.cs rename to src/Jung.SimpleWebSocket/Exceptions/WebSocketServerException.cs diff --git a/Jung.SimpleWebSocket/Exceptions/WebSocketUpgradeException.cs b/src/Jung.SimpleWebSocket/Exceptions/WebSocketUpgradeException.cs similarity index 100% rename from Jung.SimpleWebSocket/Exceptions/WebSocketUpgradeException.cs rename to src/Jung.SimpleWebSocket/Exceptions/WebSocketUpgradeException.cs diff --git a/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs b/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs similarity index 100% rename from Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs rename to src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs diff --git a/Jung.SimpleWebSocket/Helpers/WebSocketHelper.cs b/src/Jung.SimpleWebSocket/Helpers/WebSocketHelper.cs similarity index 100% rename from Jung.SimpleWebSocket/Helpers/WebSocketHelper.cs rename to src/Jung.SimpleWebSocket/Helpers/WebSocketHelper.cs diff --git a/Jung.SimpleWebSocket/Jung.SimpleWebSocket.csproj b/src/Jung.SimpleWebSocket/Jung.SimpleWebSocket.csproj similarity index 100% rename from Jung.SimpleWebSocket/Jung.SimpleWebSocket.csproj rename to src/Jung.SimpleWebSocket/Jung.SimpleWebSocket.csproj diff --git a/Jung.SimpleWebSocket/Models/EventArguments/BinaryMessageReceivedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/BinaryMessageReceivedArgs.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/EventArguments/BinaryMessageReceivedArgs.cs rename to src/Jung.SimpleWebSocket/Models/EventArguments/BinaryMessageReceivedArgs.cs diff --git a/Jung.SimpleWebSocket/Models/EventArguments/ClientBinaryMessageReceivedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/ClientBinaryMessageReceivedArgs.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/EventArguments/ClientBinaryMessageReceivedArgs.cs rename to src/Jung.SimpleWebSocket/Models/EventArguments/ClientBinaryMessageReceivedArgs.cs diff --git a/Jung.SimpleWebSocket/Models/EventArguments/ClientConnectedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/ClientConnectedArgs.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/EventArguments/ClientConnectedArgs.cs rename to src/Jung.SimpleWebSocket/Models/EventArguments/ClientConnectedArgs.cs diff --git a/Jung.SimpleWebSocket/Models/EventArguments/ClientDisconnectedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/ClientDisconnectedArgs.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/EventArguments/ClientDisconnectedArgs.cs rename to src/Jung.SimpleWebSocket/Models/EventArguments/ClientDisconnectedArgs.cs diff --git a/Jung.SimpleWebSocket/Models/EventArguments/ClientMessageReceivedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/ClientMessageReceivedArgs.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/EventArguments/ClientMessageReceivedArgs.cs rename to src/Jung.SimpleWebSocket/Models/EventArguments/ClientMessageReceivedArgs.cs diff --git a/Jung.SimpleWebSocket/Models/EventArguments/ClientUpgradeRequestReceivedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/ClientUpgradeRequestReceivedArgs.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/EventArguments/ClientUpgradeRequestReceivedArgs.cs rename to src/Jung.SimpleWebSocket/Models/EventArguments/ClientUpgradeRequestReceivedArgs.cs diff --git a/Jung.SimpleWebSocket/Models/EventArguments/DisconnectedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/DisconnectedArgs.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/EventArguments/DisconnectedArgs.cs rename to src/Jung.SimpleWebSocket/Models/EventArguments/DisconnectedArgs.cs diff --git a/Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs rename to src/Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs diff --git a/Jung.SimpleWebSocket/Models/EventArguments/MessageReceivedArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/MessageReceivedArgs.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/EventArguments/MessageReceivedArgs.cs rename to src/Jung.SimpleWebSocket/Models/EventArguments/MessageReceivedArgs.cs diff --git a/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs b/src/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs rename to src/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs diff --git a/Jung.SimpleWebSocket/Models/WebContext.cs b/src/Jung.SimpleWebSocket/Models/WebContext.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/WebContext.cs rename to src/Jung.SimpleWebSocket/Models/WebContext.cs diff --git a/Jung.SimpleWebSocket/Models/WebSocketServerClient.cs b/src/Jung.SimpleWebSocket/Models/WebSocketServerClient.cs similarity index 100% rename from Jung.SimpleWebSocket/Models/WebSocketServerClient.cs rename to src/Jung.SimpleWebSocket/Models/WebSocketServerClient.cs diff --git a/Jung.SimpleWebSocket/SimpleWebSocketClient.cs b/src/Jung.SimpleWebSocket/SimpleWebSocketClient.cs similarity index 100% rename from Jung.SimpleWebSocket/SimpleWebSocketClient.cs rename to src/Jung.SimpleWebSocket/SimpleWebSocketClient.cs diff --git a/Jung.SimpleWebSocket/SimpleWebSocketServer.cs b/src/Jung.SimpleWebSocket/SimpleWebSocketServer.cs similarity index 100% rename from Jung.SimpleWebSocket/SimpleWebSocketServer.cs rename to src/Jung.SimpleWebSocket/SimpleWebSocketServer.cs diff --git a/Jung.SimpleWebSocket/Utility/AsyncEventRaiser.cs b/src/Jung.SimpleWebSocket/Utility/AsyncEventRaiser.cs similarity index 100% rename from Jung.SimpleWebSocket/Utility/AsyncEventRaiser.cs rename to src/Jung.SimpleWebSocket/Utility/AsyncEventRaiser.cs diff --git a/Jung.SimpleWebSocket/Wrappers/NetworkStreamWrapper.cs b/src/Jung.SimpleWebSocket/Wrappers/NetworkStreamWrapper.cs similarity index 100% rename from Jung.SimpleWebSocket/Wrappers/NetworkStreamWrapper.cs rename to src/Jung.SimpleWebSocket/Wrappers/NetworkStreamWrapper.cs diff --git a/Jung.SimpleWebSocket/Wrappers/TcpClientWrapper.cs b/src/Jung.SimpleWebSocket/Wrappers/TcpClientWrapper.cs similarity index 100% rename from Jung.SimpleWebSocket/Wrappers/TcpClientWrapper.cs rename to src/Jung.SimpleWebSocket/Wrappers/TcpClientWrapper.cs diff --git a/Jung.SimpleWebSocket/Wrappers/TcpListenerWrapper.cs b/src/Jung.SimpleWebSocket/Wrappers/TcpListenerWrapper.cs similarity index 100% rename from Jung.SimpleWebSocket/Wrappers/TcpListenerWrapper.cs rename to src/Jung.SimpleWebSocket/Wrappers/TcpListenerWrapper.cs diff --git a/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs b/src/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs similarity index 100% rename from Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs rename to src/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs diff --git a/Jung.SimpleWebSocket/Wrappers/WebSocketWrapper.cs b/src/Jung.SimpleWebSocket/Wrappers/WebSocketWrapper.cs similarity index 100% rename from Jung.SimpleWebSocket/Wrappers/WebSocketWrapper.cs rename to src/Jung.SimpleWebSocket/Wrappers/WebSocketWrapper.cs diff --git a/Jung.SimpleWebSocket/docs/README.md b/src/Jung.SimpleWebSocket/docs/README.md similarity index 100% rename from Jung.SimpleWebSocket/docs/README.md rename to src/Jung.SimpleWebSocket/docs/README.md diff --git a/Jung.SimpleWebSocketTest/Jung.SimpleWebSocketTest.csproj b/tests/Jung.SimpleWebSocket.UnitTests/Jung.SimpleWebSocket.UnitTests.csproj similarity index 88% rename from Jung.SimpleWebSocketTest/Jung.SimpleWebSocketTest.csproj rename to tests/Jung.SimpleWebSocket.UnitTests/Jung.SimpleWebSocket.UnitTests.csproj index 90bd5a1..cfa0430 100644 --- a/Jung.SimpleWebSocketTest/Jung.SimpleWebSocketTest.csproj +++ b/tests/Jung.SimpleWebSocket.UnitTests/Jung.SimpleWebSocket.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/Jung.SimpleWebSocketTest/Mock/ILoggerMockHelper.cs b/tests/Jung.SimpleWebSocket.UnitTests/Mock/ILoggerMockHelper.cs similarity index 87% rename from Jung.SimpleWebSocketTest/Mock/ILoggerMockHelper.cs rename to tests/Jung.SimpleWebSocket.UnitTests/Mock/ILoggerMockHelper.cs index e72d2fa..b784ee1 100644 --- a/Jung.SimpleWebSocketTest/Mock/ILoggerMockHelper.cs +++ b/tests/Jung.SimpleWebSocket.UnitTests/Mock/ILoggerMockHelper.cs @@ -1,7 +1,10 @@ -using Microsoft.Extensions.Logging; +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +using Microsoft.Extensions.Logging; using Moq; -namespace Jung.SimpleWebSocketTest.Mock +namespace Jung.SimpleWebSocket.UnitTests.Mock { internal class ILoggerMockHelper where T : class { diff --git a/Jung.SimpleWebSocketTest/Mock/LoggerMessages.cs b/tests/Jung.SimpleWebSocket.UnitTests/Mock/LoggerMessages.cs similarity index 76% rename from Jung.SimpleWebSocketTest/Mock/LoggerMessages.cs rename to tests/Jung.SimpleWebSocket.UnitTests/Mock/LoggerMessages.cs index b5cc9b3..86af14e 100644 --- a/Jung.SimpleWebSocketTest/Mock/LoggerMessages.cs +++ b/tests/Jung.SimpleWebSocket.UnitTests/Mock/LoggerMessages.cs @@ -1,5 +1,7 @@ - -namespace Jung.SimpleWebSocketTest.Mock +// This file is part of the Jung SimpleWebSocket project. +// The project is licensed under the MIT license. + +namespace Jung.SimpleWebSocket.UnitTests.Mock { internal static class LoggerMessages { diff --git a/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs b/tests/Jung.SimpleWebSocket.UnitTests/SimpleWebSocketTest.cs similarity index 98% rename from Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs rename to tests/Jung.SimpleWebSocket.UnitTests/SimpleWebSocketTest.cs index 8f9bcb3..2621a9f 100644 --- a/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs +++ b/tests/Jung.SimpleWebSocket.UnitTests/SimpleWebSocketTest.cs @@ -1,19 +1,18 @@ // This file is part of the Jung SimpleWebSocket project. // The project is licensed under the MIT license. -using Jung.SimpleWebSocket; using Jung.SimpleWebSocket.Exceptions; using Jung.SimpleWebSocket.Models; -using Jung.SimpleWebSocketTest.Mock; +using Jung.SimpleWebSocket.UnitTests.Mock; using NUnit.Framework; using System.Diagnostics; using System.Net; using System.Runtime.CompilerServices; -// internals of the simple web socket are visible to the test project +// internals of the simple web socket project are visible to the test project // because of the InternalsVisibleTo attribute in the AssemblyInfo.cs -namespace Jung.SimpleWebSocketTest +namespace Jung.SimpleWebSocket.UnitTests { [TestFixture] public class SimpleWebSocketTest diff --git a/Jung.SimpleWebSocketTest/WebContextTest.cs b/tests/Jung.SimpleWebSocket.UnitTests/WebContextTest.cs similarity index 97% rename from Jung.SimpleWebSocketTest/WebContextTest.cs rename to tests/Jung.SimpleWebSocket.UnitTests/WebContextTest.cs index 7999dd7..cc3ad0e 100644 --- a/Jung.SimpleWebSocketTest/WebContextTest.cs +++ b/tests/Jung.SimpleWebSocket.UnitTests/WebContextTest.cs @@ -5,7 +5,10 @@ using Jung.SimpleWebSocket.Models; using NUnit.Framework; -namespace Jung.SimpleWebSocketTest +// internals of the simple web socket project are visible to the test project +// because of the InternalsVisibleTo attribute in the AssemblyInfo.cs + +namespace Jung.SimpleWebSocket.UnitTests { [TestFixture] internal class WebContextTest diff --git a/Jung.SimpleWebSocketTest/WebSocketUpgradeHandlerTests.cs b/tests/Jung.SimpleWebSocket.UnitTests/WebSocketUpgradeHandlerTests.cs similarity index 98% rename from Jung.SimpleWebSocketTest/WebSocketUpgradeHandlerTests.cs rename to tests/Jung.SimpleWebSocket.UnitTests/WebSocketUpgradeHandlerTests.cs index 158ce22..503df3e 100644 --- a/Jung.SimpleWebSocketTest/WebSocketUpgradeHandlerTests.cs +++ b/tests/Jung.SimpleWebSocket.UnitTests/WebSocketUpgradeHandlerTests.cs @@ -1,7 +1,6 @@ // This file is part of the Jung SimpleWebSocket project. // The project is licensed under the MIT license. -using Jung.SimpleWebSocket; using Jung.SimpleWebSocket.Contracts; using Jung.SimpleWebSocket.Exceptions; using Jung.SimpleWebSocket.Helpers; @@ -10,7 +9,10 @@ using NUnit.Framework; using System.Text; -namespace Jung.SimpleWebSocketTest +// internals of the simple web socket project are visible to the test project +// because of the InternalsVisibleTo attribute in the AssemblyInfo.cs + +namespace Jung.SimpleWebSocket.UnitTests { public class WebSocketUpgradeHandlerTests { From 1e74d9db36cfea2a26657e090ff17a54dc2250f0 Mon Sep 17 00:00:00 2001 From: Christoph Jung Date: Tue, 11 Feb 2025 20:20:41 +0100 Subject: [PATCH 6/7] Improve method summary --- src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs b/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs index 5519676..58772f3 100644 --- a/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs +++ b/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs @@ -141,7 +141,7 @@ private static void ThrowForResponseContextNotInitialized([NotNull] WebContext? } /// - /// Disposes the upgrade handler. + /// Releases resources that are no longer required. /// private void Cleanup() { From 26614bf5827e0da90b998d15e4cc7d4cb4a46297 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 11 Feb 2025 22:24:14 +0100 Subject: [PATCH 7/7] Remove obsolete project and file --- Jung.SimpleWebSocket.sln | 6 ------ .../Models/EventArguments/ItemExpiredArgs.cs | 10 ---------- 2 files changed, 16 deletions(-) delete mode 100644 src/Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs diff --git a/Jung.SimpleWebSocket.sln b/Jung.SimpleWebSocket.sln index b5af771..d6ee15b 100644 --- a/Jung.SimpleWebSocket.sln +++ b/Jung.SimpleWebSocket.sln @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jung.SimpleWebSocket", "src EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jung.SimpleWebSocket.UnitTests", "tests\Jung.SimpleWebSocket.UnitTests\Jung.SimpleWebSocket.UnitTests.csproj", "{26725C3C-8E90-49AC-9EE4-2A77ADB2229D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jung.SimpleWebSocket.IntegrationTests", "tests\Jung.SimpleWebSocket.IntegrationTests\Jung.SimpleWebSocket.IntegrationTests.csproj", "{144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,10 +21,6 @@ Global {26725C3C-8E90-49AC-9EE4-2A77ADB2229D}.Debug|Any CPU.Build.0 = Debug|Any CPU {26725C3C-8E90-49AC-9EE4-2A77ADB2229D}.Release|Any CPU.ActiveCfg = Release|Any CPU {26725C3C-8E90-49AC-9EE4-2A77ADB2229D}.Release|Any CPU.Build.0 = Release|Any CPU - {144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {144CF5BC-D92F-4BC4-80E9-A2FABA93C8A2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs b/src/Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs deleted file mode 100644 index f2b78be..0000000 --- a/src/Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs +++ /dev/null @@ -1,10 +0,0 @@ -// This file is part of the Jung SimpleWebSocket project. -// The project is licensed under the MIT license. - -namespace Jung.SimpleWebSocket.Models.EventArguments; - -/// -/// Represents the arguments of the event when an item is expired. -/// -/// The item that is expired. -public record ItemExpiredArgs(TValue Item); \ No newline at end of file