diff --git a/MTSC.UnitTests/E2ETests.cs b/MTSC.UnitTests/E2ETests.cs index f8e406e..8d464ac 100644 --- a/MTSC.UnitTests/E2ETests.cs +++ b/MTSC.UnitTests/E2ETests.cs @@ -1,15 +1,17 @@ +using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; +using MTSC.Client; +using MTSC.Common.Http; using MTSC.Common.Http.RoutingModules; +using MTSC.Common.Http.ServerModules; using MTSC.Common.WebSockets.ServerModules; using MTSC.Exceptions; using MTSC.Logging; using MTSC.Server.Handlers; using System; -using System.Net; +using System.Linq; using System.Net.Http; -using System.Net.Security; using System.Net.WebSockets; -using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -19,7 +21,7 @@ namespace MTSC.UnitTests [TestClass] public class E2ETests { - private static int stressIterations = 100000; + private static int stressIterations = 1000; public TestContext TestContext { get; set; } static Server.Server Server { get; set; } @@ -27,16 +29,14 @@ namespace MTSC.UnitTests public static void InitializeServer(TestContext testContext) { Server = new Server.Server(800) - .AddHandler(new WebsocketHandler() - .AddWebsocketHandler(new EchoModule())) - //.AddHandler(new HttpHandler() - // .AddHttpModule(new HelloWorldModule())) - .AddHandler(new HttpRoutingHandler() - .AddRoute(Common.Http.HttpMessage.HttpMethods.Get, "/", new Http200Module())) + //.AddHandler(new WebsocketHandler() + // .AddWebsocketHandler(new EchoModule())) + .AddHandler(new HttpHandler() + .AddHttpModule(new HttpRoutingModule() + .AddRoute(Common.Http.HttpMessage.HttpMethods.Get, "/", new Http200Module()))) .AddLogger(new ConsoleLogger()) .AddLogger(new DebugConsoleLogger()) .AddExceptionHandler(new ExceptionConsoleLogger()); - //.AddServerUsageMonitor(new TickrateEnforcer().SetTicksPerSecond(60)); Server.RunAsync().Start(); } @@ -84,6 +84,25 @@ namespace MTSC.UnitTests } } + [TestMethod] + public void HttpFragmentedMessage() + { + var client = new MTSC.Client.Client(false); + client.SetServerAddress("127.0.0.1"); + client.SetPort(800); + client.Connect(); + HttpRequest request = new HttpRequest(); + request.Method = HttpMessage.HttpMethods.Get; + request.RequestURI = "/"; + byte[] messageBytes = request.GetPackedRequest(); + byte[] firstBytes = messageBytes.Take(4).ToArray(); + byte[] restBytes = messageBytes.Skip(4).ToArray(); + client.QueueMessage(firstBytes); + client.QueueMessage(restBytes); + Thread.Sleep(10000); + Thread.Sleep(10000); + } + [ClassCleanup] public static void CleanupServer() { diff --git a/MTSC/Common/Http/HttpMessage.cs b/MTSC/Common/Http/HttpMessage.cs index 65434ff..0ccb712 100644 --- a/MTSC/Common/Http/HttpMessage.cs +++ b/MTSC/Common/Http/HttpMessage.cs @@ -601,10 +601,10 @@ namespace MTSC.Common.Http } catch (Exception e) { - throw new MethodInvalidException("Invalid request method. Buffer: " + parseBuffer.ToString(), e); + throw new InvalidMethodException("Invalid request method. Buffer: " + parseBuffer.ToString(), e); } } - throw new MethodInvalidException("Invalid request method. Buffer: " + parseBuffer.ToString()); + throw new InvalidMethodException("Invalid request method. Buffer: " + parseBuffer.ToString()); } private string ParseRequestURI(byte[] buffer, ref int index) diff --git a/MTSC/Common/Http/HttpRequest.cs b/MTSC/Common/Http/HttpRequest.cs index 95730e7..beff7a6 100644 --- a/MTSC/Common/Http/HttpRequest.cs +++ b/MTSC/Common/Http/HttpRequest.cs @@ -65,9 +65,8 @@ namespace MTSC.Common.Http } private HttpMethods GetMethod(string methodString) - { - int index = Array.IndexOf(HttpHeaders.Methods, methodString.ToUpper()); - return (HttpMethods)index; + { + return (HttpMethods)Enum.Parse(typeof(HttpMethods), methodString.ToUpper(), true); } private HttpMethods ParseMethod(MemoryStream ms) @@ -77,11 +76,11 @@ namespace MTSC.Common.Http * and continue with parsing the next step. */ StringBuilder parseBuffer = new StringBuilder(); - while (ms.CanRead) + while (ms.Position < ms.Length) { try { - char c = Convert.ToChar(ms.ReadByte()); + char c = (char)ms.ReadByte(); if (c == HttpHeaders.SP) { string methodString = parseBuffer.ToString(); @@ -94,11 +93,11 @@ namespace MTSC.Common.Http } catch (Exception e) { - throw new MethodInvalidException("Invalid request method. Buffer: " + parseBuffer.ToString(), + throw new InvalidMethodException("Invalid request method. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } - throw new MethodInvalidException("Invalid request method. Buffer: " + parseBuffer.ToString(), + throw new IncompleteMethodException("Incomplete request method. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -108,11 +107,11 @@ namespace MTSC.Common.Http * Get each character one by one. When meeting a SP character, parse the URI and clear the buffer. */ StringBuilder parseBuffer = new StringBuilder(); - while (ms.CanRead) + while (ms.Position < ms.Length) { try { - char c = Convert.ToChar(ms.ReadByte()); + char c = (char)ms.ReadByte(); if (c == HttpHeaders.SP) { return parseBuffer.ToString(); @@ -132,7 +131,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } - throw new InvalidRequestURIException("Invalid request URI. Buffer: " + parseBuffer.ToString(), + throw new IncompleteRequestURIException("Incomplete request URI. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -142,11 +141,11 @@ namespace MTSC.Common.Http * Get each character one by one. When meeting a SP character, parse the URI and clear the buffer. */ StringBuilder parseBuffer = new StringBuilder(); - while (ms.CanRead) + while (ms.Position < ms.Length) { try { - char c = Convert.ToChar(ms.ReadByte()); + char c = (char)ms.ReadByte(); if (c == (byte)HttpHeaders.SP) { return parseBuffer.ToString(); @@ -162,7 +161,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } - throw new InvalidRequestURIException("Invalid request query. Buffer: " + parseBuffer.ToString(), + throw new IncompleteRequestQueryException("Incomplete request query. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -174,11 +173,11 @@ namespace MTSC.Common.Http * If not, throw an exception. */ StringBuilder parseBuffer = new StringBuilder(); - while (ms.CanRead) + while (ms.Position < ms.Length) { try { - char c = Convert.ToChar(ms.ReadByte()); + char c = (char)ms.ReadByte(); if (c == HttpHeaders.CRLF[1] || c == HttpHeaders.SP) { string httpVer = parseBuffer.ToString(); @@ -206,6 +205,9 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } + // If code reaches here, it means the message is incomplete. + throw new IncompleteHttpVersionException("Incomplete HTTP version. Buffer: " + parseBuffer.ToString(), + new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } private string ParseHeaderKey(MemoryStream ms) @@ -214,11 +216,11 @@ namespace MTSC.Common.Http * Get each character one by one. When meeting a ':' character, parse the header key. */ StringBuilder parseBuffer = new StringBuilder(); - while(ms.CanRead) + while(ms.Position < ms.Length) { try { - char c = Convert.ToChar(ms.ReadByte()); + char c = (char)ms.ReadByte(); if (c == ':') { return parseBuffer.ToString(); @@ -234,7 +236,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } - throw new InvalidHeaderException("Invalid Header key. Buffer: " + parseBuffer.ToString(), + throw new IncompleteHeaderKeyException("Incomplete Header key. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -244,11 +246,11 @@ namespace MTSC.Common.Http * Get each character one by one. When meeting a LF character, parse the value. */ StringBuilder parseBuffer = new StringBuilder(); - while(ms.CanRead) + while(ms.Position < ms.Length) { try { - char c = Convert.ToChar(ms.ReadByte()); + char c = (char)ms.ReadByte(); if (c == HttpHeaders.CRLF[1]) { return parseBuffer.ToString().Trim(); @@ -271,7 +273,7 @@ namespace MTSC.Common.Http new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()), e)); } } - throw new InvalidHeaderException("Invalid header value. Buffer: " + parseBuffer.ToString(), + throw new IncompleteHeaderValueException("Incomplete header value. Buffer: " + parseBuffer.ToString(), new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); } @@ -328,7 +330,7 @@ namespace MTSC.Common.Http int step = 0; string headerKey = string.Empty; string headerValue = string.Empty; - while(ms.CanRead) + while(ms.Position < ms.Length) { if (step == 0) { @@ -403,13 +405,35 @@ namespace MTSC.Common.Http } } } - if (ms.Length - ms.Position > 1) + if(step < 4) { - /* - * If the message contains a body, copy it into a different array - * and save it into the HTTP message; - */ - this.Body = ms.ReadRemainingBytes(); + throw new IncompleteRequestException($"Incomplete request.", + new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); + } + if (Headers.ContainsHeader(EntityHeaders.ContentLength)) + { + int remainingBytes = int.Parse(Headers[EntityHeaders.ContentLength]); + if (remainingBytes > ms.Length - ms.Position) + { + throw new IncompleteRequestBodyException($"Incomplete request body. Expected size {remainingBytes} but remaining size is {ms.Length - ms.Position}.", + new HttpRequestParsingException("Exception during parsing of http request. Buffer: " + UTF8Encoding.UTF8.GetString(ms.ToArray()))); + } + else + { + this.Body = new byte[remainingBytes]; + ms.Read(this.Body, 0, remainingBytes); + } + } + else + { + if (ms.Length - ms.Position > 1) + { + /* + * If the message contains a body, copy it into a different array + * and save it into the HTTP message; + */ + this.Body = ms.ReadRemainingBytes(); + } } return; } diff --git a/MTSC/Common/Http/ServerModules/HttpRoutingModule.cs b/MTSC/Common/Http/ServerModules/HttpRoutingModule.cs new file mode 100644 index 0000000..de2d34b --- /dev/null +++ b/MTSC/Common/Http/ServerModules/HttpRoutingModule.cs @@ -0,0 +1,65 @@ +using MTSC.Common.Http.RoutingModules; +using MTSC.Server; +using MTSC.Server.Handlers; +using System; +using System.Collections.Generic; +using static MTSC.Common.Http.HttpMessage; + +namespace MTSC.Common.Http.ServerModules +{ + public class HttpRoutingModule : IHttpModule + { + private Dictionary)>> moduleDictionary = + new Dictionary)>>(); + + public HttpRoutingModule() + { + foreach (HttpMethods method in (HttpMethods[])Enum.GetValues(typeof(HttpMethods))) + { + moduleDictionary[method] = new Dictionary)>(); + } + } + + public HttpRoutingModule AddRoute(HttpMethods method, string uri, IHttpRoute routeModule, Func routeEnabler = null) + { + if(routeEnabler == null) + { + routeEnabler = (request, client) => { return true; }; + } + moduleDictionary[method][uri] = (routeModule, routeEnabler); + return this; + } + + bool IHttpModule.HandleRequest(Server.Server server, HttpHandler handler, ClientData client, HttpRequest request, ref HttpResponse response) + { + /* + * Now find if a routing module exists. If not let other handlers try and handle the message. + */ + if (moduleDictionary[request.Method].ContainsKey(request.RequestURI)) + { + (var module, var routeEnabler) = moduleDictionary[request.Method][request.RequestURI]; + if(routeEnabler.Invoke(request, client)) + { + try + { + server.QueueMessage(client, module.HandleRequest(request, client, server).GetPackedResponse(true)); + } + catch(Exception e) + { + server.LogDebug("Exception: " + e.Message); + server.LogDebug("Stacktrace: " + e.StackTrace); + server.QueueMessage(client, new HttpResponse() { StatusCode = StatusCodes.InternalServerError }.GetPackedResponse(true)); + } + return true; + } + else + { + return false; + } + } + return false; + } + + void IHttpModule.Tick(Server.Server server, HttpHandler handler) { } + } +} diff --git a/MTSC/Common/Http/ServerModules/IHttpModule.cs b/MTSC/Common/Http/ServerModules/IHttpModule.cs index 362716d..1670620 100644 --- a/MTSC/Common/Http/ServerModules/IHttpModule.cs +++ b/MTSC/Common/Http/ServerModules/IHttpModule.cs @@ -1,9 +1,5 @@ -using MTSC.Common; -using MTSC.Server; +using MTSC.Server; using MTSC.Server.Handlers; -using System; -using System.Collections.Generic; -using System.Text; namespace MTSC.Common.Http.ServerModules { diff --git a/MTSC/Exceptions/IncompleteHeaderKeyException.cs b/MTSC/Exceptions/IncompleteHeaderKeyException.cs new file mode 100644 index 0000000..273d36b --- /dev/null +++ b/MTSC/Exceptions/IncompleteHeaderKeyException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace MTSC.Exceptions +{ + public class IncompleteHeaderKeyException : Exception + { + public IncompleteHeaderKeyException() + { + } + + public IncompleteHeaderKeyException(string message) : base(message) + { + } + + public IncompleteHeaderKeyException(string message, Exception innerException) : base(message, innerException) + { + } + + protected IncompleteHeaderKeyException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/MTSC/Exceptions/IncompleteHeaderValueException.cs b/MTSC/Exceptions/IncompleteHeaderValueException.cs new file mode 100644 index 0000000..7d72e3e --- /dev/null +++ b/MTSC/Exceptions/IncompleteHeaderValueException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace MTSC.Exceptions +{ + class IncompleteHeaderValueException : Exception + { + public IncompleteHeaderValueException() + { + } + + public IncompleteHeaderValueException(string message) : base(message) + { + } + + public IncompleteHeaderValueException(string message, Exception innerException) : base(message, innerException) + { + } + + protected IncompleteHeaderValueException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/MTSC/Exceptions/IncompleteHttpVersionException.cs b/MTSC/Exceptions/IncompleteHttpVersionException.cs new file mode 100644 index 0000000..3faa8e6 --- /dev/null +++ b/MTSC/Exceptions/IncompleteHttpVersionException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace MTSC.Exceptions +{ + public class IncompleteHttpVersionException : Exception + { + public IncompleteHttpVersionException() + { + } + + public IncompleteHttpVersionException(string message) : base(message) + { + } + + public IncompleteHttpVersionException(string message, Exception innerException) : base(message, innerException) + { + } + + protected IncompleteHttpVersionException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/MTSC/Exceptions/IncompleteMethodException.cs b/MTSC/Exceptions/IncompleteMethodException.cs new file mode 100644 index 0000000..1801e2d --- /dev/null +++ b/MTSC/Exceptions/IncompleteMethodException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace MTSC.Exceptions +{ + public class IncompleteMethodException : Exception + { + public IncompleteMethodException() + { + } + + public IncompleteMethodException(string message) : base(message) + { + } + + public IncompleteMethodException(string message, Exception innerException) : base(message, innerException) + { + } + + protected IncompleteMethodException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/MTSC/Exceptions/IncompleteRequestBodyException.cs b/MTSC/Exceptions/IncompleteRequestBodyException.cs new file mode 100644 index 0000000..1624274 --- /dev/null +++ b/MTSC/Exceptions/IncompleteRequestBodyException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace MTSC.Exceptions +{ + public class IncompleteRequestBodyException : Exception + { + public IncompleteRequestBodyException() + { + } + + public IncompleteRequestBodyException(string message) : base(message) + { + } + + public IncompleteRequestBodyException(string message, Exception innerException) : base(message, innerException) + { + } + + protected IncompleteRequestBodyException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/MTSC/Exceptions/IncompleteRequestException.cs b/MTSC/Exceptions/IncompleteRequestException.cs new file mode 100644 index 0000000..eed93fe --- /dev/null +++ b/MTSC/Exceptions/IncompleteRequestException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace MTSC.Exceptions +{ + public class IncompleteRequestException : Exception + { + public IncompleteRequestException() + { + } + + public IncompleteRequestException(string message) : base(message) + { + } + + public IncompleteRequestException(string message, Exception innerException) : base(message, innerException) + { + } + + protected IncompleteRequestException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/MTSC/Exceptions/IncompleteRequestQueryException.cs b/MTSC/Exceptions/IncompleteRequestQueryException.cs new file mode 100644 index 0000000..f347077 --- /dev/null +++ b/MTSC/Exceptions/IncompleteRequestQueryException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace MTSC.Exceptions +{ + public class IncompleteRequestQueryException : Exception + { + public IncompleteRequestQueryException() + { + } + + public IncompleteRequestQueryException(string message) : base(message) + { + } + + public IncompleteRequestQueryException(string message, Exception innerException) : base(message, innerException) + { + } + + protected IncompleteRequestQueryException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/MTSC/Exceptions/IncompleteRequestURIException.cs b/MTSC/Exceptions/IncompleteRequestURIException.cs new file mode 100644 index 0000000..43ed9a4 --- /dev/null +++ b/MTSC/Exceptions/IncompleteRequestURIException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace MTSC.Exceptions +{ + public class IncompleteRequestURIException : Exception + { + public IncompleteRequestURIException() + { + } + + public IncompleteRequestURIException(string message) : base(message) + { + } + + public IncompleteRequestURIException(string message, Exception innerException) : base(message, innerException) + { + } + + protected IncompleteRequestURIException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/MTSC/Exceptions/MethodInvalidException.cs b/MTSC/Exceptions/InvalidMethodException.cs similarity index 59% rename from MTSC/Exceptions/MethodInvalidException.cs rename to MTSC/Exceptions/InvalidMethodException.cs index c974375..a46c4ed 100644 --- a/MTSC/Exceptions/MethodInvalidException.cs +++ b/MTSC/Exceptions/InvalidMethodException.cs @@ -8,21 +8,21 @@ namespace MTSC.Exceptions /// /// Invalid HTTP Method Exception. /// - public class MethodInvalidException : Exception + public class InvalidMethodException : Exception { - public MethodInvalidException() + public InvalidMethodException() { } - public MethodInvalidException(string message) : base(message) + public InvalidMethodException(string message) : base(message) { } - public MethodInvalidException(string message, Exception innerException) : base(message, innerException) + public InvalidMethodException(string message, Exception innerException) : base(message, innerException) { } - protected MethodInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) + protected InvalidMethodException(SerializationInfo info, StreamingContext context) : base(info, context) { } } diff --git a/MTSC/Exceptions/InvalidRequestQueryException.cs b/MTSC/Exceptions/InvalidRequestQueryException.cs new file mode 100644 index 0000000..b69cf81 --- /dev/null +++ b/MTSC/Exceptions/InvalidRequestQueryException.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.Serialization; + +namespace MTSC.Exceptions +{ + public class InvalidRequestQueryException : Exception + { + public InvalidRequestQueryException() + { + } + + public InvalidRequestQueryException(string message) : base(message) + { + } + + public InvalidRequestQueryException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidRequestQueryException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/MTSC/Server/Handlers/HttpHandler.cs b/MTSC/Server/Handlers/HttpHandler.cs index 5ae5f12..e7901d5 100644 --- a/MTSC/Server/Handlers/HttpHandler.cs +++ b/MTSC/Server/Handlers/HttpHandler.cs @@ -1,11 +1,11 @@ -using MTSC.Common; -using MTSC.Common.Http; +using MTSC.Common.Http; using MTSC.Common.Http.ServerModules; +using MTSC.Exceptions; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Text; -using System.Threading; +using System.IO; +using static MTSC.Common.Http.HttpMessage; namespace MTSC.Server.Handlers { @@ -19,7 +19,7 @@ namespace MTSC.Server.Handlers #region Fields List httpModules = new List(); ConcurrentQueue> messageQueue = new ConcurrentQueue>(); - ConcurrentDictionary fragmentedMessages = new ConcurrentDictionary(); + ConcurrentDictionary fragmentedMessages = new ConcurrentDictionary(); #endregion #region Constructors public HttpHandler() @@ -73,35 +73,51 @@ namespace MTSC.Server.Handlers /// bool IHandler.HandleReceivedMessage(Server server, ClientData client, Message message) { - HttpRequest request; - /* - * If there's an existing fragmented request, get it from the storage. - */ - if (fragmentedMessages.ContainsKey(client)) + // Parse the request. If the message is incomplete, return 100 and queue the message to be parsed later. + HttpRequest request = null; + try { - request = fragmentedMessages[client]; - request.AddToBody(message.MessageBytes); + byte[] messageBytes = null; + if (fragmentedMessages.ContainsKey(client)) + { + fragmentedMessages[client].Write(message.MessageBytes, 0, message.MessageBytes.Length); + messageBytes = fragmentedMessages[client].ToArray(); + } + else + { + messageBytes = message.MessageBytes; + } + request = HttpRequest.FromBytes(messageBytes); } - else + catch (Exception ex) when ( + ex is IncompleteHeaderKeyException || + ex is IncompleteHeaderValueException || + ex is IncompleteHttpVersionException || + ex is IncompleteMethodException || + ex is IncompleteRequestBodyException || + ex is IncompleteRequestQueryException || + ex is IncompleteRequestURIException || + ex is IncompleteRequestException) { - request = HttpRequest.FromBytes(message.MessageBytes); - } - - /* - * If the server hasn't received all the bytes specified by the request, - * add the request to storage and wait for the rest of bytes to be received. - */ - - if (request.Headers.ContainsHeader(HttpMessage.EntityHeaders.ContentLength) && - request.Body.Length < int.Parse(request.Headers[HttpMessage.EntityHeaders.ContentLength])) - { - fragmentedMessages[client] = request; - var continueResponse = new HttpResponse(); - continueResponse.StatusCode = HttpMessage.StatusCodes.Continue; - QueueResponse(client, continueResponse); + if (fragmentedMessages.ContainsKey(client)) + { + fragmentedMessages[client].Write(message.MessageBytes, 0, message.MessageBytes.Length); + } + else + { + fragmentedMessages[client] = new MemoryStream(); + fragmentedMessages[client].Write(message.MessageBytes, 0, message.MessageBytes.Length); + } + Return100Continue(server, client); return true; } - else + catch (Exception e) + { + throw e; + } + + // The message has been parsed. If there was a cache for the current message, remove it. + if (fragmentedMessages.ContainsKey(client)) { fragmentedMessages.TryRemove(client, out _); } @@ -165,5 +181,12 @@ namespace MTSC.Server.Handlers } } #endregion + private void Return100Continue(Server server, ClientData clientData) + { + HttpResponse httpResponse = new HttpResponse(); + httpResponse.StatusCode = StatusCodes.Continue; + httpResponse.Headers[GeneralHeaders.Connection] = "keep-alive"; + server.QueueMessage(clientData, httpResponse.GetPackedResponse(false)); + } } } diff --git a/MTSC/Server/Handlers/HttpRoutingHandler.cs b/MTSC/Server/Handlers/HttpRoutingHandler.cs deleted file mode 100644 index 034602d..0000000 --- a/MTSC/Server/Handlers/HttpRoutingHandler.cs +++ /dev/null @@ -1,119 +0,0 @@ -using MTSC.Common.Http; -using MTSC.Common.Http.RoutingModules; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using static MTSC.Common.Http.HttpMessage; - -namespace MTSC.Server.Handlers -{ - public class HttpRoutingHandler : IHandler - { - ConcurrentDictionary fragmentedMessages = new ConcurrentDictionary(); - private Dictionary)>> moduleDictionary = - new Dictionary)>>(); - - public HttpRoutingHandler() - { - foreach (HttpMethods method in (HttpMethods[])Enum.GetValues(typeof(HttpMethods))) - { - moduleDictionary[method] = new Dictionary)>(); - } - } - - public HttpRoutingHandler AddRoute(HttpMethods method, string uri, IHttpRoute routeModule, Func routeEnabler = null) - { - if(routeEnabler == null) - { - routeEnabler = (request, client) => { return true; }; - } - moduleDictionary[method][uri] = (routeModule, routeEnabler); - return this; - } - - void IHandler.ClientRemoved(Server server, ClientData client) { } - - bool IHandler.HandleClient(Server server, ClientData client) => false; - - bool IHandler.HandleReceivedMessage(Server server, ClientData client, Message message) - { - HttpRequest request; - /* - * If there's an existing fragmented request, get it from the storage. - */ - if (fragmentedMessages.ContainsKey(client)) - { - request = fragmentedMessages[client]; - request.AddToBody(message.MessageBytes); - } - else - { - request = HttpRequest.FromBytes(message.MessageBytes); - } - - /* - * If the server hasn't received all the bytes specified by the request, - * add the request to storage and wait for the rest of bytes to be received. - */ - - if (request.Headers.ContainsHeader(HttpMessage.EntityHeaders.ContentLength) && - request.Body.Length < int.Parse(request.Headers[HttpMessage.EntityHeaders.ContentLength])) - { - fragmentedMessages[client] = request; - var continueResponse = new HttpResponse(); - continueResponse.StatusCode = HttpMessage.StatusCodes.Continue; - server.QueueMessage(client, continueResponse.GetPackedResponse(true)); - return true; - } - else - { - fragmentedMessages.TryRemove(client, out _); - } - - HttpResponse response = new HttpResponse(); - if (request.Headers.ContainsHeader(HttpMessage.GeneralHeaders.Connection) && - request.Headers[HttpMessage.GeneralHeaders.Connection].ToLower() == "close") - { - response.Headers[HttpMessage.GeneralHeaders.Connection] = "close"; - client.ToBeRemoved = true; - } - else - { - response.Headers[HttpMessage.GeneralHeaders.Connection] = "keep-alive"; - } - - /* - * Now find if a routing module exists. If not let other handlers try and handle the message. - */ - if (moduleDictionary[request.Method].ContainsKey(request.RequestURI)) - { - (var module, var routeEnabler) = moduleDictionary[request.Method][request.RequestURI]; - if(routeEnabler.Invoke(request, client)) - { - try - { - server.QueueMessage(client, module.HandleRequest(request, client, server).GetPackedResponse(true)); - } - catch(Exception e) - { - server.LogDebug("Exception: " + e.Message); - server.LogDebug("Stacktrace: " + e.StackTrace); - server.QueueMessage(client, new HttpResponse() { StatusCode = StatusCodes.InternalServerError }.GetPackedResponse(true)); - } - return true; - } - else - { - return false; - } - } - return false; - } - - bool IHandler.HandleSendMessage(Server server, ClientData client, ref Message message) => false; - - bool IHandler.PreHandleReceivedMessage(Server server, ClientData client, ref Message message) => false; - - void IHandler.Tick(Server server) { } - } -}