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
+
+
+
+
+
+
+
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);
}
}