From 627faa271a5f624bb730bdd1d1e64b6260161244 Mon Sep 17 00:00:00 2001 From: Archivelit Date: Tue, 6 Jan 2026 12:24:43 +0100 Subject: [PATCH 1/2] Add events for cases where error returned --- .../Adapters/ParserEventAdapter.cs | 14 +++++++++++++- .../Infrastructure/Routing/src/GlobalUsings.cs | 3 ++- .../Routing/src/LiteHttp.Routing.csproj | 1 + .../src/LiteHttp/Routing/RouterEventAdapter.cs | 14 +++++++++++--- .../src/LiteHttp/Server/EventDriven/Binder.cs | 8 +++++++- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/Server/Infrastructure/RequestProcessors/src/LiteHttp/RequestProcessors/Adapters/ParserEventAdapter.cs b/src/Server/Infrastructure/RequestProcessors/src/LiteHttp/RequestProcessors/Adapters/ParserEventAdapter.cs index db2f415..aca3488 100644 --- a/src/Server/Infrastructure/RequestProcessors/src/LiteHttp/RequestProcessors/Adapters/ParserEventAdapter.cs +++ b/src/Server/Infrastructure/RequestProcessors/src/LiteHttp/RequestProcessors/Adapters/ParserEventAdapter.cs @@ -1,4 +1,6 @@ -namespace LiteHttp.RequestProcessors.Adapters; +using LiteHttp.Helpers; + +namespace LiteHttp.RequestProcessors.Adapters; public sealed class ParserEventAdapter { @@ -9,6 +11,8 @@ public void Handle(ConnectionContext connectionContext) var buffer = connectionContext.SocketEventArgs.Buffer; var result = _parser.Parse(buffer); + if (!result.Success) OnParsingError(connectionContext, InternalActionResults.BadRequest()); + connectionContext.HttpContext = result.Value; OnParsed(connectionContext); @@ -19,4 +23,12 @@ public void Handle(ConnectionContext connectionContext) public void SubscribeToParsed(Action handler) => Parsed += handler; public void UnsubscribeParsed(Action handler) => Parsed += handler; + + + private event Action ParsingError; + private void OnParsingError(ConnectionContext c, IActionResult result) => ParsingError?.Invoke(c, result); + + public void SubscribeToParsingError(Action handler) => ParsingError += handler; + public void UnsubscribParsingError(Action handler) => ParsingError += handler; + } diff --git a/src/Server/Infrastructure/Routing/src/GlobalUsings.cs b/src/Server/Infrastructure/Routing/src/GlobalUsings.cs index f538820..15afb78 100644 --- a/src/Server/Infrastructure/Routing/src/GlobalUsings.cs +++ b/src/Server/Infrastructure/Routing/src/GlobalUsings.cs @@ -3,4 +3,5 @@ global using System.Runtime.CompilerServices; global using LiteHttp.Models; -global using LiteHttp.Constants; \ No newline at end of file +global using LiteHttp.Helpers; +global using LiteHttp.Constants; diff --git a/src/Server/Infrastructure/Routing/src/LiteHttp.Routing.csproj b/src/Server/Infrastructure/Routing/src/LiteHttp.Routing.csproj index dc279fa..531e3eb 100644 --- a/src/Server/Infrastructure/Routing/src/LiteHttp.Routing.csproj +++ b/src/Server/Infrastructure/Routing/src/LiteHttp.Routing.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Server/Infrastructure/Routing/src/LiteHttp/Routing/RouterEventAdapter.cs b/src/Server/Infrastructure/Routing/src/LiteHttp/Routing/RouterEventAdapter.cs index d1476ab..38a3545 100644 --- a/src/Server/Infrastructure/Routing/src/LiteHttp/Routing/RouterEventAdapter.cs +++ b/src/Server/Infrastructure/Routing/src/LiteHttp/Routing/RouterEventAdapter.cs @@ -9,14 +9,22 @@ public sealed class RouterEventAdapter public void Handle(ConnectionContext connectionContext) { - var result = _router.GetAction(connectionContext.HttpContext); + var action = _router.GetAction(connectionContext.HttpContext); - OnCompleted(connectionContext, result); + if (action is null) OnRequestNotFound(connectionContext); + + OnCompleted(connectionContext, action); } private event Action> Completed; private void OnCompleted(ConnectionContext context, Func action) => Completed?.Invoke(context, action); public void SubscribeToCompleted(Action> handler) => Completed += handler; - public void UnsubscribeCompleted(Action> handler) => Completed -= handler; + public void UnsubscribeFromCompleted(Action> handler) => Completed -= handler; + + private event Action RequestNotFound; + private void OnRequestNotFound(ConnectionContext context) => RequestNotFound?.Invoke(context, InternalActionResults.NotFound()); + + public void SubscribeToRequestNotFound(Action handler) => RequestNotFound += handler; + public void UnsubscribeFromRequestNotFound(Action handler) => RequestNotFound -= handler; } diff --git a/src/Server/Infrastructure/Server/src/LiteHttp/Server/EventDriven/Binder.cs b/src/Server/Infrastructure/Server/src/LiteHttp/Server/EventDriven/Binder.cs index 62f6d8b..b0a141f 100644 --- a/src/Server/Infrastructure/Server/src/LiteHttp/Server/EventDriven/Binder.cs +++ b/src/Server/Infrastructure/Server/src/LiteHttp/Server/EventDriven/Binder.cs @@ -1,4 +1,6 @@ -namespace LiteHttp.Server.EventDriven; +using LiteHttp.RequestProcessors; + +namespace LiteHttp.Server.EventDriven; internal static class Binder { @@ -10,5 +12,9 @@ public static void Bind(InternalServer server) server.RouterAdapter.SubscribeToCompleted(server.ExecutorAdapter.Handle); server.ExecutorAdapter.SubscribeToExecuted(server.ResponseBuilderAdapter.Handle); server.ResponseBuilderAdapter.SubscriveResponseBuilded(server.ConnectionManager.SendResponse); + + server.ParserAdapter.SubscribeToParsingError(server.ResponseBuilderAdapter.Handle); + + server.RouterAdapter.SubscribeToRequestNotFound(server.ResponseBuilderAdapter.Handle); } } From 0913001fe1de5c3bee90b13cf519d7bc4ed59c7a Mon Sep 17 00:00:00 2001 From: Archivelit Date: Tue, 6 Jan 2026 17:24:32 +0100 Subject: [PATCH 2/2] Add Heartbeat realisation and IHeartbeatHandler interface --- LiteHttp.slnx | 28 ++++--- .../Infrastructure/Heartbeat/src/Heartbeat.cs | 75 +++++++++++++++++++ .../Heartbeat/src/IHeartbeatHandler.cs | 6 ++ .../Heartbeat/src/LiteHttp.Heartbeat.csproj | 13 ++++ 4 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 src/Server/Infrastructure/Heartbeat/src/Heartbeat.cs create mode 100644 src/Server/Infrastructure/Heartbeat/src/IHeartbeatHandler.cs create mode 100644 src/Server/Infrastructure/Heartbeat/src/LiteHttp.Heartbeat.csproj diff --git a/LiteHttp.slnx b/LiteHttp.slnx index 1e96e72..e37d964 100644 --- a/LiteHttp.slnx +++ b/LiteHttp.slnx @@ -42,6 +42,9 @@ + + + @@ -51,6 +54,13 @@ + + + + + + + @@ -74,10 +84,16 @@ + + + + + + @@ -90,18 +106,6 @@ - - - - - - - - - - - - diff --git a/src/Server/Infrastructure/Heartbeat/src/Heartbeat.cs b/src/Server/Infrastructure/Heartbeat/src/Heartbeat.cs new file mode 100644 index 0000000..e52fe24 --- /dev/null +++ b/src/Server/Infrastructure/Heartbeat/src/Heartbeat.cs @@ -0,0 +1,75 @@ +// Based on: ASP.NET Core Kestrel Heartbeat +// Source: https://github.com/dotnet/aspnetcore/blob/main/src/Servers/Kestrel/Core/src/Internal/Infrastrutcure/Heartbeat.cs +// Retrieved: 2026-01-06 +// License: MIT license + +using System.Diagnostics; + +using LiteHttp.Logging.Abstractions; + +namespace LiteHttp.Heartbeat; + +public sealed class Heartbeat : IDisposable +{ + private static readonly TimeSpan Interval = TimeSpan.FromSeconds(1); + private const string NoHandlersExceptionString = "Heartbeat not needed to be initialized if here is no handlers in app"; + + private readonly Action[] _callbacks; + private readonly ManualResetEventSlim _timer = new ManualResetEventSlim(false, 0); + private readonly Thread _heartbeatThread; + private readonly ILogger _logger; + + public Heartbeat(IHeartbeatHandler[] heartbeatHandlers, ILogger logger) + { + Debug.Assert(heartbeatHandlers.Length > 0, NoHandlersExceptionString); + + ArgumentException.ThrowIfNullOrEmpty(NoHandlersExceptionString); + + _logger = logger; + + _callbacks = new Action[heartbeatHandlers.Length]; + + for (int i = 0; i < heartbeatHandlers.Length; i++) + _callbacks[i] = heartbeatHandlers[i].OnHeartbeat; + + _heartbeatThread = new Thread(Loop) + { + IsBackground = true, + Name = "Heartbeat" + }; + + _heartbeatThread.Start(); + } + + private void OnHeartbeat() + { + foreach (var callback in _callbacks) + { + try + { + callback(); + // optional: detect long heartbeat + } + catch (Exception ex) + { + _logger.LogTrace($"Exception thrown in heartbeat handler"); + } + } + } + + private void Loop() + { + while (!_timer.Wait(Interval)) + OnHeartbeat(); + } + + public void Dispose() + { + _timer.Set(); + + if (_heartbeatThread.IsAlive) + _heartbeatThread.Join(); + + _timer.Dispose(); + } +} diff --git a/src/Server/Infrastructure/Heartbeat/src/IHeartbeatHandler.cs b/src/Server/Infrastructure/Heartbeat/src/IHeartbeatHandler.cs new file mode 100644 index 0000000..c64d805 --- /dev/null +++ b/src/Server/Infrastructure/Heartbeat/src/IHeartbeatHandler.cs @@ -0,0 +1,6 @@ +namespace LiteHttp.Heartbeat; + +public interface IHeartbeatHandler +{ + void OnHeartbeat(); +} \ No newline at end of file diff --git a/src/Server/Infrastructure/Heartbeat/src/LiteHttp.Heartbeat.csproj b/src/Server/Infrastructure/Heartbeat/src/LiteHttp.Heartbeat.csproj new file mode 100644 index 0000000..a283b1a --- /dev/null +++ b/src/Server/Infrastructure/Heartbeat/src/LiteHttp.Heartbeat.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + +