mirror of
https://github.com/AlexMacocian/MTSC.git
synced 2026-07-15 14:59:33 +00:00
Improve httproutinghandler error handling
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user