Improve httproutinghandler error handling

This commit is contained in:
Alexandru Macocian
2021-10-09 21:14:10 +02:00
parent 62b0364b8f
commit 13681f8c1e
5 changed files with 154 additions and 132 deletions
+3
View File
@@ -5,6 +5,7 @@ using MTSC.Common.Http.RoutingModules;
using MTSC.Exceptions;
using MTSC.ServerSide.Handlers;
using MTSC.ServerSide.Schedulers;
using MTSC.ServerSide.UsageMonitors;
using MTSC.UnitTests.RoutingModules;
using Newtonsoft.Json;
using System;
@@ -38,6 +39,8 @@ namespace MTSC.UnitTests
Server = new ServerSide.Server(800)
.WithReadTimeout(TimeSpan.FromMilliseconds(1000))
.WithClientCertificate(false)
.AddServerUsageMonitor(new InactiveTimeoutMonitor()
.WithTimeout(TimeSpan.FromSeconds(30)))
.AddHandler(new WebsocketRoutingHandler()
.AddRoute<EchoWebsocketModule>("echo")
.AddRoute<EchoWebsocketModule2>("echo2")
+6
View File
@@ -11,6 +11,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTSC-TestClient", "MTSC-Tes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTSC.UnitTests", "MTSC.UnitTests\MTSC.UnitTests.csproj", "{55CC0666-5599-4C3C-ADB5-E48F06361C52}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pipelines", "Pipelines", "{362EC64D-6F18-43D3-8C5E-7E3C4DBDC640}"
ProjectSection(SolutionItems) = preProject
.github\workflows\cd.yaml = .github\workflows\cd.yaml
.github\workflows\ci.yaml = .github\workflows\ci.yaml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+3 -3
View File
@@ -5,13 +5,13 @@
<TargetFrameworks>netcoreapp2.1;net48;netstandard2.0;netcoreapp3.1;net5.0</TargetFrameworks>
<ApplicationIcon />
<StartupObject />
<Version>4.5</Version>
<Version>4.5.1</Version>
<LangVersion>latest</LangVersion>
<Authors>Alexandru-Victor Macocian</Authors>
<Product>MTSC</Product>
<Description>Modular TCP Server and Client</Description>
<AssemblyVersion>4.5.0.0</AssemblyVersion>
<FileVersion>4.5.0.0</FileVersion>
<AssemblyVersion>4.5.1.0</AssemblyVersion>
<FileVersion>4.5.1.0</FileVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Platforms>AnyCPU;x64</Platforms>
<PackageProjectUrl>https://github.com/AlexMacocian/MTSC</PackageProjectUrl>
+114 -129
View File
@@ -116,124 +116,26 @@ namespace MTSC.ServerSide.Handlers
bool IHandler.HandleReceivedMessage(Server server, ClientData client, Message message)
{
/*
* If a fragmented request exists, add the new messages to the body.
* Else, parse the messages into a partial request. If this causes an exception, let it throw out of the handler,
* cause the handler needs at least valid headers to work.
*/
PartialHttpRequest request;
if (client.Resources.TryGetResource<FragmentedMessage>(out var fragmentedMessage))
try
{
var bytesToBeAdded = message.MessageBytes.TrimTrailingNullBytes();
if(fragmentedMessage.PartialRequest.HeaderByteCount +
fragmentedMessage.PartialRequest.Body.Length +
message.MessageLength > this.MaximumRequestSize)
{
this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request exceeded [{this.MaximumRequestSize}] bytes!" });
client.ResetAffinityIfMe(this);
client.Resources.RemoveResource<FragmentedMessage>();
client.Resources.RemoveResourceIfExists<RequestMapping>();
}
fragmentedMessage.AddToMessage(bytesToBeAdded);
request = fragmentedMessage.PartialRequest;
return this.HandleMessageInternal(client, server, message);
}
else
catch(Exception)
{
if (message.MessageLength > this.MaximumRequestSize)
client.ResetAffinityIfMe(this);
client.Resources.RemoveResource<FragmentedMessage>();
if (this.Return500OnException)
{
this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request exceeded [{this.MaximumRequestSize}] bytes!" });
return false;
}
request = PartialHttpRequest.FromBytes(message.MessageBytes.TrimTrailingNullBytes());
if (!request.Complete)
{
client.Resources.SetResource(new FragmentedMessage { LastReceived = DateTime.Now, PartialRequest = request });
client.SetAffinity(this);
if (request.Headers.ContainsHeader(RequestHeaders.Expect) &&
request.Headers[RequestHeaders.Expect].Equals("100-continue", StringComparison.OrdinalIgnoreCase))
var response = new HttpResponse() { StatusCode = StatusCodes.InternalServerError };
foreach (var httpLogger in this.httpLoggers)
{
server.LogDebug("Returning 100-Continue");
var contResponse = new HttpResponse { StatusCode = HttpMessage.StatusCodes.Continue };
contResponse.Headers[HttpMessage.GeneralHeaders.Connection] = "keep-alive";
this.QueueResponse(client, contResponse);
httpLogger.LogResponse(server, this, client, response);
}
}
}
/*
* Once headers are loaded, check if mapping exists. If mapping exists between request and module,
* verify that the request is complete and send it to module.
* Otherwise, if the request is complete, send it to the mapped module. If the request is not complete,
* handle it and return.
*/
this.QueueResponse(client, response);
}
if (client.Resources.TryGetResource<RequestMapping>(out var mapping))
{
if (request.Complete)
{
client.Resources.RemoveResource<RequestMapping>();
client.Resources.RemoveResource<FragmentedMessage>();
client.ResetAffinityIfMe(this);
this.HandleCompleteRequest(client, server, request.ToRequest(), mapping.MappedModule, mapping.UrlValues, mapping.RouteEnabler);
return true;
}
else
{
this.HandleIncompleteRequest(server, client.Resources.GetResource<FragmentedMessage>());
return true;
}
}
else
{
if (this.TryMatchUrl(request.Method, request.RequestURI, out var urlValues, out var routeType, out var routeEnabler))
{
var scopedServiceProvider = server.ServiceManager.CreateScope();
try
{
var module = this.GetRoute(scopedServiceProvider, routeType, client, server);
if (request.Complete)
{
var httpRequest = request.ToRequest();
client.ResetAffinityIfMe(this);
return this.HandleCompleteRequest(client, server, httpRequest, module, urlValues, routeEnabler);
}
else
{
client.Resources.SetResource(new RequestMapping { MappedModule = module, RouteEnabler = routeEnabler, UrlValues = urlValues });
return true;
}
}
catch(Exception e)
{
server.LogDebug("Exception: " + e.Message);
server.LogDebug("Stacktrace: " + e.StackTrace);
client.ResetAffinityIfMe(this);
client.Resources.RemoveResource<FragmentedMessage>();
if (this.Return500OnException)
{
var response = new HttpResponse() { StatusCode = StatusCodes.InternalServerError };
foreach (var httpLogger in this.httpLoggers)
{
httpLogger.LogResponse(server, this, client, response);
}
this.QueueResponse(client, response);
return true;
}
else
{
return false;
}
}
}
else
{
client.ResetAffinityIfMe(this);
client.Resources.RemoveResource<FragmentedMessage>();
return false;
}
throw;
}
}
@@ -299,30 +201,15 @@ namespace MTSC.ServerSide.Handlers
if (routeEnablerResponse is RouteEnablerResponse.RouteEnablerResponseAccept)
{
this.SetModuleProperties(module, request, urlValues);
try
module.CallHandleRequest(request).ContinueWith((task) =>
{
module.CallHandleRequest(request).ContinueWith((task) =>
{
foreach (var httpLogger in this.httpLoggers)
{
httpLogger.LogResponse(server, this, client, task.Result);
}
this.QueueResponse(client, task.Result);
});
}
catch (Exception e)
{
server.LogDebug("Exception: " + e.Message);
server.LogDebug("Stacktrace: " + e.StackTrace);
var response = new HttpResponse() { StatusCode = StatusCodes.InternalServerError };
foreach (var httpLogger in this.httpLoggers)
{
httpLogger.LogResponse(server, this, client, response);
httpLogger.LogResponse(server, this, client, task.Result);
}
this.QueueResponse(client, response);
}
this.QueueResponse(client, task.Result);
});
return true;
}
@@ -365,6 +252,104 @@ namespace MTSC.ServerSide.Handlers
public Func<Server, HttpRequest, ClientData, RouteEnablerResponse> RouteEnabler { get; set; }
}
private bool HandleMessageInternal(ClientData client, Server server, Message message)
{
/*
* If a fragmented request exists, add the new messages to the body.
* Else, parse the messages into a partial request. If this causes an exception, let it throw out of the handler,
* cause the handler needs at least valid headers to work.
*/
PartialHttpRequest request;
if (client.Resources.TryGetResource<FragmentedMessage>(out var fragmentedMessage))
{
var bytesToBeAdded = message.MessageBytes.TrimTrailingNullBytes();
if (fragmentedMessage.PartialRequest.HeaderByteCount +
fragmentedMessage.PartialRequest.Body.Length +
message.MessageLength > this.MaximumRequestSize)
{
this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request exceeded [{this.MaximumRequestSize}] bytes!" });
client.ResetAffinityIfMe(this);
client.Resources.RemoveResource<FragmentedMessage>();
client.Resources.RemoveResourceIfExists<RequestMapping>();
}
fragmentedMessage.AddToMessage(bytesToBeAdded);
request = fragmentedMessage.PartialRequest;
}
else
{
if (message.MessageLength > this.MaximumRequestSize)
{
this.QueueResponse(client, new HttpResponse { StatusCode = StatusCodes.BadRequest, BodyString = $"Request exceeded [{this.MaximumRequestSize}] bytes!" });
return false;
}
request = PartialHttpRequest.FromBytes(message.MessageBytes.TrimTrailingNullBytes());
if (!request.Complete)
{
client.Resources.SetResource(new FragmentedMessage { LastReceived = DateTime.Now, PartialRequest = request });
client.SetAffinity(this);
if (request.Headers.ContainsHeader(RequestHeaders.Expect) &&
request.Headers[RequestHeaders.Expect].Equals("100-continue", StringComparison.OrdinalIgnoreCase))
{
server.LogDebug("Returning 100-Continue");
var contResponse = new HttpResponse { StatusCode = HttpMessage.StatusCodes.Continue };
contResponse.Headers[HttpMessage.GeneralHeaders.Connection] = "keep-alive";
this.QueueResponse(client, contResponse);
}
}
}
/*
* Once headers are loaded, check if mapping exists. If mapping exists between request and module,
* verify that the request is complete and send it to module.
* Otherwise, if the request is complete, send it to the mapped module. If the request is not complete,
* handle it and return.
*/
if (client.Resources.TryGetResource<RequestMapping>(out var mapping))
{
if (request.Complete)
{
client.Resources.RemoveResource<RequestMapping>();
client.Resources.RemoveResource<FragmentedMessage>();
client.ResetAffinityIfMe(this);
this.HandleCompleteRequest(client, server, request.ToRequest(), mapping.MappedModule, mapping.UrlValues, mapping.RouteEnabler);
return true;
}
else
{
this.HandleIncompleteRequest(server, client.Resources.GetResource<FragmentedMessage>());
return true;
}
}
else
{
if (this.TryMatchUrl(request.Method, request.RequestURI, out var urlValues, out var routeType, out var routeEnabler))
{
var scopedServiceProvider = server.ServiceManager.CreateScope();
var module = this.GetRoute(scopedServiceProvider, routeType, client, server);
if (request.Complete)
{
var httpRequest = request.ToRequest();
client.ResetAffinityIfMe(this);
return this.HandleCompleteRequest(client, server, httpRequest, module, urlValues, routeEnabler);
}
else
{
client.Resources.SetResource(new RequestMapping { MappedModule = module, RouteEnabler = routeEnabler, UrlValues = urlValues });
return true;
}
}
else
{
client.ResetAffinityIfMe(this);
client.Resources.RemoveResource<FragmentedMessage>();
return false;
}
}
}
private HttpRouteBase GetRoute(Slim.IServiceProvider serviceProvider, Type routeType, ClientData client, Server server)
{
if (!typeof(HttpRouteBase).IsAssignableFrom(routeType))
@@ -0,0 +1,28 @@
using System;
using System.Linq;
namespace MTSC.ServerSide.UsageMonitors
{
public sealed class InactiveTimeoutMonitor : IServerUsageMonitor
{
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(5);
public InactiveTimeoutMonitor WithTimeout(TimeSpan timeout)
{
this.Timeout = timeout;
return this;
}
public void Tick(Server server)
{
var procTime = DateTime.Now;
foreach(var client in server.Clients)
{
if (procTime - client.LastActivityTime > this.Timeout)
{
client.ToBeRemoved = true;
}
}
}
}
}