Implemented websocket protocol and basic echo websocket module.

This commit is contained in:
2019-07-24 14:02:54 +03:00
parent 5d52c253aa
commit b2ea55e949
7 changed files with 342 additions and 9 deletions
+2 -1
View File
@@ -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();
}
@@ -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<byte> 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
}
}
@@ -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
{
/// <summary>
/// Handle a received message.
/// </summary>
/// <param name="handler">Handler that currently processes the message.</param>
/// <param name="messageBytes">Bytes of the message.</param>
/// <param name="client">Client data.</param>
/// <returns>True if no other module should process the message.</returns>
bool HandleReceivedMessage(IHandler handler, ClientData client, byte[] messageBytes);
}
}
+4
View File
@@ -15,4 +15,8 @@
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<Folder Include="Common\WebSockets\ClientModules\" />
</ItemGroup>
</Project>
+130
View File
@@ -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<ClientData, SocketState> webSockets = new ConcurrentDictionary<ClientData, SocketState>();
ConcurrentQueue<Tuple<ClientData, byte[]>> messageQueue = new ConcurrentQueue<Tuple<ClientData, byte[]>>();
List<IWebsocketModule> websocketModules = new List<IWebsocketModule>();
#region Public Methods
/// <summary>
/// Add a webSocket module onto the server.
/// </summary>
/// <param name="module">Module to be added.</param>
/// <returns>This handler object.</returns>
public WebsocketHandler AddWebsocketHandler(IWebsocketModule module)
{
this.websocketModules.Add(module);
return this;
}
/// <summary>
/// Send a message to the client.
/// </summary>
/// <param name="message">Message to be sent to client.</param>
public void QueueMessage(ClientData client, byte[] message)
{
messageQueue.Enqueue(new Tuple<ClientData, byte[]>(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<ClientData, byte[]> tuple = null;
if (messageQueue.TryDequeue(out tuple))
{
server.QueueMessage(tuple.Item1, tuple.Item2);
}
}
}
#endregion
}
}
+12 -8
View File
@@ -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<IHandler> handlers = new List<IHandler>();
List<ILogger> loggers = new List<ILogger>();
List<IExceptionHandler> exceptionHandlers = new List<IExceptionHandler>();
Queue<Tuple<ClientData, byte[]>> messageQueue = new Queue<Tuple<ClientData, byte[]>>();
ConcurrentQueue<Tuple<ClientData, byte[]>> messageQueue = new ConcurrentQueue<Tuple<ClientData, byte[]>>();
int loopMillis = 16;
#endregion
#region Properties
@@ -325,15 +326,18 @@ namespace MTSC.Server
{
while (messageQueue.Count > 0)
{
Tuple<ClientData, byte[]> queuedOrder = messageQueue.Dequeue();
Message sendMessage = CommunicationPrimitives.BuildMessage(queuedOrder.Item2);
for (int i = handlers.Count - 1; i >= 0; i--)
Tuple<ClientData, byte[]> 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)
+67
View File
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var wsUri = "ws://localhost:80";
var output;
function init()
{
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket()
{
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt)
{
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
}
function onClose(evt)
{
writeToScreen("DISCONNECTED");
}
function onMessage(evt)
{
writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
websocket.close();
}
function onError(evt)
{
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message)
{
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message)
{
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
<h2>WebSocket Test</h2>
<div id="output"></div>