diff --git a/packages/repositories.config b/packages/repositories.config index 5619e18..258e42c 100644 --- a/packages/repositories.config +++ b/packages/repositories.config @@ -1,5 +1,7 @@  + + \ No newline at end of file diff --git a/src/Alchemy/Alchemy.csproj b/src/Alchemy/Alchemy.csproj index 1fbc435..583ee79 100644 --- a/src/Alchemy/Alchemy.csproj +++ b/src/Alchemy/Alchemy.csproj @@ -71,6 +71,7 @@ + @@ -80,7 +81,7 @@ - + diff --git a/src/Alchemy/Classes/ServerBehaviour.cs b/src/Alchemy/Classes/ServerBehaviour.cs new file mode 100644 index 0000000..fd1fdbf --- /dev/null +++ b/src/Alchemy/Classes/ServerBehaviour.cs @@ -0,0 +1,36 @@ +namespace Alchemy.Classes +{ + /// + /// The server behaviour base class. + /// + public abstract class ServerBehaviour + { + /// + /// The event triggered when the user is connected. + /// + /// The current user context. + public virtual void OnConnected(UserContext context) + { } + + /// + /// The event triggered when the server receive data. + /// + /// The current user context. + public virtual void OnReceive(UserContext context) + { } + + /// + /// The event triggered when the server send data. + /// + /// The current user context. + public virtual void OnSend(UserContext context) + { } + + /// + /// The event triggered when the user is disconnected. + /// + /// The current user context. + public virtual void OnDisconnect(UserContext context) + { } + } +} \ No newline at end of file diff --git a/src/Alchemy/WebSocketClient.cs b/src/Alchemy/WebSocketClient.cs index 2472078..63cad57 100644 --- a/src/Alchemy/WebSocketClient.cs +++ b/src/Alchemy/WebSocketClient.cs @@ -60,7 +60,8 @@ static WebSocketClient() NewClients = new Queue(); ContextMapping = new Dictionary(); - for(int i = 0; i < ClientThreads.Length; i++){ + for (int i = 0; i < ClientThreads.Length; i++) + { ClientThreads[i] = new Thread(HandleClientThread); ClientThreads[i].Start(); } @@ -97,9 +98,12 @@ private static void HandleClientThread() } public WebSocketClient(string path) { - var r = new Regex("^(wss?)://(.*)\\:([0-9]*)/(.*)$"); + var r = new Regex("^(\\w+)://(.*)\\:([0-9]*)/(.*)$"); var matches = r.Match(path); + if (matches.Groups[1].Value != "ws" || matches.Groups[1].Value != "wss") + SubProtocols = new string[] {matches.Groups[1].Value}; + _host = matches.Groups[2].Value; _port = Int32.Parse(matches.Groups[3].Value); _path = matches.Groups[4].Value; @@ -108,7 +112,7 @@ public WebSocketClient(string path) public void Connect() { if (_client != null) return; - + try { ReadyState = ReadyStates.CONNECTING; @@ -142,7 +146,7 @@ protected void OnRunClient(IAsyncResult result) { _client.EndConnect(result); } - catch (Exception ex) + catch (Exception) { Disconnect(); connectError = true; @@ -192,7 +196,7 @@ private void SetupContext(Context context) { ReceiveEventArgs_Completed(_context.Connection.Client, _context.ReceiveEventArgs); } - + if (!IsAuthenticated) { @@ -236,7 +240,7 @@ void ReceiveEventArgs_Completed(object sender, SocketAsyncEventArgs e) private void Authenticate() { - _handshake = new ClientHandshake { Version = "8", Origin = Origin, Host = _host, Key = GenerateKey(), ResourcePath = _path, SubProtocols = SubProtocols}; + _handshake = new ClientHandshake { Version = "8", Origin = Origin, Host = _host, Key = GenerateKey(), ResourcePath = _path, SubProtocols = SubProtocols }; _client.Client.Send(Encoding.UTF8.GetBytes(_handshake.ToString())); } @@ -264,7 +268,7 @@ private bool CheckAuthenticationResponse(Context context) } } - if(String.IsNullOrEmpty(CurrentProtocol)) + if (String.IsNullOrEmpty(CurrentProtocol)) { return false; } @@ -305,7 +309,7 @@ private void ReceiveData(Context context) private void DoReceive(IAsyncResult result) { - var context = (Context) result.AsyncState; + var context = (Context)result.AsyncState; context.Reset(); try @@ -335,7 +339,7 @@ private static String GenerateKey() for (var index = 0; index < bytes.Length; index++) { - bytes[index] = (byte) random.Next(0, 255); + bytes[index] = (byte)random.Next(0, 255); } return Convert.ToBase64String(bytes); @@ -369,12 +373,12 @@ public void Send(byte[] data) { _context.UserContext.Send(data); } - + public void Dispose() { cancellation.Cancel(); Handler.Instance.Dispose(); } - + } -} +} diff --git a/src/Alchemy/WebSocketServer.cs b/src/Alchemy/WebSocketServer.cs index 3f9a61e..eedb42a 100644 --- a/src/Alchemy/WebSocketServer.cs +++ b/src/Alchemy/WebSocketServer.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Net; +using System.Text.RegularExpressions; using System.Net.Sockets; using System.Threading; using Alchemy.Classes; @@ -37,7 +39,8 @@ static WebSocketServer() CleanupThread.Name = "WebSocketServer Cleanup Thread"; CleanupThread.Start(); - for(int i = 0; i < ClientThreads.Length; i++){ + for (int i = 0; i < ClientThreads.Length; i++) + { ClientThreads[i] = new Thread(HandleClientThread); ClientThreads[i].Name = "WebSocketServer Client Thread #" + (i + 1); ClientThreads[i].Start(); @@ -67,7 +70,8 @@ private static void HandleClientThread() client.SetupContext(context); } - lock(CurrentConnections){ + lock (CurrentConnections) + { CurrentConnections.Add(context); } } @@ -89,7 +93,7 @@ private static void HandleContextCleanupThread() foreach (var connection in currentConnections) { if (cancellation.IsCancellationRequested) break; - + if (!connection.Connected) { lock (CurrentConnections) @@ -118,15 +122,21 @@ private static void HandleContextCleanupThread() /// /// These are the default OnEvent delegates for the server. By default, all new UserContexts will use these events. - /// It is up to you whether you want to replace them at runtime or even manually set the events differently per connection in OnReceive. /// public OnEventDelegate OnConnect = x => { }; - public OnEventDelegate OnConnected = x => { }; public OnEventDelegate OnDisconnect = x => { }; public OnEventDelegate OnReceive = x => { }; public OnEventDelegate OnSend = x => { }; + /// + /// These are events passed to the context to manage server behaviours. + /// + private OnEventDelegate _onConnected = x => { }; + private OnEventDelegate _onDisconnect = x => { }; + private OnEventDelegate _onReceive = x => { }; + private OnEventDelegate _onSend = x => { }; + /// /// Enables or disables the Flash Access Policy Server(APServer). /// This is used when you would like your app to only listen on a single port rather than 2. @@ -150,10 +160,65 @@ private static void HandleContextCleanupThread() private string _destination = String.Empty; private string _origin = String.Empty; + private Dictionary _behaviours = new Dictionary(); + /// /// Initializes a new instance of the class. /// - public WebSocketServer(int listenPort = 0, IPAddress listenAddress = null) : base(listenPort, listenAddress) {} + public WebSocketServer(int listenPort = 0, IPAddress listenAddress = null) : base(listenPort, listenAddress) + { + _onConnected = (context) => + { + OnConnected(context); + var keys = _behaviours.Keys.Where(k => Regex.IsMatch(context.RequestPath, $"^{k}$")); + if (keys.Count() > 0) + { + foreach (var k in keys) + { + _behaviours[k].OnConnected(context); + } + } + }; + + _onReceive = (context) => + { + OnReceive(context); + var keys = _behaviours.Keys.Where(k => Regex.IsMatch(context.RequestPath, $"^{k}$")); + if (keys.Count() > 0) + { + foreach (var k in keys) + { + _behaviours[k].OnReceive(context); + } + } + }; + + _onSend = (context) => + { + OnSend(context); + var keys = _behaviours.Keys.Where(k => Regex.IsMatch(context.RequestPath, $"^{k}$")); + if (keys.Count() > 0) + { + foreach (var k in keys) + { + _behaviours[k].OnSend(context); + } + } + }; + + _onDisconnect = (context) => + { + OnDisconnect(context); + var keys = _behaviours.Keys.Where(k => Regex.IsMatch(context.RequestPath, $"^{k}$")); + if (keys.Count() > 0) + { + foreach (var k in keys) + { + _behaviours[k].OnDisconnect(context); + } + } + }; + } /// /// Gets or sets the origin host. @@ -187,6 +252,21 @@ public string Destination } } + /// + /// Add a new server behaviour. + /// + /// The request path to apply the behaviour. Can be a . + /// The to use when the user connect using the + public void AddServerBehaviour(string path, ServerBehaviour behaviour) + { + string key = path.TrimEnd('/'); + + if (!_behaviours.ContainsKey(key)) + { + _behaviours.Add(path, behaviour); + } + } + /// /// Starts this instance. /// @@ -228,10 +308,10 @@ protected override void OnRunClient(object data) context.UserContext.ClientAddress = context.Connection.Client.RemoteEndPoint; context.UserContext.SetOnConnect(OnConnect); - context.UserContext.SetOnConnected(OnConnected); - context.UserContext.SetOnDisconnect(OnDisconnect); - context.UserContext.SetOnSend(OnSend); - context.UserContext.SetOnReceive(OnReceive); + context.UserContext.SetOnConnected(_onConnected); + context.UserContext.SetOnDisconnect(_onDisconnect); + context.UserContext.SetOnSend(_onSend); + context.UserContext.SetOnReceive(_onReceive); context.BufferSize = BufferSize; context.UserContext.OnConnect(); @@ -252,7 +332,7 @@ protected override void OnRunClient(object data) /// The Async result. private void DoReceive(IAsyncResult result) { - var context = (Context) result.AsyncState; + var context = (Context)result.AsyncState; context.Reset(); try { @@ -274,6 +354,7 @@ private void DoReceive(IAsyncResult result) context.ReceiveReady.Release(); } } + private void SetupContext(Context _context) { _context.ReceiveEventArgs.UserToken = _context; @@ -282,42 +363,49 @@ private void SetupContext(Context _context) StartReceive(_context); } + private void StartReceive(Context _context) { - try + if (_context.Connected) { - if (_context.ReceiveReady.Wait(TimeOut, cancellation.Token)) + try { - try + if (_context.ReceiveReady.Wait(TimeOut, cancellation.Token)) { - if (!_context.Connection.Client.ReceiveAsync(_context.ReceiveEventArgs)) + try + { + if (!_context.Connection.Client.ReceiveAsync(_context.ReceiveEventArgs)) + { + ReceiveEventArgs_Completed(_context.Connection.Client, _context.ReceiveEventArgs); + } + } + catch (SocketException) { - ReceiveEventArgs_Completed(_context.Connection.Client, _context.ReceiveEventArgs); + //logger.Error("SocketException in ReceieveAsync", ex); + _context.Disconnect(); } } - catch (SocketException ex) + else { - //logger.Error("SocketException in ReceieveAsync", ex); + //logger.Error("Timeout waiting for ReceiveReady"); _context.Disconnect(); } } - else - { - //logger.Error("Timeout waiting for ReceiveReady"); - _context.Disconnect(); - } + catch (OperationCanceledException) { } } - catch (OperationCanceledException) { } } + void ReceiveEventArgs_Completed(object sender, SocketAsyncEventArgs e) { var context = (Context)e.UserToken; context.Reset(); if (e.SocketError != SocketError.Success) { - //logger.Error("Socket Error: " + e.SocketError.ToString()); + //logger.Error("Socket Error: " + e.SocketError.ToString()); context.ReceivedByteCount = 0; - } else { + } + else + { context.ReceivedByteCount = e.BytesTransferred; } @@ -326,17 +414,19 @@ void ReceiveEventArgs_Completed(object sender, SocketAsyncEventArgs e) context.Handler.HandleRequest(context); context.ReceiveReady.Release(); StartReceive(context); - } else { + } + else + { context.Disconnect(); context.ReceiveReady.Release(); } } - - public void Dispose() + + public new void Dispose() { cancellation.Cancel(); base.Dispose(); Handler.Instance.Dispose(); - } + } } } diff --git a/test/Integration/Alchemy/Alchemy.csproj b/test/Integration/Alchemy/Alchemy.csproj index a57c2e2..b1d0800 100644 --- a/test/Integration/Alchemy/Alchemy.csproj +++ b/test/Integration/Alchemy/Alchemy.csproj @@ -53,12 +53,6 @@ - - - {45486CDE-86A3-4769-952F-E0821BF79493} - Alchemy %28src\Alchemy%29 - - @@ -70,4 +64,10 @@ --> + + + {D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D} + Alchemy + + \ No newline at end of file diff --git a/test/Unit/Alchemy/Alchemy.csproj b/test/Unit/Alchemy/Alchemy.csproj index 4a475d6..119ca61 100644 --- a/test/Unit/Alchemy/Alchemy.csproj +++ b/test/Unit/Alchemy/Alchemy.csproj @@ -40,6 +40,8 @@ true true false + 4 + false bin\x64\Release\ @@ -50,18 +52,13 @@ prompt false false + 4 - - - {45486CDE-86A3-4769-952F-E0821BF79493} - Alchemy %28src\Alchemy%29 - - ..\..\..\packages\NUnit.2.5.10.11092\lib\nunit.framework.dll @@ -84,4 +81,10 @@ --> + + + {D7B6AB15-5986-4FDC-ADA8-9EF14DF8F26D} + Alchemy + + \ No newline at end of file