diff --git a/MTSC.UnitTests/E2ETests.cs b/MTSC.UnitTests/E2ETests.cs index 80fe23c..dec674e 100644 --- a/MTSC.UnitTests/E2ETests.cs +++ b/MTSC.UnitTests/E2ETests.cs @@ -50,7 +50,7 @@ namespace MTSC.UnitTests .WithHeartbeatEnabled(true) .WithHeartbeatFrequency(TimeSpan.FromMilliseconds(100))) .AddHandler(new HttpRoutingHandler() - .WithReturn500OnException(true) + .WithReturn500OnUnhandledException(true) .AddRoute(HttpMessage.HttpMethods.Get, "throw") .AddRoute(HttpMessage.HttpMethods.Get, "") .AddRoute(HttpMessage.HttpMethods.Get, "query") diff --git a/MTSC.UnitTests/RoutingModules/NonActioningFilterAttribute.cs b/MTSC.UnitTests/RoutingModules/NonActioningFilterAttribute.cs index 8d40c37..afb24aa 100644 --- a/MTSC.UnitTests/RoutingModules/NonActioningFilterAttribute.cs +++ b/MTSC.UnitTests/RoutingModules/NonActioningFilterAttribute.cs @@ -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); } } } diff --git a/MTSC/Common/Http/Attributes/RouteFilterAttribute.cs b/MTSC/Common/Http/Attributes/RouteFilterAttribute.cs index b5c88cc..34e4272 100644 --- a/MTSC/Common/Http/Attributes/RouteFilterAttribute.cs +++ b/MTSC/Common/Http/Attributes/RouteFilterAttribute.cs @@ -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; + } } } diff --git a/MTSC/Common/Http/RouteContext.cs b/MTSC/Common/Http/RouteContext.cs new file mode 100644 index 0000000..a1c45da --- /dev/null +++ b/MTSC/Common/Http/RouteContext.cs @@ -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; + } + } +} diff --git a/MTSC/Common/Http/RouteFilterExceptionHandlingResponse.cs b/MTSC/Common/Http/RouteFilterExceptionHandlingResponse.cs new file mode 100644 index 0000000..287b746 --- /dev/null +++ b/MTSC/Common/Http/RouteFilterExceptionHandlingResponse.cs @@ -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); + } +} diff --git a/MTSC/Common/Http/RoutingModules/HttpRouteBase.cs b/MTSC/Common/Http/RoutingModules/HttpRouteBase.cs index e9630f3..0552b26 100644 --- a/MTSC/Common/Http/RoutingModules/HttpRouteBase.cs +++ b/MTSC/Common/Http/RoutingModules/HttpRouteBase.cs @@ -36,7 +36,7 @@ namespace MTSC.Common.Http.RoutingModules } catch { - if (this.HttpRoutingHandler.Return500OnException is true) + if (this.HttpRoutingHandler.Return500OnUnhandledException is true) { return InternalServerError500; } diff --git a/MTSC/MTSC.csproj b/MTSC/MTSC.csproj index b62ccd1..9772882 100644 --- a/MTSC/MTSC.csproj +++ b/MTSC/MTSC.csproj @@ -5,13 +5,13 @@ net48;netstandard2.0;netcoreapp3.1;net5.0;net6.0 - 5.0 + 5.1 latest Alexandru-Victor Macocian MTSC Modular TCP Server and Client - 5.0.0.0 - 5.0.0.0 + 5.1.0.0 + 5.1.0.0 true AnyCPU;x64 https://github.com/AlexMacocian/MTSC diff --git a/MTSC/ServerSide/ClientData.cs b/MTSC/ServerSide/ClientData.cs index 451eb81..ce78674 100644 --- a/MTSC/ServerSide/ClientData.cs +++ b/MTSC/ServerSide/ClientData.cs @@ -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 messageQueue = new(); + private CancellationTokenSource cancellationTokenSource = new(); + + public CancellationToken CancellationToken => this.cancellationTokenSource.Token; public Socket Socket { get; } /// /// 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(); diff --git a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs index 9b5caa5..5e03731 100644 --- a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs +++ b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs @@ -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(); - 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(); + if (this.Return404OnNotFound) + { + this.QueueResponse(client, NotFound404); + } + return false; } } } + private async Task RouteHandleRequest(HttpRouteBase httpRouteBase, RouteContext routeContext, List 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 + }; } }