Introduce cancellation tokens for HttpRoutes (#7)

Return 404 on NotFound
RouteFilters now can handle exceptions
This commit is contained in:
2022-01-18 14:31:59 +01:00
committed by GitHub
parent 35a45a395f
commit 9d7a4ff75c
9 changed files with 136 additions and 27 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ namespace MTSC.UnitTests
.WithHeartbeatEnabled(true)
.WithHeartbeatFrequency(TimeSpan.FromMilliseconds(100)))
.AddHandler(new HttpRoutingHandler()
.WithReturn500OnException(true)
.WithReturn500OnUnhandledException(true)
.AddRoute<ExceptionThrowingModule>(HttpMessage.HttpMethods.Get, "throw")
.AddRoute<Http200Module>(HttpMessage.HttpMethods.Get, "")
.AddRoute<TestQueryModule>(HttpMessage.HttpMethods.Get, "query")
@@ -1,6 +1,5 @@
using MTSC.Common.Http;
using MTSC.Common.Http.Attributes;
using MTSC.ServerSide;
namespace MTSC.UnitTests.RoutingModules
{
@@ -9,16 +8,16 @@ namespace MTSC.UnitTests.RoutingModules
public static bool RequestCalled { get; private set; }
public static bool ResponseCalled { get; private set; }
public override RouteEnablerResponse HandleRequest(Server server, ClientData clientData, HttpRequest httpRequest)
public override RouteEnablerResponse HandleRequest(RouteContext routeContext)
{
RequestCalled = true;
return base.HandleRequest(server, clientData, httpRequest);
return base.HandleRequest(routeContext);
}
public override void HandleResponse(Server server, ClientData clientData, HttpResponse httpResponse)
public override void HandleResponse(RouteContext routeContext)
{
ResponseCalled = true;
base.HandleResponse(server, clientData, httpResponse);
base.HandleResponse(routeContext);
}
}
}
@@ -1,15 +1,19 @@
using MTSC.ServerSide;
using System;
using System;
namespace MTSC.Common.Http.Attributes
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public abstract class RouteFilterAttribute : Attribute
{
public virtual RouteEnablerResponse HandleRequest(Server server, ClientData clientData, HttpRequest httpRequest) => RouteEnablerResponse.Accept;
public virtual RouteEnablerResponse HandleRequest(RouteContext routeFilterContext) => RouteEnablerResponse.Accept;
public virtual void HandleResponse(Server server, ClientData clientData, HttpResponse httpResponse)
public virtual void HandleResponse(RouteContext routeFilterContext)
{
}
public virtual RouteFilterExceptionHandlingResponse HandleException(RouteContext routeFilterContext, Exception exception)
{
return RouteFilterExceptionHandlingResponse.NotHandled;
}
}
}
+24
View File
@@ -0,0 +1,24 @@
using MTSC.ServerSide;
using System.Threading;
namespace MTSC.Common.Http
{
public sealed class RouteContext
{
public Server Server { get; }
public HttpRequest HttpRequest { get; }
public ClientData Client { get; }
public HttpResponse HttpResponse { get; set; }
public CancellationToken CancelRequest => this.Client.CancellationToken;
public RouteContext(
Server server,
HttpRequest httpRequest,
ClientData clientData)
{
this.Server = server;
this.HttpRequest = httpRequest;
this.Client = clientData;
}
}
}
@@ -0,0 +1,30 @@
using System;
namespace MTSC.Common.Http
{
public abstract class RouteFilterExceptionHandlingResponse
{
internal RouteFilterExceptionHandlingResponse()
{
}
public sealed class NotHandledResponse : RouteFilterExceptionHandlingResponse
{
internal NotHandledResponse()
{
}
}
public sealed class HandledResponse : RouteFilterExceptionHandlingResponse
{
public HttpResponse HttpResponse { get; }
internal HandledResponse(HttpResponse httpResponse)
{
this.HttpResponse = httpResponse ?? throw new ArgumentNullException(nameof(httpResponse));
}
}
public static NotHandledResponse NotHandled => new();
public static HandledResponse Handled(HttpResponse httpResponse) => new(httpResponse);
}
}
@@ -36,7 +36,7 @@ namespace MTSC.Common.Http.RoutingModules
}
catch
{
if (this.HttpRoutingHandler.Return500OnException is true)
if (this.HttpRoutingHandler.Return500OnUnhandledException is true)
{
return InternalServerError500;
}
+3 -3
View File
@@ -5,13 +5,13 @@
<TargetFrameworks>net48;netstandard2.0;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
<ApplicationIcon />
<StartupObject />
<Version>5.0</Version>
<Version>5.1</Version>
<LangVersion>latest</LangVersion>
<Authors>Alexandru-Victor Macocian</Authors>
<Product>MTSC</Product>
<Description>Modular TCP Server and Client</Description>
<AssemblyVersion>5.0.0.0</AssemblyVersion>
<FileVersion>5.0.0.0</FileVersion>
<AssemblyVersion>5.1.0.0</AssemblyVersion>
<FileVersion>5.1.0.0</FileVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Platforms>AnyCPU;x64</Platforms>
<PackageProjectUrl>https://github.com/AlexMacocian/MTSC</PackageProjectUrl>
+6
View File
@@ -3,6 +3,7 @@ using MTSC.ServerSide.Handlers;
using System;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading;
namespace MTSC.ServerSide
{
@@ -13,6 +14,9 @@ namespace MTSC.ServerSide
{
private readonly ProducerConsumerQueue<Message> messageQueue = new();
private CancellationTokenSource cancellationTokenSource = new();
public CancellationToken CancellationToken => this.cancellationTokenSource.Token;
public Socket Socket { get; }
/// <summary>
/// Latest datetime when a message has been received from the client
@@ -115,6 +119,8 @@ namespace MTSC.ServerSide
{
if (disposing)
{
this.cancellationTokenSource.Cancel();
this.cancellationTokenSource = null;
this.SafeNetworkStream?.Dispose();
this.SslStream?.Dispose();
this.Resources?.Dispose();
+59 -13
View File
@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using static MTSC.Common.Http.HttpMessage;
namespace MTSC.ServerSide.Handlers
@@ -43,7 +44,8 @@ namespace MTSC.ServerSide.Handlers
public TimeSpan FragmentsExpirationTime { get; set; } = TimeSpan.FromSeconds(15);
public double MaximumRequestSize { get; set; } = double.MaxValue;
public bool Return500OnException { get; set; } = true;
public bool Return500OnUnhandledException { get; set; } = true;
public bool Return404OnNotFound { get; set; } = false;
public HttpRoutingHandler()
{
@@ -53,9 +55,14 @@ namespace MTSC.ServerSide.Handlers
}
}
public HttpRoutingHandler WithReturn500OnException(bool return500OnException)
public HttpRoutingHandler WithReturn404OnNotFound(bool return404OnNotFound)
{
this.Return500OnException = return500OnException;
this.Return404OnNotFound = return404OnNotFound;
return this;
}
public HttpRoutingHandler WithReturn500OnUnhandledException(bool return500OnException)
{
this.Return500OnUnhandledException = return500OnException;
return this;
}
public HttpRoutingHandler AddHttpLogger(IHttpLogger logger)
@@ -124,9 +131,9 @@ namespace MTSC.ServerSide.Handlers
{
client.ResetAffinityIfMe(this);
client.Resources.RemoveResource<FragmentedMessage>();
if (this.Return500OnException)
if (this.Return500OnUnhandledException)
{
var response = new HttpResponse() { StatusCode = StatusCodes.InternalServerError };
var response = InternalServerError500;
foreach (var httpLogger in this.httpLoggers)
{
httpLogger.LogResponse(server, this, client, response);
@@ -212,10 +219,11 @@ namespace MTSC.ServerSide.Handlers
httpLogger.LogRequest(server, this, client, request);
}
var routeContext = new RouteContext(server, request, client);
foreach(var filterType in filterTypes)
{
var filter = server.ServiceManager.GetService(filterType) as RouteFilterAttribute;
var filterResponse = filter.HandleRequest(server, client, request);
var filterResponse = filter.HandleRequest(routeContext);
if (filterResponse is RouteEnablerResponse.RouteEnablerResponseAccept)
{
continue;
@@ -236,14 +244,8 @@ namespace MTSC.ServerSide.Handlers
}
this.SetModuleProperties(module, request, urlValues);
module.CallHandleRequest(request).ContinueWith((task) =>
this.RouteHandleRequest(module, routeContext, filterTypes).ContinueWith(task =>
{
foreach(var filterType in filterTypes)
{
var filter = server.ServiceManager.GetService(filterType) as RouteFilterAttribute;
filter.HandleResponse(server, client, task.Result);
}
this.QueueResponse(client, task.Result);
});
@@ -343,11 +345,46 @@ namespace MTSC.ServerSide.Handlers
{
client.ResetAffinityIfMe(this);
client.Resources.RemoveResource<FragmentedMessage>();
if (this.Return404OnNotFound)
{
this.QueueResponse(client, NotFound404);
}
return false;
}
}
}
private async Task<HttpResponse> RouteHandleRequest(HttpRouteBase httpRouteBase, RouteContext routeContext, List<Type> filterTypes)
{
try
{
var response = await httpRouteBase.CallHandleRequest(routeContext.HttpRequest);
routeContext.HttpResponse = response;
foreach (var filterType in filterTypes)
{
var filter = routeContext.Server.ServiceManager.GetService(filterType) as RouteFilterAttribute;
filter.HandleResponse(routeContext);
}
return response;
}
catch (Exception ex)
{
foreach (var filterType in filterTypes)
{
var filter = routeContext.Server.ServiceManager.GetService(filterType) as RouteFilterAttribute;
if (filter.HandleException(routeContext, ex) is RouteFilterExceptionHandlingResponse.HandledResponse handledExceptionResponse)
{
routeContext.HttpResponse = handledExceptionResponse.HttpResponse;
return handledExceptionResponse.HttpResponse;
}
}
throw;
}
}
private HttpRouteBase GetRoute(Slim.IServiceProvider serviceProvider, Type routeType, ClientData client, Server server)
{
if (!typeof(HttpRouteBase).IsAssignableFrom(routeType))
@@ -511,5 +548,14 @@ namespace MTSC.ServerSide.Handlers
convertedValue = null;
return false;
}
private static HttpResponse NotFound404 => new()
{
StatusCode = StatusCodes.NotFound
};
private static HttpResponse InternalServerError500 => new()
{
StatusCode = StatusCodes.InternalServerError
};
}
}