diff --git a/MTSC-TestServer/Program.cs b/MTSC-TestServer/Program.cs index 7cb9fb3..ed19a33 100644 --- a/MTSC-TestServer/Program.cs +++ b/MTSC-TestServer/Program.cs @@ -28,7 +28,7 @@ namespace MTSC_TestServer .AddExceptionHandler(new ExceptionConsoleLogger()) //.AddHandler(new BroadcastHandler()) .AddHandler(new WebsocketHandler().AddWebsocketHandler(new BroadcastModule())) - .AddHandler(new HttpHandler().AddHttpModule(new HelloWorldModule())) + .AddHandler(new HttpHandler().AddHttpModule(new FileServerModule())) .Run(); } diff --git a/MTSC/Common/Http/ServerModules/FileServerModule.cs b/MTSC/Common/Http/ServerModules/FileServerModule.cs new file mode 100644 index 0000000..13ab497 --- /dev/null +++ b/MTSC/Common/Http/ServerModules/FileServerModule.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Text; +using MTSC.Server; +using MTSC.Server.Handlers; + +namespace MTSC.Common.Http.ServerModules +{ + public class FileServerModule : IHttpModule + { + private string rootFolder; + ConcurrentDictionary> fileCache = new ConcurrentDictionary>(); + List toRemoveCache = new List(); + public FileServerModule(string rootFolder = "src") + { + this.rootFolder = Path.GetFullPath(rootFolder); + } + #region Interface Implementation + bool IHttpModule.HandleRequest(Server.Server server, HttpHandler handler, ClientData client, HttpMessage request, ref HttpMessage response) + { + if(request.Method == HttpMessage.MethodEnum.Get) + { + if(request.RequestURI == @"/") + { + request.RequestURI = @"/index.html"; + } + string requestFile = this.rootFolder + request.RequestURI; + if(requestFile.IsSubPathOf(this.rootFolder) && File.Exists(requestFile)) + { + /* + * If file is in cache, retrieve it from cache. + */ + byte[] bodyData; + if (fileCache.ContainsKey(requestFile)) + { + bodyData = fileCache[requestFile].Item1; + } + else + { + bodyData = File.ReadAllBytes(requestFile); + } + /* + * Insert or update cache with the requested file. + */ + Tuple tuple = new Tuple(bodyData, DateTime.Now); + fileCache.AddOrUpdate(requestFile, tuple, (key, oldValue) => oldValue = tuple); + /* + * Build the response packet. + */ + response.Body = bodyData; + response.StatusCode = HttpMessage.StatusCodes.Found; + if (!fileCache.ContainsKey(requestFile)) + { + server.LogDebug("Adding " + requestFile + " to server cache."); + } + return true; + } + } + return false; + } + + void IHttpModule.Tick(Server.Server server, HttpHandler handler) + { + foreach(KeyValuePair> keyValuePair in fileCache) + { + if((DateTime.Now - keyValuePair.Value.Item2).TotalSeconds > 60) + { + toRemoveCache.Add(keyValuePair.Key); + } + } + foreach(string fileName in toRemoveCache) + { + Tuple tp; + while(!fileCache.TryRemove(fileName, out tp)) { }; + server.LogDebug("Removed " + fileName + " from cache."); + } + toRemoveCache.Clear(); + } + + #endregion + } +} diff --git a/MTSC/Common/Http/ServerModules/HelloWorldModule.cs b/MTSC/Common/Http/ServerModules/HelloWorldModule.cs index 1b72c86..cb7e672 100644 --- a/MTSC/Common/Http/ServerModules/HelloWorldModule.cs +++ b/MTSC/Common/Http/ServerModules/HelloWorldModule.cs @@ -9,7 +9,7 @@ namespace MTSC.Common.Http.ServerModules public class HelloWorldModule : IHttpModule { byte[] response = ASCIIEncoding.ASCII.GetBytes("Hello, World!"); - bool IHttpModule.HandleRequest(HttpHandler handler, ClientData client, HttpMessage request, ref HttpMessage response) + bool IHttpModule.HandleRequest(Server.Server server, HttpHandler handler, ClientData client, HttpMessage request, ref HttpMessage response) { if (request.Method == HttpMessage.MethodEnum.Get) { @@ -22,5 +22,10 @@ namespace MTSC.Common.Http.ServerModules } return true; } + + void IHttpModule.Tick(Server.Server server, HttpHandler handler) + { + throw new NotImplementedException(); + } } } diff --git a/MTSC/Common/Http/ServerModules/Http404Module.cs b/MTSC/Common/Http/ServerModules/Http404Module.cs index 54d1ebd..e168b6c 100644 --- a/MTSC/Common/Http/ServerModules/Http404Module.cs +++ b/MTSC/Common/Http/ServerModules/Http404Module.cs @@ -19,7 +19,7 @@ namespace MTSC.Common.Http.ServerModules /// /// /// True so no other handler modifies the response, so the response contains the 404 return status code. - bool IHttpModule.HandleRequest(HttpHandler handler, ClientData client, HttpMessage request, ref HttpMessage response) + bool IHttpModule.HandleRequest(Server.Server server, HttpHandler handler, ClientData client, HttpMessage request, ref HttpMessage response) { if(request.Method == HttpMessage.MethodEnum.Get) { @@ -29,5 +29,10 @@ namespace MTSC.Common.Http.ServerModules } return true; } + + void IHttpModule.Tick(Server.Server server, HttpHandler handler) + { + throw new NotImplementedException(); + } } } diff --git a/MTSC/Common/Http/ServerModules/IHttpModule.cs b/MTSC/Common/Http/ServerModules/IHttpModule.cs index a60465a..fa075c2 100644 --- a/MTSC/Common/Http/ServerModules/IHttpModule.cs +++ b/MTSC/Common/Http/ServerModules/IHttpModule.cs @@ -19,6 +19,11 @@ namespace MTSC.Common.Http.ServerModules /// Client data. /// Request message. /// True if no other module should handle the received request. - bool HandleRequest(HttpHandler handler, ClientData client, HttpMessage request, ref HttpMessage response); + bool HandleRequest(Server.Server server, HttpHandler handler, ClientData client, HttpMessage request, ref HttpMessage response); + /// + /// Perform periodic operations. + /// + /// + void Tick(Server.Server server, HttpHandler handler); } } diff --git a/MTSC/Common/WebSockets/ClientModules/ChatModule.cs b/MTSC/Common/WebSockets/ClientModules/ChatModule.cs index c9a060d..5047445 100644 --- a/MTSC/Common/WebSockets/ClientModules/ChatModule.cs +++ b/MTSC/Common/WebSockets/ClientModules/ChatModule.cs @@ -23,18 +23,18 @@ namespace MTSC.Common.WebSockets.ClientModules } #endregion #region Interface Implementation - bool IWebsocketModule.HandleReceivedMessage(Client.Client client, IHandler handler, WebsocketMessage receivedMessage) + bool IWebsocketModule.HandleReceivedMessage(Client.Client client, WebsocketHandler handler, WebsocketMessage receivedMessage) { string messageString = Encoding.UTF8.GetString(receivedMessage.Data); client.Log(">" + messageString); return false; } - void IWebsocketModule.ConnectionClosed(Client.Client client, IHandler handler) + void IWebsocketModule.ConnectionClosed(Client.Client client, WebsocketHandler handler) { } - void IWebsocketModule.ConnectionInitialized(Client.Client client, IHandler handler) + void IWebsocketModule.ConnectionInitialized(Client.Client client, WebsocketHandler handler) { } diff --git a/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs b/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs index 6da68ca..409a17a 100644 --- a/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs +++ b/MTSC/Common/WebSockets/ClientModules/IWebsocketModule.cs @@ -15,7 +15,7 @@ namespace MTSC.Common.WebSockets.ClientModules /// /// Client object. /// Handler that performed the initialization. - void ConnectionInitialized(Client.Client client, IHandler handler); + void ConnectionInitialized(Client.Client client, WebsocketHandler handler); /// /// Handle received message. /// @@ -23,12 +23,12 @@ namespace MTSC.Common.WebSockets.ClientModules /// Handler currently processing the message. /// Array containing the message. /// True if no other module should handle this message. - bool HandleReceivedMessage(Client.Client client, IHandler handler, WebsocketMessage messageBytes); + bool HandleReceivedMessage(Client.Client client, WebsocketHandler handler, WebsocketMessage messageBytes); /// /// Called when an existing websocket connection is being closed. /// /// Client object. /// Handler that closes the connection. - void ConnectionClosed(Client.Client client, IHandler handler); + void ConnectionClosed(Client.Client client, WebsocketHandler handler); } } diff --git a/MTSC/Common/WebSockets/ServerModules/BroadcastModule.cs b/MTSC/Common/WebSockets/ServerModules/BroadcastModule.cs index 783f853..5e170ef 100644 --- a/MTSC/Common/WebSockets/ServerModules/BroadcastModule.cs +++ b/MTSC/Common/WebSockets/ServerModules/BroadcastModule.cs @@ -8,23 +8,23 @@ namespace MTSC.Common.WebSockets.ServerModules { public class BroadcastModule : IWebsocketModule { - void IWebsocketModule.ConnectionClosed(Server.Server server, IHandler handler, ClientData client) + void IWebsocketModule.ConnectionClosed(Server.Server server, WebsocketHandler handler, ClientData client) { } - void IWebsocketModule.ConnectionInitialized(Server.Server server, IHandler handler, ClientData client) + void IWebsocketModule.ConnectionInitialized(Server.Server server, WebsocketHandler handler, ClientData client) { } - bool IWebsocketModule.HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage) + bool IWebsocketModule.HandleReceivedMessage(Server.Server server, WebsocketHandler handler, ClientData client, WebsocketMessage receivedMessage) { receivedMessage.Masked = false; //receivedMessage.Opcode = WebsocketMessage.Opcodes.Text; - foreach(ClientData otherClient in server.Clients) + foreach(ClientData otherClient in handler.webSockets.Keys) { - (handler as WebsocketHandler).QueueMessage(otherClient, receivedMessage); + handler.QueueMessage(otherClient, receivedMessage); } return false; } diff --git a/MTSC/Common/WebSockets/ServerModules/EchoModule.cs b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs index ae1e0ec..06fa1bc 100644 --- a/MTSC/Common/WebSockets/ServerModules/EchoModule.cs +++ b/MTSC/Common/WebSockets/ServerModules/EchoModule.cs @@ -17,18 +17,18 @@ namespace MTSC.Common.WebSockets.ServerModules } #endregion #region Interface Implementation - bool IWebsocketModule.HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage) + bool IWebsocketModule.HandleReceivedMessage(Server.Server server, WebsocketHandler handler, ClientData client, WebsocketMessage receivedMessage) { - SendMessage((WebsocketHandler)handler, client, receivedMessage); + SendMessage(handler, client, receivedMessage); return false; } - void IWebsocketModule.ConnectionClosed(Server.Server server, IHandler handler, ClientData client) + void IWebsocketModule.ConnectionClosed(Server.Server server, WebsocketHandler handler, ClientData client) { } - void IWebsocketModule.ConnectionInitialized(Server.Server server, IHandler handler, ClientData client) + void IWebsocketModule.ConnectionInitialized(Server.Server server, WebsocketHandler handler, ClientData client) { } diff --git a/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs b/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs index c0709e8..384544d 100644 --- a/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs +++ b/MTSC/Common/WebSockets/ServerModules/IWebsocketModule.cs @@ -14,7 +14,7 @@ namespace MTSC.Common.WebSockets.ServerModules /// Server object. /// Handler currently processing. /// Client object. - void ConnectionInitialized(Server.Server server, IHandler handler, ClientData client); + void ConnectionInitialized(Server.Server server, WebsocketHandler handler, ClientData client); /// /// Handle a received message. /// @@ -22,13 +22,13 @@ namespace MTSC.Common.WebSockets.ServerModules /// Bytes of the message. /// Client data. /// True if no other module should process the message. - bool HandleReceivedMessage(Server.Server server, IHandler handler, ClientData client, WebsocketMessage receivedMessage); + bool HandleReceivedMessage(Server.Server server, WebsocketHandler handler, ClientData client, WebsocketMessage receivedMessage); /// /// Called when a connection has been closed. /// /// Server object. /// Handler currently processing. /// Client object. - void ConnectionClosed(Server.Server server, IHandler handler, ClientData client); + void ConnectionClosed(Server.Server server, WebsocketHandler handler, ClientData client); } } diff --git a/MTSC/HelperFunctions.cs b/MTSC/HelperFunctions.cs index 449dd8e..e4ee4e3 100644 --- a/MTSC/HelperFunctions.cs +++ b/MTSC/HelperFunctions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Security.Cryptography; using System.Text; using System.Xml; @@ -56,7 +57,62 @@ namespace MTSC parameters.InverseQ != null ? Convert.ToBase64String(parameters.InverseQ) : null, parameters.D != null ? Convert.ToBase64String(parameters.D) : null); } + /// + /// Returns true if starts with the path . + /// The comparison is case-insensitive, handles / and \ slashes as folder separators and + /// only matches if the base dir folder name is matched exactly ("c:\foobar\file.txt" is not a sub path of "c:\foo"). + /// + public static bool IsSubPathOf(this string path, string baseDirPath) + { + string normalizedPath = Path.GetFullPath(path.Replace('/', '\\') + .WithEnding("\\")); + string normalizedBaseDirPath = Path.GetFullPath(baseDirPath.Replace('/', '\\') + .WithEnding("\\")); + + return normalizedPath.StartsWith(normalizedBaseDirPath, StringComparison.OrdinalIgnoreCase); + } + /// + /// Returns with the minimal concatenation of (starting from end) that + /// results in satisfying .EndsWith(ending). + /// + /// "hel".WithEnding("llo") returns "hello", which is the result of "hel" + "lo". + public static string WithEnding(this string str, string ending) + { + if (str == null) + return ending; + + string result = str; + + // Right() is 1-indexed, so include these cases + // * Append no characters + // * Append up to N characters, where N is ending length + for (int i = 0; i <= ending.Length; i++) + { + string tmp = result + ending.Right(i); + if (tmp.EndsWith(ending)) + return tmp; + } + + return result; + } + /// Gets the rightmost characters from a string. + /// The string to retrieve the substring from. + /// The number of characters to retrieve. + /// The substring. + public static string Right(this string value, int length) + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + if (length < 0) + { + throw new ArgumentOutOfRangeException("length", length, "Length is less than zero"); + } + + return (length < value.Length) ? value.Substring(value.Length - length) : value; + } #endregion } } diff --git a/MTSC/MTSC.csproj b/MTSC/MTSC.csproj index 7091fb7..b8b8f58 100644 --- a/MTSC/MTSC.csproj +++ b/MTSC/MTSC.csproj @@ -5,14 +5,17 @@ netcoreapp2.1 - 0.9.71 + 0.9.8 Alexandru-Victor Macocian MTSC Modular TCP Server and Client - 0.0.9.71 - 0.0.9.71 + 0.0.9.8 + 0.0.9.8 true AnyCPU;x64 + https://github.com/AlexMacocian/MTSC + https://github.com/AlexMacocian/MTSC/blob/master/docs/MTSC_Logo.png + https://github.com/AlexMacocian/MTSC diff --git a/MTSC/Server/Handlers/HttpHandler.cs b/MTSC/Server/Handlers/HttpHandler.cs index ecb4904..11871de 100644 --- a/MTSC/Server/Handlers/HttpHandler.cs +++ b/MTSC/Server/Handlers/HttpHandler.cs @@ -17,7 +17,6 @@ namespace MTSC.Server.Handlers #region Fields List httpModules = new List(); ConcurrentQueue> messageQueue = new ConcurrentQueue>(); - object queueLock = new object(); #endregion #region Constructors public HttpHandler() @@ -86,7 +85,7 @@ namespace MTSC.Server.Handlers } foreach(IHttpModule module in httpModules) { - if(module.HandleRequest(this, client, httpMessage, ref responseMessage)) + if(module.HandleRequest(server, this, client, httpMessage, ref responseMessage)) { break; } @@ -127,6 +126,10 @@ namespace MTSC.Server.Handlers server.QueueMessage(tuple.Item1, tuple.Item2.GetResponse(true)); } } + foreach (IHttpModule module in httpModules) + { + module.Tick(server, this); + } } #endregion } diff --git a/MTSC/Server/Handlers/WebsocketHandler.cs b/MTSC/Server/Handlers/WebsocketHandler.cs index accc516..1e39845 100644 --- a/MTSC/Server/Handlers/WebsocketHandler.cs +++ b/MTSC/Server/Handlers/WebsocketHandler.cs @@ -18,7 +18,7 @@ namespace MTSC.Server.Handlers private static string GlobalUniqueIdentifier = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private static SHA1 sha1Provider = SHA1.Create(); private static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); - private enum SocketState + public enum SocketState { Initial, Handshaking, @@ -26,7 +26,7 @@ namespace MTSC.Server.Handlers Closed } #region Fields - ConcurrentDictionary webSockets = new ConcurrentDictionary(); + public ConcurrentDictionary webSockets = new ConcurrentDictionary(); ConcurrentQueue> messageQueue = new ConcurrentQueue>(); List websocketModules = new List(); #endregion @@ -80,7 +80,7 @@ namespace MTSC.Server.Handlers void IHandler.ClientRemoved(Server server, ClientData client) { SocketState state = SocketState.Initial; - webSockets.TryRemove(client, out state); + webSockets.Remove(client, out state); } bool IHandler.HandleClient(Server server, ClientData client) @@ -135,6 +135,8 @@ namespace MTSC.Server.Handlers websocketModule.ConnectionClosed(server, this, client); } client.ToBeRemoved = true; + SocketState outSocketState = SocketState.Closed; + webSockets.Remove(client, out outSocketState); } else { @@ -176,6 +178,8 @@ namespace MTSC.Server.Handlers websocketModule.ConnectionClosed(server, this, tuple.Item1); } tuple.Item1.ToBeRemoved = true; + SocketState outSocketState = SocketState.Closed; + webSockets.Remove(tuple.Item1, out outSocketState); } } }