Added SendingTemplate to HttpResponse

Implemented WebsocketRouteBase
This commit is contained in:
Alexandru Macocian
2020-03-11 22:36:13 +01:00
parent d659d22d5a
commit fc36acc494
7 changed files with 415 additions and 8 deletions
+12 -3
View File
@@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MTSC.Common.Http;
using MTSC.Common.Http.RoutingModules;
using MTSC.Common.Http.ServerModules;
using MTSC.Common.WebSockets;
using MTSC.Exceptions;
using MTSC.Logging;
using MTSC.ServerSide.Handlers;
@@ -29,8 +30,16 @@ namespace MTSC.UnitTests
public static void InitializeServer(TestContext testContext)
{
Server = new ServerSide.Server(800)
.AddHandler(new WebsocketHandler()
.AddWebsocketHandler(new Common.WebSockets.ServerModules.EchoModule()))
.AddHandler(new WebsocketRoutingHandler()
.AddRoute("echo", new EchoWebsocketModule()
.WithReceiveTemplateProvider((message) => UTF8Encoding.UTF8.GetString(message.Data))
.WithSendTemplateProvider((s) =>
{
WebsocketMessage websocketMessage = new WebsocketMessage();
websocketMessage.Data = UTF8Encoding.UTF8.GetBytes(s);
websocketMessage.Opcode = WebsocketMessage.Opcodes.Text;
return websocketMessage;
})))
.AddHandler(new HttpHandler()
.AddHttpModule(new HttpRoutingModule()
.AddRoute(HttpMessage.HttpMethods.Get, "", new Http200Module())
@@ -184,7 +193,7 @@ namespace MTSC.UnitTests
{
byte[] bytes = new byte[100];
ClientWebSocket client = new ClientWebSocket();
client.ConnectAsync(new Uri("ws://localhost:800"), CancellationToken.None).Wait();
client.ConnectAsync(new Uri("ws://localhost:800/echo"), CancellationToken.None).Wait();
client.SendAsync(ASCIIEncoding.ASCII.GetBytes("Hello world!"), WebSocketMessageType.Text, true, CancellationToken.None).Wait();
client.ReceiveAsync(bytes, CancellationToken.None).Wait();
var resultString = ASCIIEncoding.ASCII.GetString(bytes, 0, 12);
+24
View File
@@ -0,0 +1,24 @@
using MTSC.Common.WebSockets.RoutingModules;
using MTSC.ServerSide;
using MTSC.ServerSide.Handlers;
namespace MTSC.UnitTests
{
public class EchoWebsocketModule : WebsocketRouteBase<string, string>
{
public override void ConnectionClosed(Server server, WebsocketRoutingHandler handler, ClientData client)
{
}
public override void ConnectionInitialized(Server server, WebsocketRoutingHandler handler, ClientData client)
{
}
public override void HandleReceivedMessage(Server server, WebsocketRoutingHandler handler, ClientData client, string message)
{
this.SendMessage(message, client, handler);
}
}
}
@@ -26,7 +26,7 @@ namespace MTSC.Common.Http.RoutingModules
}
public HttpRouteBase WithTemplateProvider(Func<HttpRequest, T> templateProvider)
public HttpRouteBase<T> WithTemplateProvider(Func<HttpRequest, T> templateProvider)
{
this.template = templateProvider;
return this;
@@ -39,4 +39,39 @@ namespace MTSC.Common.Http.RoutingModules
public abstract HttpResponse HandleRequest(T request, ClientData client, ServerSide.Server server);
}
public abstract class HttpRouteBase<TReceive, TSend> : HttpRouteBase
{
private Func<HttpRequest, TReceive> receiveTemplate;
private Func<TSend, HttpResponse> sendTemplate;
public HttpRouteBase(Func<HttpRequest, TReceive> receiveTemplate, Func<TSend, HttpResponse> sendTemplate)
{
this.receiveTemplate = receiveTemplate;
this.sendTemplate = sendTemplate;
}
public HttpRouteBase()
{
}
public HttpRouteBase<TReceive, TSend> WithReceiveTemplateProvider(Func<HttpRequest, TReceive> templateProvider)
{
this.receiveTemplate = templateProvider;
return this;
}
public HttpRouteBase<TReceive, TSend> WithSendTemplateProvider(Func<TSend, HttpResponse> templateProvider)
{
this.sendTemplate = templateProvider;
return this;
}
public override HttpResponse HandleRequest(HttpRequest request, ClientData client, ServerSide.Server server)
{
return sendTemplate.Invoke(HandleRequest(receiveTemplate.Invoke(request), client, server));
}
public abstract TSend HandleRequest(TReceive request, ClientData client, ServerSide.Server server);
}
}
@@ -0,0 +1,98 @@
using MTSC.ServerSide;
using MTSC.ServerSide.Handlers;
using System;
namespace MTSC.Common.WebSockets.RoutingModules
{
public abstract class WebsocketRouteBase
{
public void CallConnectionInitialized(Server server, WebsocketRoutingHandler handler, ClientData client)
{
ConnectionInitialized(server, handler, client);
}
public void CallHandleReceivedMessage(Server server, WebsocketRoutingHandler handler, ClientData client, WebsocketMessage receivedMessage)
{
HandleReceivedMessage(server, handler, client, receivedMessage);
}
public void CallConnectionClosed(Server server, WebsocketRoutingHandler handler, ClientData client)
{
ConnectionClosed(server, handler, client);
}
public void SendMessage(WebsocketMessage message, ClientData client, WebsocketRoutingHandler handler)
{
handler.QueueMessage(client, message);
}
public abstract void ConnectionInitialized(Server server, WebsocketRoutingHandler handler, ClientData client);
public abstract void HandleReceivedMessage(Server server, WebsocketRoutingHandler handler, ClientData client, WebsocketMessage receivedMessage);
public abstract void ConnectionClosed(Server server, WebsocketRoutingHandler handler, ClientData client);
}
public abstract class WebsocketRouteBase<TReceive> : WebsocketRouteBase
{
private Func<WebsocketMessage, TReceive> receiveTemplate;
public WebsocketRouteBase(Func<WebsocketMessage, TReceive> receiveTemplate)
{
this.receiveTemplate = receiveTemplate;
}
public WebsocketRouteBase()
{
}
public WebsocketRouteBase<TReceive> WithReceiveTemplateProvider(Func<WebsocketMessage, TReceive> templateProvider)
{
this.receiveTemplate = templateProvider;
return this;
}
public override void HandleReceivedMessage(Server server, WebsocketRoutingHandler handler, ClientData client, WebsocketMessage receivedMessage)
{
HandleReceivedMessage(server, handler, client, receiveTemplate.Invoke(receivedMessage));
}
public abstract void HandleReceivedMessage(Server server, WebsocketRoutingHandler handler, ClientData client, TReceive message);
}
public abstract class WebsocketRouteBase<TReceive, TSend> : WebsocketRouteBase
{
private Func<WebsocketMessage, TReceive> receiveTemplate;
private Func<TSend, WebsocketMessage> sendTemplate;
public WebsocketRouteBase(Func<WebsocketMessage, TReceive> receiveTemplate, Func<TSend, WebsocketMessage> sendTemplate)
{
this.receiveTemplate = receiveTemplate;
this.sendTemplate = sendTemplate;
}
public WebsocketRouteBase()
{
}
public WebsocketRouteBase<TReceive, TSend> WithReceiveTemplateProvider(Func<WebsocketMessage, TReceive> templateProvider)
{
this.receiveTemplate = templateProvider;
return this;
}
public WebsocketRouteBase<TReceive, TSend> WithSendTemplateProvider(Func<TSend, WebsocketMessage> templateProvider)
{
this.sendTemplate = templateProvider;
return this;
}
public void SendMessage(TSend message, ClientData client, WebsocketRoutingHandler handler)
{
base.SendMessage(sendTemplate.Invoke(message), client, handler);
}
public override void HandleReceivedMessage(Server server, WebsocketRoutingHandler handler, ClientData client, WebsocketMessage receivedMessage)
{
HandleReceivedMessage(server, handler, client, receiveTemplate.Invoke(receivedMessage));
}
public abstract void HandleReceivedMessage(Server server, WebsocketRoutingHandler handler, ClientData client, TReceive message);
}
}
+3 -3
View File
@@ -5,12 +5,12 @@
<TargetFrameworks>netcoreapp2.1;net48;netstandard2.0;netcoreapp3.0</TargetFrameworks>
<ApplicationIcon />
<StartupObject />
<Version>1.9.2</Version>
<Version>2.0.0</Version>
<Authors>Alexandru-Victor Macocian</Authors>
<Product>MTSC</Product>
<Description>Modular TCP Server and Client</Description>
<AssemblyVersion>0.1.9.2</AssemblyVersion>
<FileVersion>0.1.9.2</FileVersion>
<AssemblyVersion>0.2.0.0</AssemblyVersion>
<FileVersion>0.2.0.0</FileVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Platforms>AnyCPU;x64</Platforms>
<PackageProjectUrl>https://github.com/AlexMacocian/MTSC</PackageProjectUrl>
+1 -1
View File
@@ -97,7 +97,7 @@ namespace MTSC.ServerSide.Handlers
{
if (webSockets[client] == SocketState.Initial)
{
HttpRequest request = new HttpRequest(message.MessageBytes);
PartialHttpRequest request = new PartialHttpRequest(message.MessageBytes);
if(request.Method == HttpMessage.HttpMethods.Get && request.Headers.ContainsHeader(HttpMessage.GeneralHeaders.Connection) &&
request.Headers[HttpMessage.GeneralHeaders.Connection].ToLower() == "upgrade" && request.Headers.ContainsHeader(WebsocketProtocolVersionKey) &&
request.Headers[WebsocketProtocolVersionKey] == "13")
@@ -0,0 +1,241 @@
using MTSC.Common.Http;
using MTSC.Common.WebSockets;
using MTSC.Common.WebSockets.RoutingModules;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace MTSC.ServerSide.Handlers
{
public class WebsocketRoutingHandler : IHandler
{
private static Func<Server, HttpRequest, ClientData, RouteEnablerResponse> alwaysEnabled = (server, message, client) => RouteEnablerResponse.Accept;
private static readonly string WebsocketHeaderAcceptKey = "Sec-WebSocket-Accept";
private static readonly string WebsocketHeaderKey = "Sec-WebSocket-Key";
private static readonly string WebsocketProtocolKey = "Sec-WebSocket-Protocol";
private static readonly string WebsocketProtocolVersionKey = "Sec-WebSocket-Version";
private static readonly string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private static SHA1 sha1Provider = SHA1.Create();
private static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
public enum SocketState
{
Initial,
Handshaking,
Established,
Closed
}
#region Fields
public ConcurrentDictionary<ClientData, SocketState> webSockets = new ConcurrentDictionary<ClientData, SocketState>();
Dictionary<string, (WebsocketRouteBase, Func<Server, HttpRequest, ClientData, RouteEnablerResponse>)> moduleDictionary =
new Dictionary<string, (WebsocketRouteBase, Func<Server, HttpRequest, ClientData, RouteEnablerResponse>)>();
ConcurrentDictionary<ClientData, WebsocketRouteBase> routingTable = new ConcurrentDictionary<ClientData, WebsocketRouteBase>();
ConcurrentQueue<Tuple<ClientData, WebsocketMessage>> messageQueue = new ConcurrentQueue<Tuple<ClientData, WebsocketMessage>>();
#endregion
#region Public Methods
public WebsocketRoutingHandler AddRoute(string uri, WebsocketRouteBase module)
{
this.moduleDictionary.Add(uri, (module, alwaysEnabled));
return this;
}
public WebsocketRoutingHandler AddRoute(
string uri,
WebsocketRouteBase module,
Func<Server, HttpRequest, ClientData, RouteEnablerResponse> routeEnabler)
{
this.moduleDictionary.Add(uri, (module, routeEnabler));
return this;
}
public WebsocketRoutingHandler RemoveRoute(string uri)
{
moduleDictionary.Remove(uri);
return this;
}
public void QueueMessage(ClientData client, byte[] message, WebsocketMessage.Opcodes opcode = WebsocketMessage.Opcodes.Text)
{
WebsocketMessage sendMessage = new WebsocketMessage();
sendMessage.Data = message;
sendMessage.FIN = true;
sendMessage.Masked = false;
rng.GetBytes(sendMessage.Mask);
sendMessage.Opcode = opcode;
messageQueue.Enqueue(new Tuple<ClientData, WebsocketMessage>(client, sendMessage));
}
public void QueueMessage(ClientData client, WebsocketMessage message)
{
messageQueue.Enqueue(new Tuple<ClientData, WebsocketMessage>(client, message));
}
public void CloseConnection(ClientData client)
{
WebsocketMessage websocketMessage = new WebsocketMessage();
websocketMessage.FIN = true;
websocketMessage.Opcode = WebsocketMessage.Opcodes.Close;
websocketMessage.Masked = false;
QueueMessage(client, websocketMessage);
}
#endregion
#region Handler Implementation
void IHandler.ClientRemoved(Server server, ClientData client)
{
SocketState state = SocketState.Initial;
while (webSockets.ContainsKey(client))
{
webSockets.TryRemove(client, out state);
}
routingTable[client].CallConnectionClosed(server, this, client);
while (routingTable.ContainsKey(client))
{
routingTable.TryRemove(client, out _);
}
}
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)
{
PartialHttpRequest request;
try
{
request = PartialHttpRequest.FromBytes(message.MessageBytes);
}
catch(Exception e)
{
server.LogDebug(e.Message + "\n" + e.StackTrace);
return false;
}
if (request.Method == HttpMessage.HttpMethods.Get && request.Headers.ContainsHeader(HttpMessage.GeneralHeaders.Connection) &&
request.Headers[HttpMessage.GeneralHeaders.Connection].ToLower() == "upgrade" && request.Headers.ContainsHeader(WebsocketProtocolVersionKey) &&
request.Headers[WebsocketProtocolVersionKey] == "13")
{
if (!moduleDictionary.ContainsKey(request.RequestURI))
{
QueueMessage(client, new HttpResponse { StatusCode = HttpMessage.StatusCodes.NotFound, BodyString = "URI not found" }.GetPackedResponse(true));
}
(var module, var routeEnabler) = moduleDictionary[request.RequestURI];
var routeEnablerResponse = routeEnabler.Invoke(server, request.ToRequest(), client);
if(routeEnablerResponse is RouteEnablerResponse.RouteEnablerResponseIgnore)
{
return false;
}
else if(routeEnablerResponse is RouteEnablerResponse.RouteEnablerResponseError)
{
QueueMessage(client, (routeEnablerResponse as RouteEnablerResponse.RouteEnablerResponseError).Response.GetPackedResponse(true));
return true;
}
/*
* The RouteEnabler accepted the request.
* Prepare the handshake string.
*/
string base64Key = request.Headers[WebsocketHeaderKey];
base64Key = base64Key.Trim();
string handshakeKey = base64Key + GlobalUniqueIdentifier;
string returnBase64Key = Convert.ToBase64String(sha1Provider.ComputeHash(Encoding.UTF8.GetBytes(handshakeKey)));
/*
* Prepare the response.
*/
HttpResponse response = new HttpResponse();
response.StatusCode = HttpMessage.StatusCodes.SwitchingProtocols;
response.Headers[HttpMessage.GeneralHeaders.Upgrade] = "websocket";
response.Headers[HttpMessage.GeneralHeaders.Connection] = "Upgrade";
response.Headers[WebsocketHeaderAcceptKey] = returnBase64Key;
server.QueueMessage(client, response.GetPackedResponse(true));
webSockets[client] = SocketState.Established;
server.LogDebug("Websocket initialized " + client.TcpClient.Client.RemoteEndPoint.ToString());
routingTable[client] = module;
module.CallConnectionInitialized(server, this, client);
return true;
}
else
{
return false;
}
}
else if (webSockets[client] == SocketState.Established)
{
WebsocketMessage receivedMessage = null;
try
{
receivedMessage = new WebsocketMessage(message.MessageBytes);
}
catch(Exception e)
{
server.LogDebug(e.Message + "\n" + e.StackTrace);
return false;
}
if (receivedMessage.Opcode == WebsocketMessage.Opcodes.Close)
{
client.ToBeRemoved = true;
while (webSockets.ContainsKey(client))
{
webSockets.TryRemove(client, out SocketState _);
}
WebsocketMessage closeFrame = new WebsocketMessage();
closeFrame.Opcode = WebsocketMessage.Opcodes.Close;
QueueMessage(client, closeFrame);
return true;
}
else
{
try
{
routingTable[client].CallHandleReceivedMessage(server, this, client, receivedMessage);
return true;
}
catch(Exception e)
{
server.LogDebug(e.Message + "\n" + e.StackTrace);
return false;
}
}
}
else
{
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)
{
if (messageQueue.TryDequeue(out Tuple<ClientData, WebsocketMessage> tuple))
{
server.QueueMessage(tuple.Item1, tuple.Item2.GetMessageBytes());
if (tuple.Item2.Opcode == WebsocketMessage.Opcodes.Close)
{
if (routingTable.ContainsKey(tuple.Item1))
{
routingTable[tuple.Item1].CallConnectionClosed(server, this, tuple.Item1);
}
tuple.Item1.ToBeRemoved = true;
}
}
}
}
#endregion
}
}