mirror of
https://github.com/AlexMacocian/MTSC.git
synced 2026-07-24 03:56:32 +00:00
Started work on resilient HttpHandler that repackages broken requests
This commit is contained in:
+30
-11
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<HttpMethods, Dictionary<string, (IHttpRoute, Func<HttpRequest, ClientData, bool>)>> moduleDictionary =
|
||||
new Dictionary<HttpMethods, Dictionary<string, (IHttpRoute, Func<HttpRequest, ClientData, bool>)>>();
|
||||
|
||||
public HttpRoutingModule()
|
||||
{
|
||||
foreach (HttpMethods method in (HttpMethods[])Enum.GetValues(typeof(HttpMethods)))
|
||||
{
|
||||
moduleDictionary[method] = new Dictionary<string, (IHttpRoute, Func<HttpRequest, ClientData, bool>)>();
|
||||
}
|
||||
}
|
||||
|
||||
public HttpRoutingModule AddRoute(HttpMethods method, string uri, IHttpRoute routeModule, Func<HttpRequest, ClientData, bool> 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) { }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -8,21 +8,21 @@ namespace MTSC.Exceptions
|
||||
/// <summary>
|
||||
/// Invalid HTTP Method Exception.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<IHttpModule> httpModules = new List<IHttpModule>();
|
||||
ConcurrentQueue<Tuple<ClientData, HttpResponse>> messageQueue = new ConcurrentQueue<Tuple<ClientData, HttpResponse>>();
|
||||
ConcurrentDictionary<ClientData, HttpRequest> fragmentedMessages = new ConcurrentDictionary<ClientData, HttpRequest>();
|
||||
ConcurrentDictionary<ClientData, MemoryStream> fragmentedMessages = new ConcurrentDictionary<ClientData, MemoryStream>();
|
||||
#endregion
|
||||
#region Constructors
|
||||
public HttpHandler()
|
||||
@@ -73,35 +73,51 @@ namespace MTSC.Server.Handlers
|
||||
/// <returns></returns>
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ClientData, HttpRequest> fragmentedMessages = new ConcurrentDictionary<ClientData, HttpRequest>();
|
||||
private Dictionary<HttpMethods, Dictionary<string, (IHttpRoute, Func<HttpRequest, ClientData, bool>)>> moduleDictionary =
|
||||
new Dictionary<HttpMethods, Dictionary<string, (IHttpRoute, Func<HttpRequest, ClientData, bool>)>>();
|
||||
|
||||
public HttpRoutingHandler()
|
||||
{
|
||||
foreach (HttpMethods method in (HttpMethods[])Enum.GetValues(typeof(HttpMethods)))
|
||||
{
|
||||
moduleDictionary[method] = new Dictionary<string, (IHttpRoute, Func<HttpRequest, ClientData, bool>)>();
|
||||
}
|
||||
}
|
||||
|
||||
public HttpRoutingHandler AddRoute(HttpMethods method, string uri, IHttpRoute routeModule, Func<HttpRequest, ClientData, bool> 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) { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user