diff --git a/MTSC-TestServer/Program.cs b/MTSC-TestServer/Program.cs index 35ebdd5..5e98665 100644 --- a/MTSC-TestServer/Program.cs +++ b/MTSC-TestServer/Program.cs @@ -1,4 +1,5 @@ using MTSC.Common.Http.ServerModules; +using MTSC.Common.WebSockets.ServerModules; using MTSC.Exceptions; using MTSC.Logging; using MTSC.Server; @@ -26,7 +27,7 @@ namespace MTSC_TestServer .AddLogger(new DebugConsoleLogger()) .AddExceptionHandler(new ExceptionConsoleLogger()) //.AddHandler(new BroadcastHandler()) - .AddHandler(new HttpHandler().AddHttpModule(new HelloWorldModule())) + .AddHandler(new WebsocketHandler().AddWebsocketHandler(new EchoModule())) .Run(); } diff --git a/MTSC/Common/WebSockets/ServerModules/EchoModule.cs b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs new file mode 100644 index 0000000..7c5b792 --- /dev/null +++ b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MTSC.Server; +using MTSC.Server.Handlers; + +namespace MTSC.Common.WebSockets.ServerModules +{ + public class EchoModule : IWebsocketModule + { + #region Public Methods + public void SendMessage(WebsocketHandler handler, ClientData client, string message) + { + byte[] encodedMessage = EncodeMessage(message); + handler.QueueMessage(client, encodedMessage); + } + #endregion + #region Private Methods + private string DecodeMessage(byte[] bytes) + { + byte secondByte = bytes[1]; + int dataLength = secondByte & 127; + int indexFirstMask = 2; + if (dataLength == 126) + indexFirstMask = 4; + else if (dataLength == 127) + indexFirstMask = 10; + + IEnumerable keys = bytes.Skip(indexFirstMask).Take(4); + int indexFirstDataByte = indexFirstMask + 4; + + byte[] decoded = new byte[bytes.Length - indexFirstDataByte]; + for (int i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++) + { + decoded[j] = (byte)(bytes[i] ^ keys.ElementAt(j % 4)); + } + + return Encoding.UTF8.GetString(decoded, 0, decoded.Length); + } + private static byte[] EncodeMessage(string message) + { + byte[] response; + byte[] bytesRaw = Encoding.UTF8.GetBytes(message); + byte[] frame = new byte[10]; + + int indexStartRawData = -1; + int length = bytesRaw.Length; + + frame[0] = (byte)129; + if (length <= 125) + { + frame[1] = (byte)length; + indexStartRawData = 2; + } + else if (length >= 126 && length <= 65535) + { + frame[1] = (byte)126; + frame[2] = (byte)((length >> 8) & 255); + frame[3] = (byte)(length & 255); + indexStartRawData = 4; + } + else + { + frame[1] = (byte)127; + frame[2] = (byte)((length >> 56) & 255); + frame[3] = (byte)((length >> 48) & 255); + frame[4] = (byte)((length >> 40) & 255); + frame[5] = (byte)((length >> 32) & 255); + frame[6] = (byte)((length >> 24) & 255); + frame[7] = (byte)((length >> 16) & 255); + frame[8] = (byte)((length >> 8) & 255); + frame[9] = (byte)(length & 255); + + indexStartRawData = 10; + } + + response = new byte[indexStartRawData + length]; + + int i, reponseIdx = 0; + + //Add the frame bytes to the reponse + for (i = 0; i < indexStartRawData; i++) + { + response[reponseIdx] = frame[i]; + reponseIdx++; + } + + //Add the data bytes to the response + for (i = 0; i < length; i++) + { + response[reponseIdx] = bytesRaw[i]; + reponseIdx++; + } + return response; + } + #endregion + #region Interface Implementation + bool IWebsocketModule.HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes) + { + string receivedMessage = DecodeMessage(messageBytes); + SendMessage((WebsocketHandler)handler, client, receivedMessage); + return false; + } + #endregion + } +} diff --git a/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs b/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs new file mode 100644 index 0000000..179615f --- /dev/null +++ b/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs @@ -0,0 +1,20 @@ +using MTSC.Server; +using MTSC.Server.Handlers; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MTSC.Common.WebSockets.ServerModules +{ + public interface IWebsocketModule + { + /// + /// Handle a received message. + /// + /// Handler that currently processes the message. + /// Bytes of the message. + /// Client data. + /// True if no other module should process the message. + bool HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes); + } +} diff --git a/MTSC/MTSC.csproj b/MTSC/MTSC.csproj index de0a40e..161319f 100644 --- a/MTSC/MTSC.csproj +++ b/MTSC/MTSC.csproj @@ -15,4 +15,8 @@ AnyCPU;x64 + + + + diff --git a/MTSC/Server/Handlers/WebsocketHandler.cs b/MTSC/Server/Handlers/WebsocketHandler.cs new file mode 100644 index 0000000..0b7940e --- /dev/null +++ b/MTSC/Server/Handlers/WebsocketHandler.cs @@ -0,0 +1,130 @@ +using MTSC.Common.Http; +using MTSC.Common.WebSockets.ServerModules; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace MTSC.Server.Handlers +{ + public class WebsocketHandler : IHandler + { + private static string WebsocketHeaderAcceptKey = "Sec-WebSocket-Accept"; + private static string WebsocketHeaderKey = "Sec-WebSocket-Key"; + private static string WebsocketProtocolKey = "Sec-WebSocket-Protocol"; + private static string WebsocketProtocolVersionKey = "Sec-WebSocket-Version"; + private static string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + private static SHA1 sha1Provider = SHA1.Create(); + private enum SocketState + { + Initial, + Handshaking, + Established, + Closed + } + ConcurrentDictionary webSockets = new ConcurrentDictionary(); + ConcurrentQueue> messageQueue = new ConcurrentQueue>(); + List websocketModules = new List(); + + #region Public Methods + /// + /// Add a webSocket module onto the server. + /// + /// Module to be added. + /// This handler object. + public WebsocketHandler AddWebsocketHandler(IWebsocketModule module) + { + this.websocketModules.Add(module); + return this; + } + /// + /// Send a message to the client. + /// + /// Message to be sent to client. + public void QueueMessage(ClientData client, byte[] message) + { + messageQueue.Enqueue(new Tuple(client, message)); + } + #endregion + #region Handler Implementation + void IHandler.ClientRemoved(Server server, ClientData client) + { + SocketState state = SocketState.Initial; + webSockets.TryRemove(client, out state); + } + + bool IHandler.HandleClient(Server server, ClientData client) + { + webSockets[client] = SocketState.Initial; + return false; + } + + bool IHandler.HandleReceivedMessage(Server server, ClientData client, Message message) + { + if (webSockets[client] == SocketState.Initial) + { + HttpMessage request = new HttpMessage(); + request.ParseRequest(message.MessageBytes); + if(request.Method == HttpMessage.MethodEnum.Get && + request[HttpMessage.GeneralHeadersEnum.Connection].ToLower() == "upgrade" && + request[WebsocketProtocolVersionKey] == "13") + { + /* + * Prepare the handshake string. + */ + string base64Key = request[WebsocketHeaderKey]; + base64Key = base64Key.Trim(); + string handshakeKey = base64Key + GlobalUniqueIdentifier; + string returnBase64Key = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey))); + + /* + * Prepare the response. + */ + HttpMessage response = new HttpMessage(); + response.StatusCode = HttpMessage.StatusCodes.SwitchingProtocols; + response[HttpMessage.GeneralHeadersEnum.Upgrade] = "websocket"; + response[HttpMessage.GeneralHeadersEnum.Connection] = "Upgrade"; + response[WebsocketHeaderAcceptKey] = returnBase64Key; + server.QueueMessage(client, response.GetResponse(true)); + webSockets[client] = SocketState.Established; + return true; + } + } + else if(webSockets[client] == SocketState.Established) + { + foreach(IWebsocketModule websocketModule in websocketModules) + { + if(websocketModule.HandleReceivedMessage(this, client, message.MessageBytes)) + { + break; + } + } + } + return false; + } + + bool IHandler.HandleSendMessage(Server server, ClientData client, ref Message message) + { + return false; + } + + bool IHandler.PreHandleReceivedMessage(Server server, ClientData client, ref Message message) + { + return false; + } + + void IHandler.Tick(Server server) + { + while (messageQueue.Count > 0) + { + Tuple tuple = null; + if (messageQueue.TryDequeue(out tuple)) + { + server.QueueMessage(tuple.Item1, tuple.Item2); + } + } + } + #endregion + } +} diff --git a/MTSC/Server/Server.cs b/MTSC/Server/Server.cs index 88909a2..d77d99b 100644 --- a/MTSC/Server/Server.cs +++ b/MTSC/Server/Server.cs @@ -2,6 +2,7 @@ using MTSC.Logging; using MTSC.Server.Handlers; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; using System.Net.Security; @@ -28,7 +29,7 @@ namespace MTSC.Server List handlers = new List(); List loggers = new List(); List exceptionHandlers = new List(); - Queue> messageQueue = new Queue>(); + ConcurrentQueue> messageQueue = new ConcurrentQueue>(); int loopMillis = 16; #endregion #region Properties @@ -325,15 +326,18 @@ namespace MTSC.Server { while (messageQueue.Count > 0) { - Tuple queuedOrder = messageQueue.Dequeue(); - Message sendMessage = CommunicationPrimitives.BuildMessage(queuedOrder.Item2); - for (int i = handlers.Count - 1; i >= 0; i--) + Tuple queuedOrder = null; + if (messageQueue.TryDequeue(out queuedOrder)) { - IHandler handler = handlers[i]; - ClientData client = queuedOrder.Item1; - handler.HandleSendMessage(this, client, ref sendMessage); + Message sendMessage = CommunicationPrimitives.BuildMessage(queuedOrder.Item2); + for (int i = handlers.Count - 1; i >= 0; i--) + { + IHandler handler = handlers[i]; + ClientData client = queuedOrder.Item1; + handler.HandleSendMessage(this, client, ref sendMessage); + } + CommunicationPrimitives.SendMessage(queuedOrder.Item1.TcpClient, sendMessage, queuedOrder.Item1.SslStream); } - CommunicationPrimitives.SendMessage(queuedOrder.Item1.TcpClient, sendMessage, queuedOrder.Item1.SslStream); } } catch (Exception e) diff --git a/index.html b/index.html new file mode 100644 index 0000000..c62ecfd --- /dev/null +++ b/index.html @@ -0,0 +1,67 @@ + + + WebSocket Test + + +

WebSocket Test

+ +
+ \ No newline at end of file