mirror of
https://github.com/AlexMacocian/MTSC.git
synced 2026-07-24 03:56:32 +00:00
Added SendingTemplate to HttpResponse
Implemented WebsocketRouteBase
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user