diff --git a/Jung.SimpleWebSocket.sln b/Jung.SimpleWebSocket.sln
index ed5da37..d6ee15b 100644
--- a/Jung.SimpleWebSocket.sln
+++ b/Jung.SimpleWebSocket.sln
@@ -3,9 +3,9 @@ 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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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
deleted file mode 100644
index ad5e3ab..0000000
--- a/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs
+++ /dev/null
@@ -1,237 +0,0 @@
-// This file is part of the Jung SimpleWebSocket project.
-// 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
-{
- ///
- /// A flow that handles the client connection.
- ///
- ///
- /// Creates a new instance of the class.
- ///
- /// The client to handle.
- /// The server that handles the client.
- /// The cancellation token of the server.
- internal class ClientHandlingFlow(SimpleWebSocketServer server, WebSocketServerClient client, CancellationToken cancellationToken)
- {
- ///
- /// Gets the client associated with the flow.
- ///
- internal WebSocketServerClient Client { get; set; } = client;
-
- ///
- /// Gets the request context of the client.
- ///
- internal WebContext Request { get; set; } = null!;
-
- ///
- /// Gets the upgrade handler for the client.
- ///
- private WebSocketUpgradeHandler _upgradeHandler = null!;
-
- ///
- /// Gets the response context that is being use to response to the client.
- ///
- 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.
- ///
- private readonly ILogger? _logger = server.Logger;
-
- ///
- /// Gets the cancellation token of the server.
- ///
- private readonly CancellationToken _cancellationToken = cancellationToken;
-
- ///
- /// The lock object for the client dictionaries.
- ///
- private static readonly object _clientLock = new();
-
- ///
- /// Loads the request context.
- ///
- internal async Task LoadRequestContext()
- {
- var stream = Client.ClientConnection!.GetStream();
- _upgradeHandler = new WebSocketUpgradeHandler(stream);
- 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);
-
- // 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;
- }
-
- ///
- /// Rejects the web socket connection.
- ///
- /// The response context to send to the client.
- internal async Task RejectWebSocketAsync(WebContext responseContext)
- {
- await _upgradeHandler.RejectWebSocketAsync(responseContext, _cancellationToken);
- }
-
- ///
- /// Handles the disconnected client.
- ///
- internal void HandleDisconnectedClient()
- {
- if (_clientWasPassiveClient || _clientAccepted)
- {
- 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);
- }
- }
- }
- }
-
- ///
- /// Raises the upgrade event.
- ///
- /// The event handler for the upgrade request.
- /// The event arguments of the upgrade request.
- internal async Task RaiseUpgradeEventAsync(AsyncEventHandler? clientUpgradeRequestReceivedAsync)
- {
- var eventArgs = new ClientUpgradeRequestReceivedArgs(Client, Request, _logger);
- await AsyncEventRaiser.RaiseAsync(clientUpgradeRequestReceivedAsync, server, eventArgs, _cancellationToken);
- _responseContext = eventArgs.ResponseContext;
- return eventArgs;
- }
-
- ///
- /// Tries to add the client to the active user list.
- ///
- /// True if the client was added to the active user list. False if the client is already connected.
- internal bool TryAddClientToActiveUserList()
- {
- return _activeClients.TryAdd(Client.Id, Client);
- }
- }
-}
\ No newline at end of file
diff --git a/Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs b/Jung.SimpleWebSocket/Models/EventArguments/ItemExpiredArgs.cs
deleted file mode 100644
index f2b78be..0000000
--- a/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
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
deleted file mode 100644
index 7e5a486..0000000
--- a/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System.Net;
-
-namespace Jung.SimpleWebSocket.Models
-{
- ///
- /// Represents the options for the SimpleWebSocketServer.
- ///
- public class SimpleWebSocketServerOptions
- {
- ///
- /// Gets or sets the local IP address of the server.
- ///
- public IPAddress LocalIpAddress { get; set; } = IPAddress.Any;
-
- ///
- /// 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/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.SimpleWebSocketTest/ClientHandlingFlowTest.cs b/Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs
deleted file mode 100644
index 0e8916e..0000000
--- a/Jung.SimpleWebSocketTest/ClientHandlingFlowTest.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-using Jung.SimpleWebSocket;
-using Jung.SimpleWebSocket.Contracts;
-using Jung.SimpleWebSocket.Exceptions;
-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(string? userId = null)
- {
- 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");
-
- if (!string.IsNullOrEmpty(userId))
- {
- sb.Append($"x-user-id: {userId}");
- }
-
- sb.Append("\r\n\r\n");
- return sb.ToString();
- }
-
- [Test]
- public void HandleClientIdentification_NoNewUser_UserIdIsUpdated()
- {
- // setup
- var userId = "6C8D0844-D84F-4AD9-B28D-23B3940887B7";
- var requestText = CreateUpgradeRequest(userId);
- var serverOptions = new SimpleWebSocketServerOptions
- {
- RememberDisconnectedClients = true,
- };
-
- var clientHandlingFlow = SetupClientHandlingFlow(serverOptions);
- clientHandlingFlow.Request = new WebContext(requestText);
-
- // act
- clientHandlingFlow.HandleClientIdentification();
-
- // assert
- Assert.That(clientHandlingFlow.Client.Id, Is.EqualTo(userId));
- }
-
- [Test]
- public void HandleClientIdentification_NoNewUser_UserAlreadyConnected()
- {
- // setup
- var userId = "6C8D0844-D84F-4AD9-B28D-23B3940887B7";
- var activeUsers = new 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.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 93%
rename from Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs
rename to src/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs
index 5bad1b4..1a73240 100644
--- a/Jung.SimpleWebSocket/Contracts/IWebSocketClient.cs
+++ b/src/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/src/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs
similarity index 87%
rename from Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs
rename to src/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs
index 5add6a8..a12093e 100644
--- a/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs
+++ b/src/Jung.SimpleWebSocket/Contracts/IWebSocketServer.cs
@@ -2,9 +2,9 @@
// 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 System.Net;
namespace Jung.SimpleWebSocket.Contracts;
@@ -59,11 +59,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.
///
@@ -98,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/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/src/Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs b/src/Jung.SimpleWebSocket/Exceptions/ClientIdAlreadyExistsException.cs
new file mode 100644
index 0000000..91635d6
--- /dev/null
+++ b/src/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/src/Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs b/src/Jung.SimpleWebSocket/Exceptions/ClientNotFoundException.cs
new file mode 100644
index 0000000..58d4a95
--- /dev/null
+++ b/src/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/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/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs b/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs
new file mode 100644
index 0000000..58772f3
--- /dev/null
+++ b/src/Jung.SimpleWebSocket/Flows/ClientHandlingFlow.cs
@@ -0,0 +1,153 @@
+// This file is part of the Jung SimpleWebSocket project.
+// The project is licensed under the MIT license.
+
+using Jung.SimpleWebSocket.Delegates;
+using Jung.SimpleWebSocket.Models;
+using Jung.SimpleWebSocket.Models.EventArguments;
+using Jung.SimpleWebSocket.Utility;
+using Microsoft.Extensions.Logging;
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Jung.SimpleWebSocket.Flows
+{
+ ///
+ /// A flow that handles the client connection.
+ ///
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The client to handle.
+ /// The server that handles the client.
+ /// The cancellation token of the server.
+ internal class ClientHandlingFlow(SimpleWebSocketServer server, WebSocketServerClient client, CancellationToken cancellationToken)
+ {
+ ///
+ /// Gets the client associated with the flow.
+ ///
+ internal WebSocketServerClient Client { get; set; } = client;
+
+ ///
+ /// Gets the request context of the client.
+ ///
+ internal WebContext? Request { get; set; } = null!;
+
+ ///
+ /// Gets the upgrade handler for the client.
+ ///
+ private WebSocketUpgradeHandler? _upgradeHandler = null;
+
+ ///
+ /// Gets the response context that is being use to response to the client.
+ ///
+ private WebContext? _responseContext = null;
+
+ ///
+ /// Gets the active clients of the server.
+ ///
+ private readonly ConcurrentDictionary _activeClients = server.ActiveClients;
+
+ ///
+ /// Gets the logger of the server.
+ ///
+ private readonly ILogger? _logger = server.Logger;
+
+ ///
+ /// Gets the cancellation token of the server.
+ ///
+ private readonly CancellationToken _cancellationToken = cancellationToken;
+
+ ///
+ /// Loads the request context.
+ ///
+ internal async Task LoadRequestContext()
+ {
+ var stream = Client.ClientConnection!.GetStream();
+ _upgradeHandler = new WebSocketUpgradeHandler(stream);
+ Request = await _upgradeHandler.AwaitContextAsync(_cancellationToken);
+ }
+
+ ///
+ /// Accepts the web socket connection.
+ ///
+ internal async Task AcceptWebSocketAsync()
+ {
+ // Check if the response context are initialized
+ ThrowForResponseContextNotInitialized(_responseContext);
+
+ // The client is accepted
+ await _upgradeHandler!.AcceptWebSocketAsync(Request!, _responseContext, null, _cancellationToken);
+
+ // Use the web socket for the client
+ Client.UseWebSocket(_upgradeHandler.CreateWebSocket(isServer: true));
+ Cleanup();
+ }
+
+ ///
+ /// Rejects the web socket connection.
+ ///
+ /// The response context to send to the client.
+ internal async Task RejectWebSocketAsync(WebContext responseContext)
+ {
+ // The client is rejected
+ await _upgradeHandler!.RejectWebSocketAsync(responseContext, _cancellationToken);
+ Cleanup();
+ }
+
+ ///
+ /// Handles the disconnected client.
+ ///
+ internal void HandleDisconnectedClient()
+ {
+ _activeClients.TryRemove(Client.Id, out _);
+ Client.Dispose();
+
+ _logger?.LogDebug("Client {clientId} is removed.", Client.Id);
+ }
+
+ ///
+ /// Raises the upgrade event.
+ ///
+ /// The event handler for the upgrade request.
+ /// The event arguments of the upgrade request.
+ internal async Task RaiseUpgradeEventAsync(AsyncEventHandler? clientUpgradeRequestReceivedAsync)
+ {
+ var eventArgs = new ClientUpgradeRequestReceivedArgs(Client, Request!, _logger);
+ await AsyncEventRaiser.RaiseAsync(clientUpgradeRequestReceivedAsync, server, eventArgs, _cancellationToken);
+ _responseContext = eventArgs.ResponseContext;
+ return eventArgs;
+ }
+
+ ///
+ /// Tries to add the client to the active user list.
+ ///
+ /// True if the client was added to the active user list. False if the client is already connected.
+ 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.");
+ }
+ }
+
+ ///
+ /// Releases resources that are no longer required.
+ ///
+ private void Cleanup()
+ {
+ _upgradeHandler = null;
+ _responseContext = null;
+ Request = null;
+ }
+ }
+}
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/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/src/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs b/src/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs
new file mode 100644
index 0000000..ea58fde
--- /dev/null
+++ b/src/Jung.SimpleWebSocket/Models/SimpleWebSocketServerOptions.cs
@@ -0,0 +1,23 @@
+// This file is part of the Jung SimpleWebSocket project.
+// The project is licensed under the MIT license.
+
+using System.Net;
+
+namespace Jung.SimpleWebSocket.Models
+{
+ ///
+ /// Represents the options for the SimpleWebSocketServer.
+ ///
+ public class SimpleWebSocketServerOptions
+ {
+ ///
+ /// Gets or sets the local IP address of the server.
+ ///
+ public IPAddress LocalIpAddress { get; set; } = IPAddress.Any;
+
+ ///
+ /// Gets or sets the port of the server.
+ ///
+ public int Port { get; set; }
+ }
+}
diff --git a/Jung.SimpleWebSocket/Models/WebContext.cs b/src/Jung.SimpleWebSocket/Models/WebContext.cs
similarity index 94%
rename from Jung.SimpleWebSocket/Models/WebContext.cs
rename to src/Jung.SimpleWebSocket/Models/WebContext.cs
index 3b7626c..589ab61 100644
--- a/Jung.SimpleWebSocket/Models/WebContext.cs
+++ b/src/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/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 95%
rename from Jung.SimpleWebSocket/SimpleWebSocketClient.cs
rename to src/Jung.SimpleWebSocket/SimpleWebSocketClient.cs
index 2138cd6..6793e3d 100644
--- a/Jung.SimpleWebSocket/SimpleWebSocketClient.cs
+++ b/src/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/src/Jung.SimpleWebSocket/SimpleWebSocketServer.cs
similarity index 80%
rename from Jung.SimpleWebSocket/SimpleWebSocketServer.cs
rename to src/Jung.SimpleWebSocket/SimpleWebSocketServer.cs
index 13d28b9..4b298cf 100644
--- a/Jung.SimpleWebSocket/SimpleWebSocketServer.cs
+++ b/src/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.
@@ -257,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.
///
@@ -271,10 +241,7 @@ 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
+ // Raise async client upgrade request received event
var eventArgs = await flow.RaiseUpgradeEventAsync(ClientUpgradeRequestReceivedAsync);
// Respond to the upgrade request
@@ -282,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);
@@ -291,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
@@ -372,23 +340,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/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 96%
rename from Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs
rename to src/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs
index 416a4aa..5d9f87b 100644
--- a/Jung.SimpleWebSocket/Wrappers/WebSocketUpgradeHandler.cs
+++ b/src/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)
@@ -63,7 +62,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 +78,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;
}
@@ -89,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);
}
}
@@ -296,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)
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 82%
rename from Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs
rename to tests/Jung.SimpleWebSocket.UnitTests/SimpleWebSocketTest.cs
index e86696c..2621a9f 100644
--- a/Jung.SimpleWebSocketTest/SimpleWebSocketTest.cs
+++ b/tests/Jung.SimpleWebSocket.UnitTests/SimpleWebSocketTest.cs
@@ -1,18 +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
@@ -39,7 +39,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.")]
@@ -50,7 +123,6 @@ public async Task TestClientServerConnection_ShouldSendAndReceiveHelloWorld()
{
LocalIpAddress = IPAddress.Any,
Port = 8010,
- RememberDisconnectedClients = true,
};
using var server = new SimpleWebSocketServer(serverOptions, _serverLoggerMockHelper.Logger);
@@ -61,7 +133,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,20 +194,10 @@ 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");
@@ -148,7 +209,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 +225,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/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 96%
rename from Jung.SimpleWebSocketTest/WebSocketUpgradeHandlerTests.cs
rename to tests/Jung.SimpleWebSocket.UnitTests/WebSocketUpgradeHandlerTests.cs
index aa69078..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
{
@@ -64,7 +66,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 +92,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(() =>