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