diff --git a/OverclockedApp/OverclockedApp.csproj b/OverclockedApp/OverclockedApp.csproj
new file mode 100644
index 0000000..ae36db7
--- /dev/null
+++ b/OverclockedApp/OverclockedApp.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OverclockedApp/Program.cs b/OverclockedApp/Program.cs
new file mode 100644
index 0000000..65a90cf
--- /dev/null
+++ b/OverclockedApp/Program.cs
@@ -0,0 +1,73 @@
+using System.Buffers;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Wired.IO.App;
+using Wired.IO.Protocol.Response;
+
+namespace OverclockedApp;
+
+// dotnet publish -f net10.0 -c Release /p:PublishAot=true /p:OptimizationPreference=Speed
+
+[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization | JsonSourceGenerationMode.Metadata)]
+[JsonSerializable(typeof(Program.JsonMessage))]
+public partial class JsonContext : JsonSerializerContext { }
+
+public static class Program
+{
+ [ThreadStatic] private static Utf8JsonWriter? _tUtf8JsonWriter;
+ public static async Task Main(string[] args)
+ {
+ await WiredApp
+ .CreateOverclockedBuilder() // io_uring
+ .NoScopedEndpoints()
+ .UseRootEndpoints()
+ .Port(8080)
+ .MapGet("/json", context =>
+ {
+ context.Respond()
+ .Status(ResponseStatus.Ok)
+ .Type("application/json"u8)
+ .Content(() =>
+ {
+ var utf8JsonWriter = _tUtf8JsonWriter ??= new Utf8JsonWriter(context.Connection, new JsonWriterOptions { SkipValidation = true });
+ utf8JsonWriter.Reset(context.Connection);
+ JsonSerializer.Serialize(utf8JsonWriter, new JsonMessage { Message = JsonBody }, SerializerContext.JsonMessage);
+ }, 27);
+
+ })
+ .Build()
+ .RunAsync();
+ }
+
+ private const string JsonBody = "Hello, World!";
+ public static readonly JsonContext SerializerContext = JsonContext.Default;
+ public struct JsonMessage { public string Message { get; set; } }
+}
+
+
+/*
+context.Connection.Write("HTTP/1.1 200 OK\r\n"u8 +
+ "Server: W\r\n"u8 +
+ "Content-Length: 27\r\n"u8 +
+ "Content-Type: application/json\r\n\r\n"u8 +
+ "{\"Message\":\"Hello, World!\"}"u8);
+*/
+
+/*
+var headers =
+ "HTTP/1.1 200 OK\r\n"u8 +
+ "Server: W\r\n"u8 +
+ "Content-Length: 27\r\n"u8 +
+ "Content-Type: application/json\r\n"u8;
+
+context.Connection.Write(headers);
+context.Connection.Write(DateHelper.HeaderBytes);
+
+_tUtf8JsonWriter ??= new Utf8JsonWriter(context.Connection, new JsonWriterOptions { SkipValidation = true });
+_tUtf8JsonWriter.Reset(context.Connection);
+
+// Creating(Allocating) a new JsonMessage every request
+var message = new JsonMessage { Message = "Hello, World!" };
+// Serializing it every request
+JsonSerializer.Serialize(_tUtf8JsonWriter, message, SerializerContext.JsonMessage);
+*/
\ No newline at end of file
diff --git a/Wired.IO.Playground/Program.cs b/Wired.IO.Playground/Program.cs
index 3517c9d..916c443 100644
--- a/Wired.IO.Playground/Program.cs
+++ b/Wired.IO.Playground/Program.cs
@@ -1,26 +1,70 @@
-using System.Net;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Wired.IO.App;
-using Wired.IO.Http11Express.Response.Content;
+using Wired.IO.Handlers.Http11Express.Response.Content;
using Wired.IO.Protocol.Response;
+// dotnet publish -f net10.0 -c Release /p:PublishAot=true /p:OptimizationPreference=Speed
+
var services = new ServiceCollection();
+services.AddScoped();
+
var builder = WiredApp
- .CreateExpressBuilder()
+ //.CreateOverclockedBuilder()
+ .CreateRocketBuilder()
+ //.CreateExpressBuilder()
+ .NoScopedEndpoints()
.Port(8080);
builder.EmbedServices(services);
-
+
builder
.MapGroup("/")
- .MapGet("/my-endpoint", context =>
+ .MapGet("/route", context =>
{
JsonContext SerializerContext = JsonContext.Default;
- var ip = ((IPEndPoint)context.Inner.RemoteEndPoint!).Address;
- Console.WriteLine($"Client: {ip.ToString()}");
+ context
+ .Respond()
+ .Status(ResponseStatus.Ok)
+ .Type("application/json"u8)
+ .Content(new ExpressJsonAotContent(new JsonMessage
+ {
+ Message = "Hello World!"
+ }, SerializerContext.JsonMessage));
+ });
+
+builder
+ .MapGroup("/api")
+ .UseMiddleware(async (context, next) =>
+ {
+ // logger or any dependencies can be resolved using scope
+ var logger = context.Services.GetRequiredService>();
+
+ try
+ {
+ Console.WriteLine("Executing Middleware");
+ // Execute next in line, could be another middleware or the endpoint
+ await next(context);
+ }
+
+ catch (Exception e)
+ {
+ logger.LogError(e.Message);
+
+ context.Respond()
+ .Status(ResponseStatus.InternalServerError)
+ .Type("application/json"u8)
+ .Content(new ExpressJsonContent(new { Error = e.Message }));
+ }
+ })
+ .MapGet("/my-endpoint", async context =>
+ {
+ await context.Services.GetRequiredService().HandleAsync();
+
+ JsonContext SerializerContext = JsonContext.Default;
context
.Respond()
@@ -43,3 +87,16 @@ public struct JsonMessage { public string Message { get; set; } }
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization | JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(JsonMessage))]
public partial class JsonContext : JsonSerializerContext { }
+
+public class Service : IDisposable
+{
+ public Service() => Console.WriteLine("Created Service");
+
+ public async Task HandleAsync()
+ {
+ await Task.Delay(0);
+ Console.WriteLine("Handled Service");
+ }
+
+ public void Dispose() => Console.WriteLine("Disposed Service");
+}
\ No newline at end of file
diff --git a/Wired.IO.sln b/Wired.IO.sln
index 7f4812a..1abe262 100755
--- a/Wired.IO.sln
+++ b/Wired.IO.sln
@@ -9,6 +9,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wired.IO.Playground", "Wire
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wired.IO.Tests", "Wired.IO.Tests\Wired.IO.Tests.csproj", "{21364EC0-8822-40F5-8EEB-339919ABD5C6}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{8BA4F484-5D28-4E2B-BE5A-BA62BD943FB7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OverclockedApp", "OverclockedApp\OverclockedApp.csproj", "{068FC1BE-C9B2-4C82-BACE-98547D68DDB5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +31,10 @@ Global
{21364EC0-8822-40F5-8EEB-339919ABD5C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21364EC0-8822-40F5-8EEB-339919ABD5C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21364EC0-8822-40F5-8EEB-339919ABD5C6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {068FC1BE-C9B2-4C82-BACE-98547D68DDB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {068FC1BE-C9B2-4C82-BACE-98547D68DDB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {068FC1BE-C9B2-4C82-BACE-98547D68DDB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {068FC1BE-C9B2-4C82-BACE-98547D68DDB5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -34,4 +42,7 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC158062-AC46-467E-AFC4-2ED35B471D73}
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {068FC1BE-C9B2-4C82-BACE-98547D68DDB5} = {8BA4F484-5D28-4E2B-BE5A-BA62BD943FB7}
+ EndGlobalSection
EndGlobal
diff --git a/Wired.IO/App/App.Builder.cs b/Wired.IO/App/App.Builder.cs
index 574d2a5..4a6387e 100644
--- a/Wired.IO/App/App.Builder.cs
+++ b/Wired.IO/App/App.Builder.cs
@@ -4,7 +4,6 @@
using Microsoft.Extensions.Logging;
using Wired.IO.Builder;
using Wired.IO.Protocol;
-using Wired.IO.Protocol.Handlers;
using Wired.IO.Protocol.Request;
using Wired.IO.Protocol.Response;
@@ -125,11 +124,6 @@ public sealed partial class WiredApp
///
internal bool UseRootOnlyEndpoints { get; set; } = false;
- ///
- /// Gets or sets the HTTP handler responsible for dispatching requests and handling routing.
- ///
- public IHttpHandler HttpHandler { get; set; } = null!;
-
#endregion
}
diff --git a/Wired.IO/App/App.ClientHandling.cs b/Wired.IO/App/App.ClientHandling.cs
index ed7eb22..146f2ad 100644
--- a/Wired.IO/App/App.ClientHandling.cs
+++ b/Wired.IO/App/App.ClientHandling.cs
@@ -3,20 +3,21 @@
using System.Net.Sockets;
using System.Security;
using System.Security.Authentication;
-using Wired.IO.MemoryBuffers;
using Wired.IO.Protocol;
using Wired.IO.Protocol.Request;
using Wired.IO.Protocol.Response;
+using Wired.IO.Transport.Socket;
namespace Wired.IO.App;
public sealed partial class WiredApp
where TContext : IBaseContext
{
- private Func _pipeline = null!;
+ //private Func _pipeline = null!;
- internal void SetPipeline(Func pipeline) => _pipeline = pipeline;
+ //internal void SetPipeline(Func pipeline) => _pipeline = pipeline;
+ /*
///
/// Handles an incoming plain (non-TLS) TCP client connection.
/// Wraps the client socket in a and delegates request handling to .
@@ -26,7 +27,7 @@ public sealed partial class WiredApp
private async Task HandlePlainClientAsync(Socket client, CancellationToken stoppingToken)
{
await using var networkStream = new PoolBufferedStream(new NetworkStream(client, ownsSocket: true), 65 * 1024);
- await HttpHandler.HandleClientAsync(
+ await ((ISocketHttpHandler)HttpHandler).HandleClientAsync(
client,
networkStream,
_pipeline,
@@ -72,7 +73,7 @@ private async Task HandleTlsClientAsync(Socket client, CancellationToken stoppin
// Handle the client connection securely
//
- await HttpHandler.HandleClientAsync(
+ await ((ISocketHttpHandler)HttpHandler).HandleClientAsync(
client,
sslStream,
_pipeline,
@@ -126,4 +127,5 @@ private static async Task SendTlsFailureMessageAsync(Socket client)
client.Close();
}
}
+ */
}
\ No newline at end of file
diff --git a/Wired.IO/App/App.Engine.cs b/Wired.IO/App/App.Engine.cs
index c96ee19..575befc 100644
--- a/Wired.IO/App/App.Engine.cs
+++ b/Wired.IO/App/App.Engine.cs
@@ -10,6 +10,7 @@ namespace Wired.IO.App;
public sealed partial class WiredApp
where TContext : IBaseContext
{
+ /*
///
/// Creates an instance configured to accept and process plain (non-TLS) HTTP connections.
///
@@ -126,4 +127,5 @@ private async Task HandleClientAsync(Socket client, Func : IDisposable
/// Gets or sets the service collection used to configure application dependencies before building the provider.
///
public IServiceCollection ServiceCollection { get; set; } = new ServiceCollection();
+
+ ///
+ /// Underlying Engine Ongoing Task
+ ///
+ public Task? TransportTask => _transportTask;
+
+ internal ITransport Transport { get; set; } = null!;
private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly TaskCompletionSource _startedTcs = new();
- private Task? _engineTask;
private bool _disposed;
-
- ///
- /// Encapsulates the server's execution logic and abstracts the engine behavior (e.g., plain or TLS).
- ///
- private sealed class Engine(Func action)
- {
- ///
- /// Executes the engine logic using the provided cancellation token.
- ///
- /// Token used to signal cancellation of the server loop.
- public async Task ExecuteAsync(CancellationToken stoppingToken)
- {
- await action(stoppingToken);
- }
- }
+ private Task? _transportTask;
///
/// Asynchronously starts the application and begins listening for incoming requests.
@@ -52,9 +45,14 @@ public async Task> StartAsync()
{
if (_disposed)
throw new ObjectDisposedException(nameof(WiredApp));
-
- var engine = TlsEnabled ? CreateTlsEnabledEngine() : CreatePlainEngine();
- _engineTask = engine.ExecuteAsync(_cancellationTokenSource.Token);
+
+ Transport.IPAddress = IpAddress;
+ Transport.Port = Port;
+ Transport.Backlog = Backlog;
+ Transport.TlsEnabled = TlsEnabled;
+ Transport.Logger = Logger;
+ Transport.SslServerAuthenticationOptions = SslServerAuthenticationOptions;
+ _transportTask = Transport.ExecuteAsync(_cancellationTokenSource.Token);
// Give the engine a moment to start listening
await Task.Delay(10);
@@ -73,10 +71,10 @@ public async Task RunAsync()
if (_disposed)
throw new ObjectDisposedException(nameof(WiredApp));
- if (_engineTask == null)
+ if (_transportTask == null)
await StartAsync();
- await _engineTask!;
+ await _transportTask!;
}
///
@@ -88,9 +86,14 @@ public WiredApp Start()
{
if (_disposed)
throw new ObjectDisposedException(nameof(WiredApp));
-
- var engine = TlsEnabled ? CreateTlsEnabledEngine() : CreatePlainEngine();
- _engineTask = engine.ExecuteAsync(_cancellationTokenSource.Token);
+
+ Transport.IPAddress = IpAddress;
+ Transport.Port = Port;
+ Transport.Backlog = Backlog;
+ Transport.TlsEnabled = TlsEnabled;
+ Transport.Logger = Logger;
+ Transport.SslServerAuthenticationOptions = SslServerAuthenticationOptions;
+ _transportTask = Transport.ExecuteAsync(_cancellationTokenSource.Token);
// Brief delay to ensure listening starts
Thread.Sleep(10);
@@ -109,10 +112,10 @@ public void Run()
if (_disposed)
throw new ObjectDisposedException(nameof(WiredApp));
- if (_engineTask == null)
+ if (_transportTask == null)
Start();
- _engineTask!.GetAwaiter().GetResult();
+ _transportTask!.GetAwaiter().GetResult();
}
///
@@ -125,11 +128,11 @@ public async Task StopAsync()
await _cancellationTokenSource.CancelAsync();
- if (_engineTask != null)
+ if (_transportTask != null)
{
try
{
- await _engineTask;
+ await _transportTask;
}
catch (OperationCanceledException)
{
@@ -151,7 +154,9 @@ public void Dispose()
_cancellationTokenSource.Cancel();
_cancellationTokenSource.Dispose();
- _socket?.Dispose();
+
+ Transport.Dispose();
+ //_socket?.Dispose();
if (Services is IDisposable disposableProvider)
disposableProvider.Dispose();
diff --git a/Wired.IO/App/App.cs b/Wired.IO/App/App.cs
index b62b997..21d15b0 100644
--- a/Wired.IO/App/App.cs
+++ b/Wired.IO/App/App.cs
@@ -1,12 +1,19 @@
using System.Net.Security;
using Wired.IO.Builder;
-using Wired.IO.Http11Express;
-using Wired.IO.Http11Express.Context;
-using Wired.IO.Http11Express.StaticHandlers;
+using Wired.IO.Handlers.Http11Express;
+using Wired.IO.Handlers.Http11Express.Context;
+using Wired.IO.Handlers.Http11Express.StaticHandlers;
+using Wired.IO.Handlers.Http11Overclocked;
+using Wired.IO.Handlers.Http11Overclocked.Context;
+using Wired.IO.Handlers.Http11Rocket;
+using Wired.IO.Handlers.Http11Rocket.Context;
using Wired.IO.Protocol;
using Wired.IO.Protocol.Handlers;
using Wired.IO.Protocol.Request;
using Wired.IO.Protocol.Response;
+using Wired.IO.Transport;
+using Wired.IO.Transport.Rocket;
+using Wired.IO.Transport.Socket;
namespace Wired.IO.App;
@@ -16,10 +23,26 @@ namespace Wired.IO.App;
///
public sealed class WiredApp
{
+ public static Builder CreateOverclockedBuilder()
+ {
+ var builder = new Builder(() => new WiredHttp11Overclocked(),
+ [SslApplicationProtocol.Http11], new RocketTransport());
+
+ return builder;
+ }
+
+ public static Builder CreateRocketBuilder()
+ {
+ var builder = new Builder(() => new WiredHttp11Rocket(),
+ [SslApplicationProtocol.Http11], new RocketTransport());
+
+ return builder;
+ }
+
public static Builder CreateExpressBuilder()
{
- var builder = new Builder(() =>
- new WiredHttp11Express(), [SslApplicationProtocol.Http11]);
+ var builder = new Builder(() => new WiredHttp11Express(),
+ [SslApplicationProtocol.Http11], new SocketTransport());
return builder.MapFlowControl("NotFound", FlowControl.CreateEndpointNotFoundHandler());
}
@@ -28,8 +51,8 @@ public static Builder CreateExpressBui
public static Builder, TContext> CreateExpressBuilder()
where TContext : Http11ExpressContext, new()
{
- var builder = new Builder, TContext>(() =>
- new WiredHttp11Express(), [SslApplicationProtocol.Http11]);
+ var builder = new Builder, TContext>(() => new WiredHttp11Express(),
+ [SslApplicationProtocol.Http11], new SocketTransport());
return builder.MapFlowControl("NotFound", FlowControl.CreateEndpointNotFoundHandler());
}
@@ -37,34 +60,37 @@ public static Builder, TContext> CreateExpressBuild
///
/// Creates a generic for a custom handler and context type.
///
- /// The custom HTTP handler type implementing .
+ /// The custom HTTP handler type implementing .
/// The request context type implementing .
/// A factory delegate that produces an instance of .
+ ///
/// A configured instance.
- public static Builder CreateBuilder(Func handlerFactory)
- where THandler : IHttpHandler
+ public static Builder CreateBuilder(Func handlerFactory, ITransport transport)
+ where THandler : IHttpHandler
where TContext : IBaseContext
{
- return CreateBuilder(handlerFactory, [SslApplicationProtocol.Http11]);
+ return CreateBuilder(handlerFactory, [SslApplicationProtocol.Http11], transport);
}
///
/// Creates a generic for a custom handler and context type,
/// using a custom list of supported ALPN protocols.
///
- /// The custom HTTP handler type implementing .
+ /// The custom HTTP handler type implementing .
/// The request context type implementing .
/// A factory delegate that produces an instance of .
///
/// A list of supported values for ALPN negotiation.
///
+ ///
/// A configured instance.
public static Builder CreateBuilder(
Func handlerFactory,
- List sslApplicationProtocols)
- where THandler : IHttpHandler
+ List sslApplicationProtocols,
+ ITransport transport)
+ where THandler : IHttpHandler
where TContext : IBaseContext
{
- return new Builder(handlerFactory, sslApplicationProtocols);
+ return new Builder(handlerFactory, sslApplicationProtocols, transport);
}
}
\ No newline at end of file
diff --git a/Wired.IO/Builder/Builder.GroupsMapping.cs b/Wired.IO/Builder/Builder.GroupsMapping.cs
index 21a5c2e..1af12f0 100644
--- a/Wired.IO/Builder/Builder.GroupsMapping.cs
+++ b/Wired.IO/Builder/Builder.GroupsMapping.cs
@@ -14,7 +14,7 @@ namespace Wired.IO.Builder;
public sealed partial class Builder
where TContext : IBaseContext
- where THandler : IHttpHandler
+ where THandler : IHttpHandler
{
// Public entry that creates/returns a top-level group
public Group MapGroup(string groupRoute)
diff --git a/Wired.IO/Builder/Builder.ManualPipeline.cs b/Wired.IO/Builder/Builder.ManualPipeline.cs
index bf1b422..9cd7ee5 100644
--- a/Wired.IO/Builder/Builder.ManualPipeline.cs
+++ b/Wired.IO/Builder/Builder.ManualPipeline.cs
@@ -9,7 +9,7 @@ namespace Wired.IO.Builder;
public sealed partial class Builder
where TContext : IBaseContext
- where THandler : IHttpHandler
+ where THandler : IHttpHandler
{
public Builder AddManualPipeline(
string route,
diff --git a/Wired.IO/Builder/Builder.RootEndpoints.cs b/Wired.IO/Builder/Builder.RootEndpoints.cs
index 21dc5ab..59f7e21 100644
--- a/Wired.IO/Builder/Builder.RootEndpoints.cs
+++ b/Wired.IO/Builder/Builder.RootEndpoints.cs
@@ -9,7 +9,7 @@ namespace Wired.IO.Builder;
public sealed partial class Builder
where TContext : IBaseContext
- where THandler : IHttpHandler
+ where THandler : IHttpHandler
{
// ======== FlowControl ==========
diff --git a/Wired.IO/Builder/Builder.RootMiddleware.cs b/Wired.IO/Builder/Builder.RootMiddleware.cs
index a2d9508..d4b43cf 100644
--- a/Wired.IO/Builder/Builder.RootMiddleware.cs
+++ b/Wired.IO/Builder/Builder.RootMiddleware.cs
@@ -8,7 +8,7 @@ namespace Wired.IO.Builder;
public sealed partial class Builder
where TContext : IBaseContext
- where THandler : IHttpHandler
+ where THandler : IHttpHandler
{
///
/// Registers a middleware component that resolves dependencies per request scope
diff --git a/Wired.IO/Builder/Builder.cs b/Wired.IO/Builder/Builder.cs
index a00b576..286e7fd 100644
--- a/Wired.IO/Builder/Builder.cs
+++ b/Wired.IO/Builder/Builder.cs
@@ -2,12 +2,11 @@
using Microsoft.Extensions.Logging;
using System.Net;
using System.Net.Security;
-using System.Reflection;
using Wired.IO.App;
-using Wired.IO.Http11Express.StaticHandlers;
using Wired.IO.Protocol;
using Wired.IO.Protocol.Handlers;
using Wired.IO.Protocol.Response;
+using Wired.IO.Transport;
using IBaseRequest = Wired.IO.Protocol.Request.IBaseRequest;
namespace Wired.IO.Builder;
@@ -16,11 +15,11 @@ namespace Wired.IO.Builder;
/// Provides a fluent configuration API to build and configure a instance.
/// Supports setting up dependency injection, middleware, routes, TLS options, and runtime parameters.
///
-/// The HTTP handler type implementing .
+/// The HTTP handler type implementing .
///
public sealed partial class Builder
where TContext : IBaseContext
- where THandler : IHttpHandler
+ where THandler : IHttpHandler
{
///
/// Gets the service collection used to register middleware, handlers, and services.
@@ -36,9 +35,10 @@ public sealed partial class Builder
/// Initializes the builder using a handler factory and defaults to HTTP/1.1 protocol.
///
/// A delegate that creates the HTTP handler.
- public Builder(Func handlerFactory)
+ ///
+ public Builder(Func handlerFactory, ITransport transport)
{
- Initialize(handlerFactory, [SslApplicationProtocol.Http11]);
+ Initialize(handlerFactory, [SslApplicationProtocol.Http11], transport);
}
///
@@ -46,9 +46,10 @@ public Builder(Func handlerFactory)
///
/// A delegate that creates the HTTP handler.
/// The list of supported ALPN protocols.
- public Builder(Func handlerFactory, List sslApplicationProtocols)
+ ///
+ public Builder(Func handlerFactory, List sslApplicationProtocols, ITransport transport)
{
- Initialize(handlerFactory, sslApplicationProtocols);
+ Initialize(handlerFactory, sslApplicationProtocols, transport);
}
///
@@ -56,14 +57,17 @@ public Builder(Func handlerFactory, List sslAp
///
/// The handler creation delegate.
/// List of supported ALPN protocols.
- private void Initialize(Func handlerFactory, List sslApplicationProtocols)
+ ///
+ private void Initialize(Func handlerFactory, List sslApplicationProtocols, ITransport transport)
{
_endpointDIRegister = new EndpointDIRegister(this);
_root = new Group(prefix: "/", parent: null, diRegister: _endpointDIRegister);
-
- App.HttpHandler = handlerFactory();
+
App.SslServerAuthenticationOptions.ApplicationProtocols = sslApplicationProtocols;
+ App.Transport = transport;
+ App.Transport.HttpHandler = handlerFactory();
+
App.ServiceCollection.AddLogging(DefaultLoggingBuilder);
}
@@ -103,7 +107,7 @@ private void BuildRootOnly(IServiceProvider? serviceProvider = null!)
App.RootMiddleware = App.Services.GetServices, Task>>().ToList();
App.BuildPipeline(App.RootMiddleware, App.EndpointInvoker);
- App.SetPipeline(App.RootPipeline);
+ App.Transport.Pipeline = App.RootPipeline;
App.RootEndpoints = [];
@@ -141,7 +145,7 @@ private void BuildGroup(IServiceProvider? serviceProvider = null!)
}
App.CachePipelines(App.Services);
- App.SetPipeline(App.GroupPipeline);
+ App.Transport.Pipeline = App.GroupPipeline;
// Rearrange EmbeddedRoutes
RearrangeEncodedRoutes(App.EncodedRoutes);
diff --git a/Wired.IO/Handlers/CachedData.cs b/Wired.IO/Handlers/CachedData.cs
new file mode 100644
index 0000000..e15d0ae
--- /dev/null
+++ b/Wired.IO/Handlers/CachedData.cs
@@ -0,0 +1,44 @@
+using Wired.IO.Utilities.StringCache;
+
+namespace Wired.IO.Handlers;
+
+///
+/// Caches commonly-seen strings (routes, header keys/values, methods) to avoid repeated allocations
+/// and string interning during hot paths.
+///
+///
+/// Backed by custom fast hash caches sized for typical HTTP workloads. The pre-cached sets include common
+/// request methods and frequently-present headers/values seen in benchmarks (e.g., TechEmpower Plaintext JSON).
+///
+internal static class CachedData
+{
+ /// Cache of parsed routes (path components of the URL).
+ internal static readonly FastHashStringCache32 CachedRoutes = new FastHashStringCache32();
+ /// Cache of query-string keys.
+ internal static readonly FastHashStringCache32 CachedQueryKeys = new FastHashStringCache32();
+ /// Pre-cached HTTP methods (8 common verbs).
+ internal static readonly FastHashStringCache16 PreCachedHttpMethods = new FastHashStringCache16([
+ "GET",
+ "POST",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "HEAD",
+ "OPTIONS",
+ "TRACE"
+ ], 8);
+ /// Pre-cached header keys commonly present in requests.
+ internal static readonly FastHashStringCache32 PreCachedHeaderKeys = new FastHashStringCache32([
+ "Host",
+ "User-Agent",
+ "Cookie",
+ "Accept",
+ "Accept-Language",
+ "Connection"
+ ]);
+ /// Pre-cached header values commonly seen on the wire.
+ internal static readonly FastHashStringCache32 PreCachedHeaderValues = new FastHashStringCache32([
+ "keep-alive",
+ "server",
+ ]);
+}
\ No newline at end of file
diff --git a/Wired.IO/Http11Express/BuilderExtensions/StaticFiles.cs b/Wired.IO/Handlers/Http11Express/BuilderExtensions/StaticFiles.cs
similarity index 98%
rename from Wired.IO/Http11Express/BuilderExtensions/StaticFiles.cs
rename to Wired.IO/Handlers/Http11Express/BuilderExtensions/StaticFiles.cs
index 2c59467..cac70bf 100644
--- a/Wired.IO/Http11Express/BuilderExtensions/StaticFiles.cs
+++ b/Wired.IO/Handlers/Http11Express/BuilderExtensions/StaticFiles.cs
@@ -1,9 +1,8 @@
using Wired.IO.App;
using Wired.IO.Builder;
-using Wired.IO.Http11Express.Context;
-using Wired.IO.Http11Express.StaticHandlers;
+using Wired.IO.Handlers.Http11Express.Context;
-namespace Wired.IO.Http11Express.BuilderExtensions;
+namespace Wired.IO.Handlers.Http11Express.BuilderExtensions;
///
/// Extension methods that wire up **static** and **SPA** file serving into
diff --git a/Wired.IO/Http11Express/BuilderExtensions/StaticResources.cs b/Wired.IO/Handlers/Http11Express/BuilderExtensions/StaticResources.cs
similarity index 99%
rename from Wired.IO/Http11Express/BuilderExtensions/StaticResources.cs
rename to Wired.IO/Handlers/Http11Express/BuilderExtensions/StaticResources.cs
index 9636ea5..0f50cdb 100644
--- a/Wired.IO/Http11Express/BuilderExtensions/StaticResources.cs
+++ b/Wired.IO/Handlers/Http11Express/BuilderExtensions/StaticResources.cs
@@ -1,14 +1,13 @@
using System.Buffers;
-using System.Diagnostics.Contracts;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using Wired.IO.App;
using Wired.IO.Builder;
-using Wired.IO.Http11Express.Context;
+using Wired.IO.Handlers.Http11Express.Context;
using Wired.IO.Protocol.Response;
using Wired.IO.Utilities;
-namespace Wired.IO.Http11Express.StaticHandlers;
+namespace Wired.IO.Handlers.Http11Express.BuilderExtensions;
public static class BuilderExtensions
{
diff --git a/Wired.IO/Http11Express/Context/Http11ExpressContext.cs b/Wired.IO/Handlers/Http11Express/Context/Http11ExpressContext.cs
similarity index 86%
rename from Wired.IO/Http11Express/Context/Http11ExpressContext.cs
rename to Wired.IO/Handlers/Http11Express/Context/Http11ExpressContext.cs
index efb5b89..3bc5d34 100644
--- a/Wired.IO/Http11Express/Context/Http11ExpressContext.cs
+++ b/Wired.IO/Handlers/Http11Express/Context/Http11ExpressContext.cs
@@ -1,19 +1,21 @@
using System.IO.Pipelines;
-using System.Net.Sockets;
-using Wired.IO.Http11Express.Request;
-using Wired.IO.Http11Express.Response;
+using Wired.IO.Handlers.Http11Express.Request;
+using Wired.IO.Handlers.Http11Express.Response;
using Wired.IO.Protocol;
using Wired.IO.Protocol.Response;
using Wired.IO.Utilities;
-namespace Wired.IO.Http11Express.Context;
+namespace Wired.IO.Handlers.Http11Express.Context;
// This class cannot be sealed, might have super types
public class Http11ExpressContext : IBaseContext
{
- public Socket Inner { get; internal set; } = null!;
+ public System.Net.Sockets.Socket Inner { get; internal set; } = null!;
+
public PipeReader Reader { get; set; } = null!;
+
public PipeWriter Writer { get; set; } = null!;
+
public IExpressRequest Request { get; set; } = new Http11ExpressRequest()
{
Headers = new PooledDictionary(
@@ -23,9 +25,11 @@ public class Http11ExpressContext : IBaseContext _responseBuilder ??= new ExpressResponseBuilder(Response!);
public ExpressResponseBuilder Respond()
@@ -43,6 +47,7 @@ public ExpressResponseBuilder Respond()
public void Clear()
{
+ Inner = null!;
Response?.Clear();
Request.Clear();
}
diff --git a/Wired.IO/Http11Express/Context/Http11ExpressContextExtensions.cs b/Wired.IO/Handlers/Http11Express/Context/Http11ExpressContextExtensions.cs
similarity index 96%
rename from Wired.IO/Http11Express/Context/Http11ExpressContextExtensions.cs
rename to Wired.IO/Handlers/Http11Express/Context/Http11ExpressContextExtensions.cs
index 54bc85a..4d09192 100644
--- a/Wired.IO/Http11Express/Context/Http11ExpressContextExtensions.cs
+++ b/Wired.IO/Handlers/Http11Express/Context/Http11ExpressContextExtensions.cs
@@ -3,7 +3,7 @@
// ReSharper disable MemberCanBePrivate.Global
-namespace Wired.IO.Http11Express.Context;
+namespace Wired.IO.Handlers.Http11Express.Context;
public static class Http11ExpressContextExtensions
{
diff --git a/Wired.IO/Http11Express/Request/Http11ExpressRequest.cs b/Wired.IO/Handlers/Http11Express/Request/Http11ExpressRequest.cs
similarity index 96%
rename from Wired.IO/Http11Express/Request/Http11ExpressRequest.cs
rename to Wired.IO/Handlers/Http11Express/Request/Http11ExpressRequest.cs
index 34945d1..7710623 100644
--- a/Wired.IO/Http11Express/Request/Http11ExpressRequest.cs
+++ b/Wired.IO/Handlers/Http11Express/Request/Http11ExpressRequest.cs
@@ -3,7 +3,7 @@
using Wired.IO.Protocol.Request;
using Wired.IO.Utilities;
-namespace Wired.IO.Http11Express.Request;
+namespace Wired.IO.Handlers.Http11Express.Request;
[SkipLocalsInit]
public class Http11ExpressRequest : IExpressRequest
diff --git a/Wired.IO/Http11Express/Request/IExpressRequest.cs b/Wired.IO/Handlers/Http11Express/Request/IExpressRequest.cs
similarity index 96%
rename from Wired.IO/Http11Express/Request/IExpressRequest.cs
rename to Wired.IO/Handlers/Http11Express/Request/IExpressRequest.cs
index d89fd51..4eb4dd2 100644
--- a/Wired.IO/Http11Express/Request/IExpressRequest.cs
+++ b/Wired.IO/Handlers/Http11Express/Request/IExpressRequest.cs
@@ -1,7 +1,7 @@
using Wired.IO.Protocol.Request;
using Wired.IO.Utilities;
-namespace Wired.IO.Http11Express.Request;
+namespace Wired.IO.Handlers.Http11Express.Request;
public interface IExpressRequest : IBaseRequest
{
diff --git a/Wired.IO/Http11Express/Response/Content/ExpressJsonContent.cs b/Wired.IO/Handlers/Http11Express/Response/Content/ExpressJsonContent.cs
similarity index 97%
rename from Wired.IO/Http11Express/Response/Content/ExpressJsonContent.cs
rename to Wired.IO/Handlers/Http11Express/Response/Content/ExpressJsonContent.cs
index 48809c6..c67000f 100644
--- a/Wired.IO/Http11Express/Response/Content/ExpressJsonContent.cs
+++ b/Wired.IO/Handlers/Http11Express/Response/Content/ExpressJsonContent.cs
@@ -1,12 +1,11 @@
-using Microsoft.Extensions.ObjectPool;
-using System.IO.Pipelines;
+using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Text.Json;
-using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
+using Microsoft.Extensions.ObjectPool;
using Wired.IO.Protocol.Writers;
-namespace Wired.IO.Http11Express.Response.Content;
+namespace Wired.IO.Handlers.Http11Express.Response.Content;
///
/// Provides pooled writers and utilities for JSON serialization.
diff --git a/Wired.IO/Http11Express/Response/Content/ExpressRawContent.cs b/Wired.IO/Handlers/Http11Express/Response/Content/ExpressRawContent.cs
similarity index 88%
rename from Wired.IO/Http11Express/Response/Content/ExpressRawContent.cs
rename to Wired.IO/Handlers/Http11Express/Response/Content/ExpressRawContent.cs
index 819f07e..8e16e98 100644
--- a/Wired.IO/Http11Express/Response/Content/ExpressRawContent.cs
+++ b/Wired.IO/Handlers/Http11Express/Response/Content/ExpressRawContent.cs
@@ -2,7 +2,7 @@
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
-namespace Wired.IO.Http11Express.Response.Content;
+namespace Wired.IO.Handlers.Http11Express.Response.Content;
[SkipLocalsInit]
public class ExpressRawContent : IExpressResponseContent
diff --git a/Wired.IO/Http11Express/Response/Content/ExpressStringContent.cs b/Wired.IO/Handlers/Http11Express/Response/Content/ExpressStringContent.cs
similarity index 92%
rename from Wired.IO/Http11Express/Response/Content/ExpressStringContent.cs
rename to Wired.IO/Handlers/Http11Express/Response/Content/ExpressStringContent.cs
index 08fa4fc..9ccb55c 100644
--- a/Wired.IO/Http11Express/Response/Content/ExpressStringContent.cs
+++ b/Wired.IO/Handlers/Http11Express/Response/Content/ExpressStringContent.cs
@@ -3,7 +3,7 @@
using System.Runtime.CompilerServices;
using Wired.IO.Utilities;
-namespace Wired.IO.Http11Express.Response.Content;
+namespace Wired.IO.Handlers.Http11Express.Response.Content;
[SkipLocalsInit]
public class ExpressStringContent : IExpressResponseContent
diff --git a/Wired.IO/Http11Express/Response/Content/IExpressResponseContent.cs b/Wired.IO/Handlers/Http11Express/Response/Content/IExpressResponseContent.cs
similarity index 86%
rename from Wired.IO/Http11Express/Response/Content/IExpressResponseContent.cs
rename to Wired.IO/Handlers/Http11Express/Response/Content/IExpressResponseContent.cs
index e334eef..473b048 100644
--- a/Wired.IO/Http11Express/Response/Content/IExpressResponseContent.cs
+++ b/Wired.IO/Handlers/Http11Express/Response/Content/IExpressResponseContent.cs
@@ -1,7 +1,7 @@
using System.IO.Pipelines;
using System.Text.Json.Serialization.Metadata;
-namespace Wired.IO.Http11Express.Response.Content;
+namespace Wired.IO.Handlers.Http11Express.Response.Content;
public interface IExpressResponseContent
{
diff --git a/Wired.IO/Protocol/Response/ExpressResponseBuilder.cs b/Wired.IO/Handlers/Http11Express/Response/ExpressResponseBuilder.cs
similarity index 97%
rename from Wired.IO/Protocol/Response/ExpressResponseBuilder.cs
rename to Wired.IO/Handlers/Http11Express/Response/ExpressResponseBuilder.cs
index ba08419..a73c7fb 100644
--- a/Wired.IO/Protocol/Response/ExpressResponseBuilder.cs
+++ b/Wired.IO/Handlers/Http11Express/Response/ExpressResponseBuilder.cs
@@ -1,10 +1,10 @@
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization.Metadata;
-using Wired.IO.Http11Express.Response;
-using Wired.IO.Http11Express.Response.Content;
+using Wired.IO.Handlers.Http11Express.Response.Content;
+using Wired.IO.Protocol.Response;
using Wired.IO.Utilities;
-namespace Wired.IO.Protocol.Response;
+namespace Wired.IO.Handlers.Http11Express.Response;
public enum ContentLengthStrategy
{
diff --git a/Wired.IO/Http11Express/Response/Http11ExpressResponse.cs b/Wired.IO/Handlers/Http11Express/Response/Http11ExpressResponse.cs
similarity index 94%
rename from Wired.IO/Http11Express/Response/Http11ExpressResponse.cs
rename to Wired.IO/Handlers/Http11Express/Response/Http11ExpressResponse.cs
index e81c96c..ec45bad 100644
--- a/Wired.IO/Http11Express/Response/Http11ExpressResponse.cs
+++ b/Wired.IO/Handlers/Http11Express/Response/Http11ExpressResponse.cs
@@ -1,9 +1,9 @@
-using Wired.IO.Http11Express.Response.Content;
+using Wired.IO.Handlers.Http11Express.Response.Content;
using Wired.IO.Protocol.Response;
using Wired.IO.Protocol.Response.Headers;
using Wired.IO.Utilities;
-namespace Wired.IO.Http11Express.Response;
+namespace Wired.IO.Handlers.Http11Express.Response;
public class Http11ExpressResponse : IExpressResponse
{
diff --git a/Wired.IO/Http11Express/Response/IExpressResponse.cs b/Wired.IO/Handlers/Http11Express/Response/IExpressResponse.cs
similarity index 94%
rename from Wired.IO/Http11Express/Response/IExpressResponse.cs
rename to Wired.IO/Handlers/Http11Express/Response/IExpressResponse.cs
index 679d257..e9bbe17 100644
--- a/Wired.IO/Http11Express/Response/IExpressResponse.cs
+++ b/Wired.IO/Handlers/Http11Express/Response/IExpressResponse.cs
@@ -1,9 +1,9 @@
-using Wired.IO.Http11Express.Response.Content;
+using Wired.IO.Handlers.Http11Express.Response.Content;
using Wired.IO.Protocol.Response;
using Wired.IO.Protocol.Response.Headers;
using Wired.IO.Utilities;
-namespace Wired.IO.Http11Express.Response;
+namespace Wired.IO.Handlers.Http11Express.Response;
public interface IExpressResponse : IBaseResponse
{
diff --git a/Wired.IO/Http11Express/StaticHandlers/FlowControl.cs b/Wired.IO/Handlers/Http11Express/StaticHandlers/FlowControl.cs
similarity index 95%
rename from Wired.IO/Http11Express/StaticHandlers/FlowControl.cs
rename to Wired.IO/Handlers/Http11Express/StaticHandlers/FlowControl.cs
index 4fd5d86..33c79de 100644
--- a/Wired.IO/Http11Express/StaticHandlers/FlowControl.cs
+++ b/Wired.IO/Handlers/Http11Express/StaticHandlers/FlowControl.cs
@@ -1,12 +1,10 @@
using System.Buffers;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
-using Wired.IO.App;
-using Wired.IO.Http11Express.Context;
+using Wired.IO.Handlers.Http11Express.Context;
using Wired.IO.Protocol.Response;
-using Wired.IO.Utilities;
-namespace Wired.IO.Http11Express.StaticHandlers;
+namespace Wired.IO.Handlers.Http11Express.StaticHandlers;
///
/// Provides prebuilt lightweight response handlers for exceptional or control flow
diff --git a/Wired.IO/Http11Express/WiredHttp11Express.Handler.cs b/Wired.IO/Handlers/Http11Express/WiredHttp11Express.Handler.cs
similarity index 94%
rename from Wired.IO/Http11Express/WiredHttp11Express.Handler.cs
rename to Wired.IO/Handlers/Http11Express/WiredHttp11Express.Handler.cs
index 7f2760f..bcdfad9 100644
--- a/Wired.IO/Http11Express/WiredHttp11Express.Handler.cs
+++ b/Wired.IO/Handlers/Http11Express/WiredHttp11Express.Handler.cs
@@ -1,21 +1,16 @@
-using Microsoft.Extensions.ObjectPool;
-using System.Buffers;
-using System.Diagnostics;
+using System.Buffers;
using System.Globalization;
using System.IO.Pipelines;
-using System.Net.Security;
-using System.Net.Sockets;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices.Marshalling;
using System.Text;
-using Wired.IO.Http11Express.Context;
-using Wired.IO.Http11Express.Request;
-using Wired.IO.Protocol.Handlers;
+using Microsoft.Extensions.ObjectPool;
+using Wired.IO.Handlers.Http11Express.Context;
+using Wired.IO.Handlers.Http11Express.Request;
using Wired.IO.Protocol.Writers;
+using Wired.IO.Transport.Socket;
using Wired.IO.Utilities;
-using Wired.IO.Utilities.StringCache;
-namespace Wired.IO.Http11Express;
+namespace Wired.IO.Handlers.Http11Express;
#pragma warning disable CS1591 // (We document via XML comments below; suppress warnings if any members stay undocumented.)
@@ -40,7 +35,7 @@ public sealed class WiredHttp11Express : WiredHttp11Express
///
-public partial class WiredHttp11Express : IHttpHandler
+public partial class WiredHttp11Express : ISocketHttpHandler
// TContext can be a super type of Http11ExpressContext
where TContext : Http11ExpressContext, new()
{
@@ -97,7 +92,7 @@ public override bool Return(TContext context)
///
/// The connection is half-managed here: exceptions are swallowed to avoid noisy logs in benchmark scenarios; the stream is closed when the reader/writer complete.
///
- public async Task HandleClientAsync(Socket inner, Stream stream, Func pipeline, CancellationToken stoppingToken)
+ public async Task HandleClientAsync(System.Net.Sockets.Socket inner, Stream stream, Func pipeline, CancellationToken stoppingToken)
{
// Rent a context object from the pool
var context = ContextPool.Get();
@@ -918,44 +913,4 @@ private static void ParseHeaderLine(in ReadOnlySequence headerLine, IExpre
private const byte Equal = 0x3D; // '='
private const byte Colon = 0x3A; // ':'
private const byte SemiColon = 0x3B; // ';'
-}
-///
-/// Caches commonly-seen strings (routes, header keys/values, methods) to avoid repeated allocations
-/// and string interning during hot paths.
-///
-///
-/// Backed by custom fast hash caches sized for typical HTTP workloads. The pre-cached sets include common
-/// request methods and frequently-present headers/values seen in benchmarks (e.g., TechEmpower Plaintext JSON).
-///
-internal static class CachedData
-{
- /// Cache of parsed routes (path components of the URL).
- internal static readonly FastHashStringCache32 CachedRoutes = new FastHashStringCache32();
- /// Cache of query-string keys.
- internal static readonly FastHashStringCache32 CachedQueryKeys = new FastHashStringCache32();
- /// Pre-cached HTTP methods (8 common verbs).
- internal static readonly FastHashStringCache16 PreCachedHttpMethods = new FastHashStringCache16([
- "GET",
- "POST",
- "PUT",
- "DELETE",
- "PATCH",
- "HEAD",
- "OPTIONS",
- "TRACE"
- ], 8);
- /// Pre-cached header keys commonly present in requests.
- internal static readonly FastHashStringCache32 PreCachedHeaderKeys = new FastHashStringCache32([
- "Host",
- "User-Agent",
- "Cookie",
- "Accept",
- "Accept-Language",
- "Connection"
- ]);
- /// Pre-cached header values commonly seen on the wire.
- internal static readonly FastHashStringCache32 PreCachedHeaderValues = new FastHashStringCache32([
- "keep-alive",
- "server",
- ]);
}
\ No newline at end of file
diff --git a/Wired.IO/Http11Express/WiredHttp11Express.Response.cs b/Wired.IO/Handlers/Http11Express/WiredHttp11Express.Response.cs
similarity index 97%
rename from Wired.IO/Http11Express/WiredHttp11Express.Response.cs
rename to Wired.IO/Handlers/Http11Express/WiredHttp11Express.Response.cs
index 77f9cfc..768e87f 100644
--- a/Wired.IO/Http11Express/WiredHttp11Express.Response.cs
+++ b/Wired.IO/Handlers/Http11Express/WiredHttp11Express.Response.cs
@@ -1,12 +1,12 @@
using System.Buffers;
using System.Buffers.Text;
-using System.ComponentModel.DataAnnotations;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
+using Wired.IO.Handlers.Http11Express.Response;
using Wired.IO.Protocol.Response;
using Wired.IO.Utilities;
-namespace Wired.IO.Http11Express;
+namespace Wired.IO.Handlers.Http11Express;
public partial class WiredHttp11Express
{
diff --git a/Wired.IO/Http11Express/_README.txt b/Wired.IO/Handlers/Http11Express/_README.txt
similarity index 100%
rename from Wired.IO/Http11Express/_README.txt
rename to Wired.IO/Handlers/Http11Express/_README.txt
diff --git a/Wired.IO/Handlers/Http11Overclocked/Context/Http11OverclockedContext.cs b/Wired.IO/Handlers/Http11Overclocked/Context/Http11OverclockedContext.cs
new file mode 100644
index 0000000..1749a08
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Overclocked/Context/Http11OverclockedContext.cs
@@ -0,0 +1,40 @@
+using URocket.Connection;
+using Wired.IO.Handlers.Http11Overclocked.Request;
+using Wired.IO.Handlers.Http11Overclocked.Response;
+using Wired.IO.Protocol;
+using Wired.IO.Protocol.Request;
+
+namespace Wired.IO.Handlers.Http11Overclocked.Context;
+
+public class Http11OverclockedContext : IBaseContext
+{
+ public Connection Connection { get; internal set; } = null!;
+
+ public IBaseRequest Request { get; } = new Http11OverclockedRequest();
+
+ public IOverclockedResponse? Response { get; private set; }
+
+ private OverclockedResponseBuilder? _responseBuilder;
+
+ private OverclockedResponseBuilder ResponseBuilder => _responseBuilder ??= new OverclockedResponseBuilder(Response!);
+
+ public OverclockedResponseBuilder Respond()
+ {
+ Response ??= new Http11OverclockedResponse();
+ Response.Activate();
+
+ return ResponseBuilder;
+ }
+
+ public IServiceProvider Services { get; set; } = null!;
+
+ public CancellationToken CancellationToken { get; set; }
+
+ public void Clear()
+ {
+ Request.Clear();
+ Response?.Clear();
+ }
+
+ public void Dispose() { }
+}
\ No newline at end of file
diff --git a/Wired.IO/Handlers/Http11Overclocked/Request/Http11OverclockedRequest.cs b/Wired.IO/Handlers/Http11Overclocked/Request/Http11OverclockedRequest.cs
new file mode 100644
index 0000000..3097728
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Overclocked/Request/Http11OverclockedRequest.cs
@@ -0,0 +1,17 @@
+using Wired.IO.Protocol.Request;
+
+namespace Wired.IO.Handlers.Http11Overclocked.Request;
+
+public class Http11OverclockedRequest : IBaseRequest
+{
+ public string Route { get; set; } = null!;
+
+ public string HttpMethod { get; set; } = null!;
+
+ public void Clear()
+ {
+
+ }
+
+ public void Dispose() { }
+}
\ No newline at end of file
diff --git a/Wired.IO/Handlers/Http11Overclocked/Response/Http11OverclockedResponse.cs b/Wired.IO/Handlers/Http11Overclocked/Response/Http11OverclockedResponse.cs
new file mode 100644
index 0000000..1483913
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Overclocked/Response/Http11OverclockedResponse.cs
@@ -0,0 +1,36 @@
+using Wired.IO.Protocol.Response;
+using Wired.IO.Utilities;
+
+namespace Wired.IO.Handlers.Http11Overclocked.Response;
+
+public class Http11OverclockedResponse : IOverclockedResponse
+{
+ private bool _active;
+
+ public void Activate() => _active = true;
+
+ public bool IsActive() => _active;
+
+ public ResponseStatus Status { get; set; } = ResponseStatus.Ok;
+
+ public Action ContentHandler { get; set; } = null!;
+
+ public Func AsyncContentHandler { get; set; } = null!;
+
+ public Utf8View ContentType { get; set; }
+
+ public ulong? ContentLength { get; set; }
+
+ public void Clear()
+ {
+ _active = false;
+
+ ContentType = default;
+ ContentLength = null;
+
+ }
+
+ public void Dispose()
+ {
+ }
+}
\ No newline at end of file
diff --git a/Wired.IO/Handlers/Http11Overclocked/Response/IOverclockedResponse.cs b/Wired.IO/Handlers/Http11Overclocked/Response/IOverclockedResponse.cs
new file mode 100644
index 0000000..c7edfab
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Overclocked/Response/IOverclockedResponse.cs
@@ -0,0 +1,26 @@
+using Wired.IO.Protocol.Response;
+using Wired.IO.Utilities;
+
+namespace Wired.IO.Handlers.Http11Overclocked.Response;
+
+public interface IOverclockedResponse : IBaseResponse
+{
+ void Activate();
+
+ bool IsActive();
+
+ ///
+ /// The type of the content.
+ ///
+ Utf8View ContentType { get; set; }
+
+ ulong? ContentLength { get; set; }
+
+ ResponseStatus Status { get; set; }
+
+ Action ContentHandler { get; set; }
+
+ Func AsyncContentHandler { get; set; }
+
+ void Clear();
+}
\ No newline at end of file
diff --git a/Wired.IO/Handlers/Http11Overclocked/Response/OverclockedResponseBuilder.cs b/Wired.IO/Handlers/Http11Overclocked/Response/OverclockedResponseBuilder.cs
new file mode 100644
index 0000000..2629ac2
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Overclocked/Response/OverclockedResponseBuilder.cs
@@ -0,0 +1,43 @@
+using Wired.IO.Protocol.Response;
+using Wired.IO.Utilities;
+
+namespace Wired.IO.Handlers.Http11Overclocked.Response;
+
+public enum ContentStrategy
+{
+ Utf8JsonWriter
+}
+
+public class OverclockedResponseBuilder(IOverclockedResponse response)
+{
+
+
+ public OverclockedResponseBuilder Content(Action contentHandler, ulong? length = null)
+ {
+ response.ContentLength = length;
+ response.ContentHandler = contentHandler;
+
+ return this;
+ }
+
+ public OverclockedResponseBuilder Type(ReadOnlySpan contentType)
+ {
+ response.ContentType = Utf8View.FromLiteral(contentType);
+
+ return this;
+ }
+
+ public OverclockedResponseBuilder Status(ResponseStatus status)
+ {
+ response.Status = status;
+
+ return this;
+ }
+
+ public OverclockedResponseBuilder Length(ulong length)
+ {
+ response.ContentLength = length;
+
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/Wired.IO/Handlers/Http11Overclocked/WiredHttp11Overclocked.Handler.cs b/Wired.IO/Handlers/Http11Overclocked/WiredHttp11Overclocked.Handler.cs
new file mode 100644
index 0000000..5a37a5b
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Overclocked/WiredHttp11Overclocked.Handler.cs
@@ -0,0 +1,169 @@
+using System.Buffers;
+using Microsoft.Extensions.ObjectPool;
+using URocket.Connection;
+using URocket.Utils.UnmanagedMemoryManager;
+using Wired.IO.Handlers.Http11Overclocked.Context;
+using Wired.IO.Protocol.Writers;
+using Wired.IO.Transport.Rocket;
+
+namespace Wired.IO.Handlers.Http11Overclocked;
+
+// This Handler does not support pipelined requests
+
+public sealed class WiredHttp11Overclocked : WiredHttp11Rocket { }
+
+public partial class WiredHttp11Rocket : IRocketHttpHandler
+ where TContext : Http11OverclockedContext, new()
+{
+ ///
+ /// Object pool used to recycle request contexts for reduced allocations and improved performance.
+ ///
+ private static readonly ObjectPool ContextPool =
+ new DefaultObjectPool(new PipelinedContextPoolPolicy(), 4096 * 4);
+
+ ///
+ /// Pool policy that defines how to create and reset pooled instances.
+ ///
+ private class PipelinedContextPoolPolicy : PooledObjectPolicy
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ public override TContext Create() => new();
+
+ ///
+ /// Resets the context before returning it to the pool.
+ ///
+ /// The context instance to return.
+ /// true if the context can be reused; otherwise, false.
+ public override bool Return(TContext context)
+ {
+ // Overclocked context does not need to be cleared
+ //context.Clear(); // User-defined reset method to clean internal state.
+ return true;
+ }
+ }
+
+ public async Task HandleClientAsync(Connection connection, Func pipeline, CancellationToken stoppingToken)
+ {
+ // Rent a context object from the pool
+ var context = ContextPool.Get();
+ context.Connection = connection;
+
+ try
+ {
+ await ProcessRequestsAsync(context, pipeline);
+ }
+ catch(Exception e)
+ {
+ Console.WriteLine(e.Message);
+ // TODO Check the CancellationTokenSource Impl @ Express Handler
+ }
+ finally
+ {
+ // Return context to pool for reuse
+ ContextPool.Return(context);
+ }
+ }
+
+ private async Task ProcessRequestsAsync(TContext context, Func pipeline)
+ {
+ while (true)
+ {
+ var result = await context.Connection.ReadAsync(); // Read data from the wire
+ if (result.IsClosed)
+ break;
+
+ var rings = context.Connection.PeekAllSnapshotRingsAsUnmanagedMemory(result);
+ var sequence = rings.ToReadOnlySequence();
+
+ if (sequence.IsSingleSegment)
+ {
+ // Single segment, fast path, use span based operations
+ var span = sequence.FirstSpan;
+
+ var idx = span.IndexOf(CrlfCrlf);
+ if (idx == -1) /* Not enough data */ continue;
+
+ // Full header is present
+ var firstLineEnd = span.IndexOf(Crlf);
+ var firstHeader = span[..firstLineEnd];
+
+ var firstSpace = firstHeader.IndexOf(Space);
+ if (firstSpace == -1) throw new InvalidOperationException("Invalid request line");
+
+ context.Request.HttpMethod = CachedData.PreCachedHttpMethods.GetOrAdd(firstHeader[..firstSpace]);
+
+ var secondSpaceRelative = firstHeader[(firstSpace + 1)..].IndexOf(Space);
+ if (secondSpaceRelative == -1) throw new InvalidOperationException("Invalid request line");
+
+ var secondSpace = firstSpace + secondSpaceRelative + 1;
+ var url = firstHeader[(firstSpace + 1)..secondSpace];
+
+ // Url is same as route
+ context.Request.Route = CachedData.CachedRoutes.GetOrAdd(url);
+
+ // No more parsing, this handler now delegates parsing control to the request pipeline.
+ // We could also add logic to check whether a body is present and wait for more data if body is not fully received. - Not needed for this benchmark
+ }
+ else
+ {
+ var reader = new SequenceReader(sequence);
+ if (!reader.TryReadTo(out ReadOnlySequence headers, CrlfCrlf)) /* Not enough data */ continue;
+
+ // Full header is present
+
+ // Extract the Http method
+ if(!reader.TryReadTo(out ReadOnlySequence methodSequence, Space)) /* Not enough data */ continue;
+
+ context.Request.HttpMethod = CachedData.PreCachedHttpMethods.GetOrAdd(methodSequence.ToSpan());
+
+ // Read URL/path
+ if (!reader.TryReadTo(out ReadOnlySequence urlSequence, Space)) /* Not enough data */ continue;
+
+ // Url is same as route
+ context.Request.Route = CachedData.CachedRoutes.GetOrAdd(urlSequence.ToSpan());
+
+ // No more parsing, this handler now delegates parsing control to the request pipeline.
+ // We could also add logic to check whether a body is present and wait for more data if body is not fully received. - Not needed for this benchmark
+ }
+
+ // Execute request pipeline
+ await pipeline(context);
+
+ if (context.Response is not null && context.Response.IsActive())
+ {
+ WriteStatusLine(context.Connection, context.Response.Status);
+ WriteHeaders(context);
+ context.Response.ContentHandler();
+ }
+
+ await context.Connection.FlushAsync();
+
+ // Dequeue and return rings to the kernel
+ for(int i = 0; i < rings.Length; i++)
+ context.Connection.GetRing();
+ foreach (var ring in rings)
+ context.Connection.ReturnRing(ring.BufferId);
+
+ // Signal we are ready for a new read
+ context.Connection.ResetRead();
+ }
+ }
+
+ // ---- Constants & literals ----
+
+ /// CRLF delimiter used for line termination.
+ private static ReadOnlySpan Crlf => "\r\n"u8;
+ private static ReadOnlySpan CrlfCrlf => "\r\n\r\n"u8;
+
+ private const string ContentLength = "Content-Length";
+ private const string TransferEncoding = "Transfer-Encoding";
+
+ private const byte Space = 0x20; // ' '
+ private const byte Question = 0x3F; // '?'
+ private const byte QuerySeparator = 0x26; // '&'
+ private const byte Equal = 0x3D; // '='
+ private const byte Colon = 0x3A; // ':'
+ private const byte SemiColon = 0x3B; // ';'
+}
\ No newline at end of file
diff --git a/Wired.IO/Handlers/Http11Overclocked/WiredHttp11Overclocked.Response.cs b/Wired.IO/Handlers/Http11Overclocked/WiredHttp11Overclocked.Response.cs
new file mode 100644
index 0000000..01b0d1c
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Overclocked/WiredHttp11Overclocked.Response.cs
@@ -0,0 +1,57 @@
+using System.Buffers;
+using System.Buffers.Text;
+using System.Runtime.CompilerServices;
+using Wired.IO.Protocol.Response;
+using Wired.IO.Utilities;
+
+namespace Wired.IO.Handlers.Http11Overclocked;
+
+public partial class WiredHttp11Rocket
+{
+ private static ReadOnlySpan ServerHeaderName => "Server: "u8;
+ private static ReadOnlySpan ContentTypeHeader => "Content-Type: "u8;
+ private static ReadOnlySpan ContentLengthHeader => "Content-Length: "u8;
+ private static ReadOnlySpan ContentEncodingHeader => "Content-Encoding: "u8;
+ private static ReadOnlySpan TransferEncodingHeader => "Transfer-Encoding: "u8;
+ private static ReadOnlySpan TransferEncodingChunkedHeader => "Transfer-Encoding: chunked\r\n"u8;
+ private static ReadOnlySpan LastModifiedHeader => "Last-Modified: "u8;
+ private static ReadOnlySpan ExpiresHeader => "Expires: "u8;
+ private static ReadOnlySpan ConnectionHeader => "Connection: "u8;
+ private static ReadOnlySpan DateHeader => "Date: "u8;
+
+ [SkipLocalsInit]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteStatusLine(IBufferWriter writer, ResponseStatus statusCode)
+ => writer.Write(HttpStatusLines.Lines[(int)statusCode]);
+
+ [SkipLocalsInit]
+ private static void WriteHeaders(TContext context)
+ {
+ var writer = context.Connection;
+
+ writer.Write("Server: W\r\n"u8);
+
+ if (!context.Response!.ContentType.IsEmpty)
+ {
+ writer.Write(ContentTypeHeader);
+ writer.Write(context.Response.ContentType.AsSpan());
+ writer.Write("\r\n"u8);
+ }
+
+ // If ContentLength is not zero, its length is known and is valid to use Content-Length header
+ if (context.Response.ContentLength is not null)
+ {
+ writer.Write(ContentLengthHeader);
+
+ var buffer = writer.GetSpan(16); // 16 is enough for any int in UTF-8
+ if (!Utf8Formatter.TryFormat((ulong)context.Response.ContentLength, buffer, out var written))
+ throw new InvalidOperationException("Failed to format int");
+
+ writer.Advance(written);
+
+ writer.Write("\r\n"u8);
+ }
+
+ writer.Write(DateHelper.HeaderBytes);
+ }
+}
\ No newline at end of file
diff --git a/Wired.IO/Handlers/Http11Rocket/Context/Http11RocketContext.cs b/Wired.IO/Handlers/Http11Rocket/Context/Http11RocketContext.cs
new file mode 100644
index 0000000..8b0c033
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Rocket/Context/Http11RocketContext.cs
@@ -0,0 +1,58 @@
+using System.IO.Pipelines;
+using URocket.Connection;
+using Wired.IO.Handlers.Http11Express.Request;
+using Wired.IO.Handlers.Http11Express.Response;
+using Wired.IO.Protocol;
+using Wired.IO.Utilities;
+
+namespace Wired.IO.Handlers.Http11Rocket.Context;
+
+// This class cannot be sealed, might have super types
+public class Http11RocketContext : IBaseContext
+{
+ public Connection Connection { get; internal set; } = null!;
+
+ public PipeReader Reader { get; set; } = null!;
+
+ public PipeWriter Writer { get; set; } = null!;
+
+ public IExpressRequest Request { get; } = new Http11ExpressRequest()
+ {
+ Headers = new PooledDictionary(
+ capacity: 8,
+ comparer: StringComparer.OrdinalIgnoreCase),
+ QueryParameters = new PooledDictionary(
+ capacity: 8,
+ comparer: StringComparer.OrdinalIgnoreCase)
+ };
+
+ public IExpressResponse? Response { get; private set; }
+
+ private ExpressResponseBuilder? _responseBuilder;
+ private ExpressResponseBuilder ResponseBuilder => _responseBuilder ??= new ExpressResponseBuilder(Response!);
+
+ public ExpressResponseBuilder Respond()
+ {
+ Response ??= new Http11ExpressResponse();
+ Response.Activate();
+
+ return ResponseBuilder;
+ }
+
+ public IServiceProvider Services { get; set; } = null!;
+
+ public CancellationToken CancellationToken { get; set; }
+
+ public void Clear()
+ {
+ Connection = null!;
+ Response?.Clear();
+ Request.Clear();
+ }
+
+ public void Dispose()
+ {
+ Response?.Dispose();
+ Request.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/Wired.IO/Handlers/Http11Rocket/WiredHttp11Rocket.Handler.cs b/Wired.IO/Handlers/Http11Rocket/WiredHttp11Rocket.Handler.cs
new file mode 100644
index 0000000..06a89e1
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Rocket/WiredHttp11Rocket.Handler.cs
@@ -0,0 +1,788 @@
+using System.Buffers;
+using System.Globalization;
+using System.IO.Pipelines;
+using System.Runtime.CompilerServices;
+using System.Text;
+using Microsoft.Extensions.ObjectPool;
+using URocket.Connection;
+using Wired.IO.Handlers.Http11Express.Request;
+using Wired.IO.Handlers.Http11Rocket.Context;
+using Wired.IO.Protocol.Writers;
+using Wired.IO.Transport.Rocket;
+using Wired.IO.Utilities;
+
+namespace Wired.IO.Handlers.Http11Rocket;
+
+// Public non-generic facade
+public sealed class WiredHttp11Rocket : WiredHttp11Rocket { }
+
+public partial class WiredHttp11Rocket : IRocketHttpHandler
+ where TContext : Http11RocketContext, new()
+{
+ ///
+ /// Parser state machine for the multi-segment path.
+ ///
+ private enum State
+ {
+ /// Expecting the request line: METHOD SP PATH SP HTTP/VERSION CRLF
+ StartLine,
+ /// Reading header lines until a blank line (CRLF) is found.
+ Headers,
+ /// Headers complete; body (if any) would be processed next.
+ Body
+ }
+
+ ///
+ /// Object pool used to recycle request contexts for reduced allocations and improved performance.
+ ///
+ private static readonly ObjectPool ContextPool =
+ new DefaultObjectPool(new PipelinedContextPoolPolicy(), 4096 * 4);
+
+ ///
+ /// Pool policy that defines how to create and reset pooled instances.
+ ///
+ private class PipelinedContextPoolPolicy : PooledObjectPolicy
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ public override TContext Create() => new();
+
+ ///
+ /// Resets the context before returning it to the pool.
+ ///
+ /// The context instance to return.
+ /// true if the context can be reused; otherwise, false.
+ public override bool Return(TContext context)
+ {
+ context.Clear(); // User-defined reset method to clean internal state.
+ return true;
+ }
+ }
+
+ ///
+ /// Handles a client connection using a , wiring it to /
+ /// and processing zero or more HTTP/1.1 requests in sequence.
+ ///
+ ///
+ /// The application pipeline delegate that processes a fully-parsed request.
+ /// Cancellation token signaling server shutdown.
+ /// A task that completes when the connection closes or parsing finishes.
+ ///
+ /// The connection is half-managed here: exceptions are swallowed to avoid noisy logs in benchmark scenarios; the stream is closed when the reader/writer complete.
+ ///
+ public async Task HandleClientAsync(Connection connection, Func pipeline, CancellationToken stoppingToken)
+ {
+ // Rent a context object from the pool
+ var context = ContextPool.Get();
+
+ var stream = new ConnectionStream(connection);
+
+ // Assign a new CancellationTokenSource to support per-request cancellation
+ var cts = new CancellationTokenSource();
+ context.CancellationToken = cts.Token;
+
+ // Wrap the stream in a PipeReader and PipeWriter for efficient buffered reads/writes
+ context.Reader = PipeReader.Create(stream,
+ new StreamPipeReaderOptions(
+ MemoryPool.Shared,
+ leaveOpen: false,
+ bufferSize: 4096 * 4,
+ minimumReadSize: 1024));
+
+ context.Writer = PipeWriter.Create(stream,
+ new StreamPipeWriterOptions(
+ MemoryPool.Shared,
+ leaveOpen: false,
+ minimumBufferSize: 512));
+
+ try
+ {
+ await ProcessRequestsAsync(context, pipeline);
+ }
+ catch
+ {
+ // Swallow all exceptions; connection will be closed silently
+ await cts.CancelAsync();
+ }
+ finally
+ {
+ // Gracefully complete the reader/writer to release underlying resources
+ await context.Reader.CompleteAsync();
+ await context.Writer.CompleteAsync();
+
+ // Return context to pool for reuse
+ ContextPool.Return(context);
+ }
+ }
+
+ ///
+ /// Reads from the and parses as many complete HTTP requests as are currently available,
+ /// dispatching each to .
+ ///
+ /// The pooled per-connection context.
+ /// Application delegate to invoke once a request line and headers are parsed.
+ ///
+ /// Uses a fast path for single-segment buffers to minimize copying and branching. For multi-segment buffers,
+ /// a is used to parse across segments safely.
+ ///
+ [SkipLocalsInit]
+ private static async Task ProcessRequestsAsync(TContext context, Func pipeline)
+ {
+ var state = State.StartLine;
+
+ while (true)
+ {
+ var readResult = await context.Reader.ReadAsync();
+ var buffer = readResult.Buffer;
+
+ var isCompleted = readResult.IsCompleted;
+
+ if (buffer.IsEmpty && isCompleted)
+ return;
+
+ var flush = false;
+
+ // Hot path: A new request is starting, and the buffer is a single segment
+ // If some of the request is already read, always fall back to multi-segment path
+ // This avoids complex state management in the single-segment path
+ // This optimizes for the common case of small requests that fit in one segment (vast majority of cases)
+ if (buffer.IsSingleSegment && state == State.StartLine)
+ {
+ var currentPosition = 0;
+
+ while (true)
+ {
+ if (buffer.Length == 0 || isCompleted)
+ break;
+
+ var requestReceived = ExtractHeaderFromSingleSegment(context, ref buffer, ref currentPosition);
+
+ if (!requestReceived)
+ {
+ context.Reader.AdvanceTo(buffer.GetPosition(0), buffer.GetPosition(buffer.FirstSpan.Length));
+ break;
+ }
+
+ context.Reader.AdvanceTo(buffer.GetPosition(currentPosition));
+
+ state = State.Body;
+
+ var bodyReceived = TryExtractBodyFromSingleSegment(context, ref buffer, ref currentPosition, out var bodyEmpty);
+ if (!bodyReceived)
+ break;
+
+ if (!bodyEmpty)
+ context.Reader.AdvanceTo(buffer.GetPosition(currentPosition));
+
+ // Handle the request pipeline
+ await pipeline(context);
+
+ // Respond
+ if(context.Response is not null && context.Response.IsActive())
+ await WriteResponse(context);
+
+ // Clear context for next request
+ context.Clear();
+
+ // Signal that there is something to flush
+ flush = true;
+
+ state = State.StartLine;
+
+ if (currentPosition == buffer.Length) // There is no more data, need to ReadAsync()
+ break;
+ }
+ }
+ // Slower path: multi-segment buffer or already partway through a request
+ // This handles cases where the request line or headers span multiple segments
+ else
+ {
+ var currentPosition = buffer.Start;
+
+ while (true)
+ {
+ if (buffer.Length == 0 || isCompleted)
+ break;
+
+ if (state != State.Body)
+ {
+ var requestReceived =
+ ExtractHeaderFromMultipleSegment(context, ref buffer, ref currentPosition, ref state);
+
+ context.Reader.AdvanceTo(currentPosition, buffer.End);
+
+ if (!requestReceived)
+ break;
+
+ state = State.Body;
+ }
+
+ //Try to get the body, if the full body isn't read, break
+ var bodyReceived = TryExtractBodyFromMultipleSegment(context, ref buffer, ref currentPosition, ref state, out var bodyExists);
+
+ if (bodyExists)
+ {
+ if (!bodyReceived)
+ {
+ break;
+ }
+
+ context.Reader.AdvanceTo(currentPosition, buffer.End);
+ }
+
+ await pipeline(context);
+
+ // Respond
+ if(context.Response is not null && context.Response.IsActive())
+ await WriteResponse(context);
+
+ context.Clear();
+ flush = true;
+
+ state = State.StartLine;
+
+ if (buffer.Slice(currentPosition).IsEmpty) // There is no more data, need to ReadAsync()
+ break;
+ }
+ }
+
+ if (flush)
+ await context.Writer.FlushAsync();
+ }
+ }
+
+ ///
+ /// Attempts to extract the HTTP request body from a single-segment buffer.
+ ///
+ /// The HTTP request context to populate with body data.
+ /// The input buffer containing the body.
+ ///
+ /// The current offset in the single segment span.
+ /// Updated to point just after the body if extraction succeeds.
+ ///
+ ///
+ /// Output flag indicating whether the request body was empty or absent.
+ /// true if no body is present, otherwise false.
+ ///
+ ///
+ /// true if the body was fully extracted or no body is required;
+ /// false if more data is needed from the transport.
+ ///
+ ///
+ /// Thrown if the Content-Length or chunked encoding format is invalid.
+ ///
+ [SkipLocalsInit]
+ private static bool TryExtractBodyFromSingleSegment(
+ TContext context,
+ ref ReadOnlySequence buffer,
+ ref int position,
+ out bool bodyEmpty)
+ {
+ bodyEmpty = true;
+
+ // Content-Length header present
+ // TODO: Test if this works, it's odd that the position is not used by method caller
+ var contentLengthAvailable = context.Request.Headers.TryGetValue(ContentLength, out var contentLengthValue);
+ if (contentLengthAvailable)
+ {
+ var validContentLength = int.TryParse(contentLengthValue, out var contentLength);
+ if (contentLength <= 0 || !validContentLength)
+ {
+ return true; // Invalid Content-Length header
+ }
+
+ bodyEmpty = false;
+
+ var remainingBytes = buffer.FirstSpan.Length - position;
+
+ if(remainingBytes < contentLength)
+ return false; // Not enough bytes yet
+
+ context.Request.ContentLength = contentLength;
+ context.Request.Content = ArrayPool.Shared.Rent(contentLength);
+ buffer.FirstSpan.Slice(position, contentLength).CopyTo(context.Request.Content);
+
+ position += contentLength;
+
+ return true;
+ }
+
+ // Transfer-Encoding header present
+ var transferEncodingAvailable = context.Request.Headers.TryGetValue(TransferEncoding, out var transferEncodingValue);
+ if (transferEncodingAvailable && transferEncodingValue.Equals("chunked", StringComparison.OrdinalIgnoreCase))
+ {
+ var currentOffset = 0;
+ var bufferSpan = buffer.FirstSpan[position..];
+
+ var bodyEndIndex = bufferSpan.IndexOf("0\r\n\r\n"u8);
+ if (bodyEndIndex == -1)
+ return false; // Not enough bytes yet
+
+ context.Request.Content = ArrayPool.Shared.Rent(bodyEndIndex);
+
+ while (true)
+ {
+ var chunkSizeEnd = bufferSpan[currentOffset..].IndexOf(Crlf);
+ var validChunkSize = int.TryParse(bufferSpan[currentOffset..(currentOffset + chunkSizeEnd)], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var currentChunkSize);
+ if (!validChunkSize)
+ throw new InvalidOperationException("Invalid chunk size");
+
+ if (currentChunkSize == 0) // End of body detected
+ {
+ if (chunkSizeEnd + currentOffset != bodyEndIndex + 1)
+ throw new InvalidOperationException("Invalid chunked body termination");
+
+ position += 5; // Move past "0\r\n\r\n"
+
+ bodyEmpty = false;
+ return true;
+ }
+
+ bodyEmpty = false;
+
+ var destinationSpan = context.Request.Content.AsSpan().Slice(currentOffset, currentChunkSize);
+ bufferSpan.Slice(chunkSizeEnd + 2, currentChunkSize).CopyTo(destinationSpan);
+
+ context.Request.ContentLength += currentChunkSize;
+ currentOffset += chunkSizeEnd + 2 + currentChunkSize + 2; // Move past chunk size line, chunk data, and trailing CRLF
+ position += currentOffset;
+ }
+ }
+
+ return true;
+ }
+
+
+ ///
+ /// Attempts to extract the HTTP request body from a multi-segment buffer.
+ ///
+ /// The HTTP request context to populate with body data.
+ /// The input buffer containing the body.
+ ///
+ /// The current position in the buffer.
+ /// Updated to point just after the body if extraction succeeds.
+ ///
+ /// Parser state, may be advanced after body extraction.
+ ///
+ ///
+ /// true if the body was fully extracted or no body is required;
+ /// false if more data is needed from the transport.
+ ///
+ ///
+ /// Thrown if the Content-Length or chunked encoding format is invalid.
+ ///
+ private static bool TryExtractBodyFromMultipleSegment(
+ TContext context,
+ ref ReadOnlySequence buffer,
+ ref SequencePosition position,
+ ref State state,
+ out bool bodyExists)
+ {
+ bodyExists = false;
+
+ // Content-Length header present
+ var contentLengthAvailable = context.Request.Headers.TryGetValue(ContentLength, out var contentLengthValue);
+ // TODO: Test if this works, it's odd that the position is not used by method caller
+ if (contentLengthAvailable)
+ {
+ bodyExists = true;
+
+ var validContentLength = int.TryParse(contentLengthValue, out var contentLength);
+
+ if (!validContentLength || contentLength < 0)
+ return false; // Invalid Content-Length header
+
+ if (buffer.Slice(position).Length < contentLength)
+ return false; // Not enough bytes yet
+
+ context.Request.Content = ArrayPool.Shared.Rent(contentLength);
+ buffer.Slice(position, contentLength).CopyTo(context.Request.Content);
+
+ context.Request.ContentLength = contentLength;
+
+ position = buffer.GetPosition(contentLength, position);
+
+ return true;
+ }
+
+ var transferEncodingAvailable = context.Request.Headers.TryGetValue(TransferEncoding, out var transferEncodingValue);
+ if (transferEncodingAvailable && transferEncodingValue.Equals("chunked", StringComparison.OrdinalIgnoreCase))
+ {
+ bodyExists = true;
+
+ var reader = new SequenceReader(buffer.Slice(position));
+ var currentOffset = context.Request.Content != null ? context.Request.ContentLength : 0;
+
+ while (true)
+ {
+ if (!reader.TryReadTo(out ReadOnlySequence chunkSizeSequence, Crlf))
+ return false; // Not enough bytes yet
+
+ var chunkSizeSpan = chunkSizeSequence.ToSpan();
+ if (!TryParseChunkSize(chunkSizeSpan, out var chunkSize))
+ throw new InvalidOperationException("Invalid chunk size");
+
+ if (chunkSize == 0) // End of body detected
+ {
+ if (!reader.TryReadTo(out ReadOnlySequence _, Crlf))
+ return false; // Not enough bytes yet
+
+ position = reader.Position;
+ break;
+ }
+
+ if (reader.Remaining < chunkSize + 2) // +2 for trailing CRLF
+ return false; // Not enough bytes yet
+
+ if (context.Request.Content == null)
+ {
+ // Estimate a reasonable buffer size for the first chunk, will be re-rented if more chunks arrive
+ context.Request.Content = ArrayPool.Shared.Rent(chunkSize);
+ }
+ else if (context.Request.Content.Length < currentOffset + chunkSize)
+ {
+ // Grow the buffer if needed
+ var newBuffer = ArrayPool.Shared.Rent(currentOffset + chunkSize);
+ context.Request.Content.AsSpan(0, currentOffset).CopyTo(newBuffer);
+ ArrayPool.Shared.Return(context.Request.Content);
+ context.Request.Content = newBuffer;
+ }
+
+ var destinationSpan = context.Request.Content.AsSpan(currentOffset, chunkSize);
+ if (!reader.TryCopyTo(destinationSpan))
+ return false; // Not enough bytes yet
+
+ currentOffset += chunkSize;
+
+ // Advance past chunk data and trailing CRLF
+ reader.Advance(chunkSize);
+ if (!reader.TryReadTo(out ReadOnlySequence _, Crlf))
+ return false; // Not enough bytes yet
+ }
+
+ context.Request.ContentLength = currentOffset;
+ position = reader.Position;
+ }
+
+ return true;
+ }
+
+ // Helper for parsing chunk size (hex)
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool TryParseChunkSize(ReadOnlySpan span, out int value)
+ {
+ value = 0;
+ int i = 0;
+ for (; i < span.Length; i++)
+ {
+ byte b = span[i];
+ if (b >= '0' && b <= '9')
+ value = (value << 4) + (b - '0');
+ else if (b >= 'A' && b <= 'F')
+ value = (value << 4) + (b - 'A' + 10);
+ else if (b >= 'a' && b <= 'f')
+ value = (value << 4) + (b - 'a' + 10);
+ else
+ return false;
+ }
+ return i > 0;
+ }
+
+ ///
+ /// Parses the request line and headers from a single-segment buffer.
+ ///
+ /// Target request context to populate.
+ /// The read-only buffer (single segment).
+ /// Current integer offset in the first segment; updated to point just after the header terminator.
+ /// if a complete header block was parsed; otherwise .
+ /// Thrown when the request line is malformed.
+ [SkipLocalsInit]
+ private static bool ExtractHeaderFromSingleSegment(
+ TContext context,
+ ref ReadOnlySequence buffer,
+ ref int position)
+ {
+ // Hot path, single segment buffer
+ var bufferSpan = buffer.FirstSpan[position..];
+ var fullHeaderIndex = bufferSpan.IndexOf("\r\n\r\n"u8);
+
+ if (fullHeaderIndex == -1)
+ return false;
+
+ // Whole headers are present for the request
+ // Parse first header
+
+ var lineEnd = bufferSpan.IndexOf("\r\n"u8);
+ var firstHeader = bufferSpan[..lineEnd];
+
+ var firstSpace = firstHeader.IndexOf(Space);
+ if (firstSpace == -1)
+ throw new InvalidOperationException("Invalid request line");
+
+ context.Request.HttpMethod = CachedData.PreCachedHttpMethods.GetOrAdd(firstHeader[..firstSpace]);
+
+ var secondSpaceRelative = firstHeader[(firstSpace + 1)..].IndexOf(Space);
+ if (secondSpaceRelative == -1)
+ throw new InvalidOperationException("Invalid request line");
+
+ var secondSpace = firstSpace + secondSpaceRelative + 1;
+ var url = firstHeader[(firstSpace + 1)..secondSpace];
+ var queryStart = url.IndexOf(Question); // (byte)'?'
+
+ if (queryStart != -1)
+ {
+ // Route has params
+ context.Request.Route = CachedData.CachedRoutes.GetOrAdd(url[..queryStart]);
+ var querySpan = url[(queryStart + 1)..];
+ var current = 0;
+ while (current < querySpan.Length)
+ {
+ var separator = querySpan[current..].IndexOf(QuerySeparator); // (byte)'&'
+ ReadOnlySpan pair;
+
+ if (separator == -1)
+ {
+ pair = querySpan[current..];
+ current = querySpan.Length;
+ }
+ else
+ {
+ pair = querySpan.Slice(current, separator);
+ current += separator + 1;
+ }
+
+ var equalsIndex = pair.IndexOf(Equal); // (byte)'='
+ if (equalsIndex == -1)
+ break;
+
+ context.Request.QueryParameters?
+ .TryAdd(CachedData.CachedQueryKeys.GetOrAdd(pair[..equalsIndex]),
+ Encoders.Utf8Encoder.GetString(pair[(equalsIndex + 1)..]));
+ }
+ }
+ else
+ {
+ // Url is same as route
+ context.Request.Route = CachedData.CachedRoutes.GetOrAdd(url);
+ }
+
+ // Parse remaining headers
+ var lineStart = 0;
+ while (true)
+ {
+ lineStart += lineEnd + 2;
+
+ lineEnd = bufferSpan[lineStart..].IndexOf("\r\n"u8);
+ if (lineEnd == 0)
+ {
+ // All Headers read
+ break;
+ }
+
+ var header = bufferSpan.Slice(lineStart, lineEnd);
+ var colonIndex = header.IndexOf(Colon);
+
+ if (colonIndex == -1)
+ {
+ // Malformed header
+ continue;
+ }
+
+ var headerKey = header[..colonIndex];
+ var headerValue = header[(colonIndex + 2)..];
+
+ context.Request.Headers
+ .TryAdd(CachedData.PreCachedHeaderKeys.GetOrAdd(headerKey), CachedData.PreCachedHeaderValues.GetOrAdd(headerValue));
+ }
+
+ position += fullHeaderIndex + 4;
+ //context.Reader.AdvanceTo(buffer.GetPosition(position));
+
+ return true;
+ }
+
+ ///
+ /// Parses the request line and headers from a multi-segment buffer using .
+ ///
+ /// Target request context to populate.
+ /// The read-only buffer (may span multiple segments).
+ /// The current sequence position; updated as bytes are consumed.
+ /// State machine indicating where to resume parsing.
+ /// if a complete header block was parsed; otherwise .
+ [SkipLocalsInit]
+ private static bool ExtractHeaderFromMultipleSegment(
+ TContext context,
+ ref ReadOnlySequence buffer,
+ ref SequencePosition position,
+ ref State state)
+ {
+ // Parse the complete headers, taking off from where it left off
+ var headerReader = new SequenceReader(buffer.Slice(position));
+
+ if (state == State.StartLine)
+ {
+ if (!TryParseRequestLine(ref headerReader, context.Request))
+ return false;
+ }
+
+ // Header route was read, update state
+ position = headerReader.Position;
+ state = State.Headers;
+
+ // Parse remaining headers
+ while (true)
+ {
+ if (!headerReader.TryReadTo(out ReadOnlySequence headerLine, Crlf))
+ return false;
+
+ position = headerReader.Position;
+
+ if (headerLine.Length == 0)
+ break; // Empty line indicates end of headers
+
+ ParseHeaderLine(in headerLine, context.Request);
+
+ if (headerReader.End)
+ return false;
+
+ }
+ // All headers were read
+ //state = State.Body;
+ position = headerReader.Position;
+ return true;
+ }
+
+ // ---- Parsing helpers ----
+ ///
+ /// Parses the request line (method, URL, version) from the reader position and advances past CRLF.
+ ///
+ /// Sequence reader positioned at the start of the request line.
+ /// Target request object to populate.
+ /// if the full request line was parsed; otherwise .
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [SkipLocalsInit]
+ private static bool TryParseRequestLine(ref SequenceReader reader, IExpressRequest request)
+ {
+ // Read method
+ if (!reader.TryReadTo(out ReadOnlySequence methodSequence, (byte)' '))
+ return false;
+
+ request.HttpMethod = CachedData.PreCachedHttpMethods.GetOrAdd(methodSequence.ToSpan());
+
+ // Read URL/path
+ if (!reader.TryReadTo(out ReadOnlySequence urlSequence, (byte)' '))
+ return false;
+
+ ParseUrl(in urlSequence, request);
+
+ // Skip HTTP version (read to end of line)
+ return reader.TryReadTo(out ReadOnlySequence _, Crlf);
+ }
+
+ ///
+ /// Parses the URL and optional query string into the request route and query dictionary.
+ ///
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [SkipLocalsInit]
+ private static void ParseUrl(in ReadOnlySequence urlSequence, IExpressRequest request)
+ {
+ var urlSpan = urlSequence.ToSpan();
+ var queryStart = urlSpan.IndexOf((byte)'?');
+
+ if (queryStart != -1)
+ {
+ // URL has query parameters
+ var routeSpan = urlSpan[..queryStart];
+ request.Route = CachedData.CachedRoutes.GetOrAdd(routeSpan);
+
+ // Parse query parameters
+ ParseQueryParameters(urlSpan[(queryStart + 1)..], request);
+ }
+ else
+ {
+ // Simple URL without query parameters
+ request.Route = CachedData.CachedRoutes.GetOrAdd(urlSpan);
+ }
+ }
+
+ ///
+ /// Parses a query string in key=value&key2=value2 form into .
+ ///
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [SkipLocalsInit]
+ private static void ParseQueryParameters(in ReadOnlySpan querySpan, IExpressRequest request)
+ {
+ var current = 0;
+ while (current < querySpan.Length)
+ {
+ var separator = querySpan[current..].IndexOf((byte)'&');
+ ReadOnlySpan pair;
+
+ if (separator == -1)
+ {
+ pair = querySpan[current..];
+ current = querySpan.Length;
+ }
+ else
+ {
+ pair = querySpan.Slice(current, separator);
+ current += separator + 1;
+ }
+
+ var equalsIndex = pair.IndexOf((byte)'=');
+ if (equalsIndex == -1)
+ continue;
+
+ var key = CachedData.CachedQueryKeys.GetOrAdd(pair[..equalsIndex]);
+ var value = Encoding.UTF8.GetString(pair[(equalsIndex + 1)..]);
+
+ request.QueryParameters?.TryAdd(key, value);
+ }
+ }
+
+ ///
+ /// Parses a single header line and adds it to .
+ ///
+ /// A buffer containing a single header line with trailing CRLF removed.
+ /// Target request.
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ [SkipLocalsInit]
+ private static void ParseHeaderLine(in ReadOnlySequence headerLine, IExpressRequest request)
+ {
+ var headerSpan = headerLine.ToSpan();
+ var colonIndex = headerSpan.IndexOf((byte)':');
+
+ if (colonIndex == -1)
+ return; // Malformed header, skip
+
+ var headerKey = headerSpan[..colonIndex];
+
+ // Skip colon and optional whitespace
+ var valueStart = colonIndex + 1;
+ while (valueStart < headerSpan.Length && headerSpan[valueStart] == (byte)' ')
+ valueStart++;
+
+ var headerValue = headerSpan[valueStart..];
+
+ request.Headers?.TryAdd(
+ CachedData.PreCachedHeaderKeys.GetOrAdd(headerKey),
+ CachedData.PreCachedHeaderValues.GetOrAdd(headerValue));
+ }
+
+ // ---- Constants & literals ----
+
+ /// CRLF delimiter used for line termination.
+ private static ReadOnlySpan Crlf => "\r\n"u8;
+ private static ReadOnlySpan CrlfCrlf => "\r\n\r\n"u8;
+
+ private const string ContentLength = "Content-Length";
+ private const string TransferEncoding = "Transfer-Encoding";
+
+ private const byte Space = 0x20; // ' '
+ private const byte Question = 0x3F; // '?'
+ private const byte QuerySeparator = 0x26; // '&'
+ private const byte Equal = 0x3D; // '='
+ private const byte Colon = 0x3A; // ':'
+ private const byte SemiColon = 0x3B; // ';'
+}
\ No newline at end of file
diff --git a/Wired.IO/Handlers/Http11Rocket/WiredHttp11Rocket.Response.cs b/Wired.IO/Handlers/Http11Rocket/WiredHttp11Rocket.Response.cs
new file mode 100644
index 0000000..ed156eb
--- /dev/null
+++ b/Wired.IO/Handlers/Http11Rocket/WiredHttp11Rocket.Response.cs
@@ -0,0 +1,119 @@
+using System.Buffers;
+using System.Buffers.Text;
+using System.IO.Pipelines;
+using System.Runtime.CompilerServices;
+using Wired.IO.Handlers.Http11Express.Response;
+using Wired.IO.Protocol.Response;
+using Wired.IO.Utilities;
+
+namespace Wired.IO.Handlers.Http11Rocket;
+
+public partial class WiredHttp11Rocket
+{
+ private static ReadOnlySpan ServerHeaderName => "Server: "u8;
+ private static ReadOnlySpan ContentTypeHeader => "Content-Type: "u8;
+ private static ReadOnlySpan ContentLengthHeader => "Content-Length: "u8;
+ private static ReadOnlySpan ContentEncodingHeader => "Content-Encoding: "u8;
+ private static ReadOnlySpan TransferEncodingHeader => "Transfer-Encoding: "u8;
+ private static ReadOnlySpan TransferEncodingChunkedHeader => "Transfer-Encoding: chunked\r\n"u8;
+ private static ReadOnlySpan LastModifiedHeader => "Last-Modified: "u8;
+ private static ReadOnlySpan ExpiresHeader => "Expires: "u8;
+ private static ReadOnlySpan ConnectionHeader => "Connection: "u8;
+ private static ReadOnlySpan DateHeader => "Date: "u8;
+
+ private static async Task WriteResponse(TContext context)
+ {
+ WriteStatusLine(context.Writer, context.Response!.Status);
+
+ WriteHeaders(context);
+
+ await WriteBody(context);
+ }
+
+ [SkipLocalsInit]
+ private static async ValueTask WriteBody(TContext context)
+ {
+ if (context.Response!.ContentLengthStrategy is ContentLengthStrategy.Action)
+ {
+ context.Response.Handler();
+ return;
+ }
+ else if (context.Response!.ContentLengthStrategy is ContentLengthStrategy.AsyncTask)
+ {
+ await context.Response.AsyncHandler();
+ return;
+ }
+
+ if (context.Response!.ContentLengthStrategy is ContentLengthStrategy.Utf8View)
+ {
+ context.Writer.Write(context.Response.Utf8Content.AsSpan());
+ return;
+ }
+
+ context.Response.Content?.Write(context.Writer);
+ }
+
+ [SkipLocalsInit]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteStatusLine(PipeWriter writer, ResponseStatus statusCode)
+ => writer.Write(HttpStatusLines.Lines[(int)statusCode]);
+
+ [SkipLocalsInit]
+ private static void WriteHeaders(TContext context)
+ {
+ var writer = context.Writer;
+
+ writer.Write("Server: W\r\n"u8);
+
+ if (!context.Response!.ContentType.IsEmpty)
+ {
+ writer.Write(ContentTypeHeader);
+ writer.Write(context.Response.ContentType.AsSpan());
+ writer.Write("\r\n"u8);
+ }
+
+ // If ContentLength is not zero, its length is known and is valid to use Content-Length header
+ if (context.Response.ContentLength is not null)
+ {
+ writer.Write(ContentLengthHeader);
+
+ var buffer = writer.GetSpan(16); // 16 is enough for any int in UTF-8
+ if (!Utf8Formatter.TryFormat((ulong)context.Response.ContentLength, buffer, out var written))
+ throw new InvalidOperationException("Failed to format int");
+
+ writer.Advance(written);
+
+ writer.Write("\r\n"u8);
+ }
+ else if (context.Response.ContentLengthStrategy is ContentLengthStrategy.Chunked or ContentLengthStrategy.Action)
+ {
+ writer.Write(TransferEncodingChunkedHeader);
+ }
+
+ if (context.Response.Utf8Headers is not null)
+ {
+ foreach (var header in context.Response.Utf8Headers)
+ {
+ writer.Write(header.Key.AsSpan());
+ writer.Write(": "u8);
+ writer.Write(header.Value.AsSpan());
+ writer.Write("\r\n"u8);
+ }
+ }
+
+ if (context.Response.Headers.Count > 0)
+ {
+ foreach (var header in context.Response.Headers)
+ {
+ writer.WriteString(header.Key);
+ writer.Write(": "u8);
+ writer.WriteString(header.Value);
+ writer.Write("\r\n"u8);
+ }
+ }
+
+ // TODO: Add Modified and Expires headers
+
+ writer.Write(DateHelper.HeaderBytes);
+ }
+}
\ No newline at end of file
diff --git a/Wired.IO/Protocol/Handlers/IHttpHandler.cs b/Wired.IO/Protocol/Handlers/IHttpHandler.cs
index ca5cc6e..7b0184d 100644
--- a/Wired.IO/Protocol/Handlers/IHttpHandler.cs
+++ b/Wired.IO/Protocol/Handlers/IHttpHandler.cs
@@ -1,32 +1,9 @@
-using System.Net.Sockets;
-using Wired.IO.Protocol.Request;
-using Wired.IO.Protocol.Response;
-
-namespace Wired.IO.Protocol.Handlers;
+namespace Wired.IO.Protocol.Handlers;
///
/// Defines a contract for handling client connections using a custom or HTTP-based protocol.
///
-public interface IHttpHandler
- where TContext : IBaseContext
+public interface IHttpHandler
{
- ///
- /// Processes a client connection and dispatches one or more protocol-compliant requests.
- ///
- ///
- /// The representing the client connection.
- ///
- /// A delegate that executes the application's request-handling pipeline, typically consisting of middleware and endpoint logic.
- ///
- ///
- /// A used to signal cancellation, such as during server shutdown.
- ///
- ///
- /// A that represents the asynchronous operation of handling the client session.
- ///
- Task HandleClientAsync(
- Socket inner,
- Stream stream,
- Func pipeline,
- CancellationToken stoppingToken);
+
}
\ No newline at end of file
diff --git a/Wired.IO/Protocol/IBaseContext.cs b/Wired.IO/Protocol/IBaseContext.cs
index 235f435..b573456 100644
--- a/Wired.IO/Protocol/IBaseContext.cs
+++ b/Wired.IO/Protocol/IBaseContext.cs
@@ -9,24 +9,6 @@ public interface IBaseContext : IDisposable
where TRequest : IBaseRequest
where TResponse : IBaseResponse
{
- ///
- /// Gets or sets the used to read incoming data from the client connection.
- ///
- ///
- /// This reader is typically bound to the network stream and used to consume HTTP request data
- /// such as headers, body content, or raw bytes from the client.
- ///
- PipeReader Reader { get; set; }
-
- ///
- /// Gets or sets the used to write outgoing data to the client connection.
- ///
- ///
- /// This writer is used to serialize the HTTP response, including headers and body, and flush it
- /// to the client. It can be wrapped with encoders like chunked or plain writers.
- ///
- PipeWriter Writer { get; set; }
-
///
/// Gets or sets the HTTP request for the current connection.
/// This property contains all the details of the incoming HTTP request,
diff --git a/Wired.IO/Protocol/Response/ResponseStatus.cs b/Wired.IO/Protocol/Response/ResponseStatus.cs
index b817d17..8d514d5 100644
--- a/Wired.IO/Protocol/Response/ResponseStatus.cs
+++ b/Wired.IO/Protocol/Response/ResponseStatus.cs
@@ -1,6 +1,4 @@
-using Wired.IO.Http11Express;
-
-namespace Wired.IO.Protocol.Response;
+namespace Wired.IO.Protocol.Response;
public enum ResponseStatus
{
diff --git a/Wired.IO/Transport/ITransport.cs b/Wired.IO/Transport/ITransport.cs
new file mode 100644
index 0000000..cf19d4c
--- /dev/null
+++ b/Wired.IO/Transport/ITransport.cs
@@ -0,0 +1,31 @@
+using System.Net;
+using System.Net.Security;
+using Microsoft.Extensions.Logging;
+using Wired.IO.Protocol;
+using Wired.IO.Protocol.Handlers;
+using Wired.IO.Protocol.Request;
+using Wired.IO.Protocol.Response;
+
+namespace Wired.IO.Transport;
+
+public interface ITransport : IDisposable
+ where TContext : IBaseContext
+{
+ IPAddress IPAddress { get; set; }
+
+ int Port { get; set; }
+
+ int Backlog { get; set; }
+
+ IHttpHandler HttpHandler { get; set; }
+
+ ILogger? Logger { get; set; }
+
+ bool TlsEnabled { get; set; }
+
+ SslServerAuthenticationOptions SslServerAuthenticationOptions { get; set; }
+
+ Func Pipeline { get; set; }
+
+ Task ExecuteAsync(CancellationToken stoppingToken);
+}
\ No newline at end of file
diff --git a/Wired.IO/Transport/NamedPipes/INamedPipesHttpHandler.cs b/Wired.IO/Transport/NamedPipes/INamedPipesHttpHandler.cs
new file mode 100644
index 0000000..67300ff
--- /dev/null
+++ b/Wired.IO/Transport/NamedPipes/INamedPipesHttpHandler.cs
@@ -0,0 +1,12 @@
+using Wired.IO.Protocol;
+using Wired.IO.Protocol.Handlers;
+using Wired.IO.Protocol.Request;
+using Wired.IO.Protocol.Response;
+
+namespace Wired.IO.Transport.NamedPipes;
+
+public interface INamedPipesHttpHandler : IHttpHandler
+ where TContext : IBaseContext
+{
+
+}
\ No newline at end of file
diff --git a/Wired.IO/Transport/NamedPipes/NamedPipesTransport.cs b/Wired.IO/Transport/NamedPipes/NamedPipesTransport.cs
new file mode 100644
index 0000000..8158beb
--- /dev/null
+++ b/Wired.IO/Transport/NamedPipes/NamedPipesTransport.cs
@@ -0,0 +1,39 @@
+using System.Net;
+using System.Net.Security;
+using Microsoft.Extensions.Logging;
+using Wired.IO.Protocol;
+using Wired.IO.Protocol.Handlers;
+using Wired.IO.Protocol.Request;
+using Wired.IO.Protocol.Response;
+
+namespace Wired.IO.Transport.NamedPipes;
+
+public class NamedPipesTransport : ITransport
+ where TContext : IBaseContext
+{
+ public IPAddress IPAddress { get; set; } = null!;
+
+ public int Port { get; set; }
+
+ public int Backlog { get; set; }
+
+ public IHttpHandler HttpHandler { get; set; } = null!;
+
+ public ILogger? Logger { get; set; }
+
+ public bool TlsEnabled { get; set; }
+
+ public SslServerAuthenticationOptions SslServerAuthenticationOptions { get; set; } = null!;
+
+ public Func Pipeline { get; set; } = null!;
+
+ public Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ // TODO release managed resources here
+ }
+}
\ No newline at end of file
diff --git a/Wired.IO/Transport/Rocket/IRocketHttpHandler.cs b/Wired.IO/Transport/Rocket/IRocketHttpHandler.cs
new file mode 100644
index 0000000..4b57dd4
--- /dev/null
+++ b/Wired.IO/Transport/Rocket/IRocketHttpHandler.cs
@@ -0,0 +1,16 @@
+using URocket.Connection;
+using Wired.IO.Protocol;
+using Wired.IO.Protocol.Handlers;
+using Wired.IO.Protocol.Request;
+using Wired.IO.Protocol.Response;
+
+namespace Wired.IO.Transport.Rocket;
+
+public interface IRocketHttpHandler : IHttpHandler
+ where TContext : IBaseContext
+{
+ Task HandleClientAsync(
+ Connection connection,
+ Func pipeline,
+ CancellationToken stoppingToken);
+}
\ No newline at end of file
diff --git a/Wired.IO/Transport/Rocket/RocketTransport.cs b/Wired.IO/Transport/Rocket/RocketTransport.cs
new file mode 100644
index 0000000..017e105
--- /dev/null
+++ b/Wired.IO/Transport/Rocket/RocketTransport.cs
@@ -0,0 +1,73 @@
+using System.Net;
+using System.Net.Security;
+using Microsoft.Extensions.Logging;
+using URocket.Engine;
+using URocket.Engine.Configs;
+using Wired.IO.Protocol;
+using Wired.IO.Protocol.Handlers;
+using Wired.IO.Protocol.Request;
+using Wired.IO.Protocol.Response;
+
+namespace Wired.IO.Transport.Rocket;
+
+public class RocketTransport : ITransport
+ where TContext : IBaseContext
+{
+ private Engine _engine = null!;
+
+ private EngineOptions _options;
+
+ public IPAddress IPAddress { get; set; } = null!;
+
+ public int Port { get; set; }
+
+ public int Backlog { get; set; }
+
+ public IHttpHandler HttpHandler { get; set; } = null!;
+
+ public ILogger? Logger { get; set; }
+
+ public bool TlsEnabled { get; set; } // Not Supported yet by uRocket
+
+ public SslServerAuthenticationOptions SslServerAuthenticationOptions { get; set; } = null!; // Not Supported yet by uRocket
+
+ public Func Pipeline { get; set; } = null!;
+
+ public RocketTransport(EngineOptions? options = null)
+ {
+ _options = options ?? new EngineOptions();
+ }
+
+ public async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ CreateEngine();
+
+ while(_engine.ServerRunning)
+ {
+ var connection = await _engine.AcceptAsync(stoppingToken);
+
+ if(connection == null)
+ continue;
+
+ _ = ((IRocketHttpHandler)HttpHandler).HandleClientAsync(connection, Pipeline, stoppingToken);
+ }
+ }
+
+ private void CreateEngine()
+ {
+ _engine= new Engine(new EngineOptions
+ {
+ Port = (ushort)Port,
+ Ip = "0.0.0.0",
+ Backlog = Backlog,
+ ReactorCount = 32
+ });
+
+ _engine.Listen();
+ }
+
+ public void Dispose()
+ {
+ _engine.Stop();
+ }
+}
\ No newline at end of file
diff --git a/Wired.IO/Transport/Socket/ISocketHttpHandler.cs b/Wired.IO/Transport/Socket/ISocketHttpHandler.cs
new file mode 100644
index 0000000..a31e117
--- /dev/null
+++ b/Wired.IO/Transport/Socket/ISocketHttpHandler.cs
@@ -0,0 +1,33 @@
+using Wired.IO.Protocol;
+using Wired.IO.Protocol.Handlers;
+using Wired.IO.Protocol.Request;
+using Wired.IO.Protocol.Response;
+
+namespace Wired.IO.Transport.Socket;
+
+///
+/// Defines a contract for handling client connections using a custom or HTTP-based protocol.
+///
+public interface ISocketHttpHandler : IHttpHandler
+ where TContext : IBaseContext
+{
+ ///
+ /// Processes a client connection and dispatches one or more protocol-compliant requests.
+ ///
+ ///
+ /// The representing the client connection.
+ ///
+ /// A delegate that executes the application's request-handling pipeline, typically consisting of middleware and endpoint logic.
+ ///
+ ///
+ /// A used to signal cancellation, such as during server shutdown.
+ ///
+ ///
+ /// A that represents the asynchronous operation of handling the client session.
+ ///
+ Task HandleClientAsync(
+ System.Net.Sockets.Socket inner,
+ Stream stream,
+ Func pipeline,
+ CancellationToken stoppingToken);
+}
\ No newline at end of file
diff --git a/Wired.IO/Transport/Socket/SocketTransport.cs b/Wired.IO/Transport/Socket/SocketTransport.cs
new file mode 100644
index 0000000..b78ed46
--- /dev/null
+++ b/Wired.IO/Transport/Socket/SocketTransport.cs
@@ -0,0 +1,276 @@
+using System.Net;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security;
+using System.Security.Authentication;
+using Microsoft.Extensions.Logging;
+using Wired.IO.Protocol;
+using Wired.IO.Protocol.Handlers;
+using Wired.IO.Protocol.Request;
+using Wired.IO.Protocol.Response;
+using Wired.IO.Utilities.MemoryBuffers;
+
+namespace Wired.IO.Transport.Socket;
+
+///
+/// Encapsulates the server's execution logic and abstracts the engine behavior (e.g., plain or TLS).
+///
+public sealed class SocketTransport : ITransport
+ where TContext : IBaseContext
+{
+ private System.Net.Sockets.Socket? _socket;
+
+ public IPAddress IPAddress { get; set; } = null!;
+
+ public int Port { get; set; }
+
+ public int Backlog { get; set; }
+
+ public IHttpHandler HttpHandler { get; set; } = null!;
+
+ public ILogger? Logger { get; set; }
+
+ public bool TlsEnabled { get; set; }
+
+ public Func Pipeline { get; set; }
+
+ ///
+ /// Gets or sets the TLS configuration for the server.
+ /// Defaults to , indicating no TLS enabled unless explicitly configured.
+ ///
+ public SslServerAuthenticationOptions SslServerAuthenticationOptions { get; set; } =
+ new SslServerAuthenticationOptions
+ {
+ EnabledSslProtocols = SslProtocols.None
+ };
+
+ public SocketTransport() { }
+
+ ///
+ /// Executes the engine logic using the provided cancellation token.
+ ///
+ /// Token used to signal cancellation of the server loop.
+ public async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ CreateListeningSocket();
+ if (TlsEnabled)
+ {
+ await RunAcceptLoopAsync(HandleTlsClientAsync, stoppingToken);
+ }
+ else
+ {
+ await RunAcceptLoopAsync(HandlePlainClientAsync, stoppingToken);
+ }
+ }
+
+ ///
+ /// Initializes the TCP listening socket and binds it to the configured IP address and port.
+ /// Uses dual-stack IPv6 socket with IPv4 support enabled.
+ ///
+ private void CreateListeningSocket()
+ {
+ //IPv4
+ //_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+
+ //IPv6 DualStack
+ _socket = new System.Net.Sockets.Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
+ _socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
+ _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
+ _socket.NoDelay = true;
+
+ _socket.Bind(new IPEndPoint(IPAddress, Port));
+ _socket.Listen(Backlog);
+ }
+
+ ///
+ /// Launches parallel accept loops, one per processor core, to maximize concurrent connection handling throughput.
+ ///
+ /// A delegate that handles a connected .
+ /// A token used to cancel the accept loops.
+ /// A task that completes when all accept loops have terminated.
+ private async Task RunAcceptLoopAsync(Func clientHandler, CancellationToken stoppingToken)
+ {
+ // Multiple concurrent accept loops for maximum throughput
+ //var acceptTasks = new Task[Environment.ProcessorCount/2];
+ var acceptTasks = new Task[4];
+ for (var i = 0; i < acceptTasks.Length; i++)
+ {
+ acceptTasks[i] = AcceptLoopAsync(clientHandler, stoppingToken);
+ }
+
+ await Task.WhenAll(acceptTasks);
+ }
+
+ ///
+ /// Continuously accepts incoming connections and dispatches them to the provided client handler delegate.
+ /// Each accepted client is processed in the background to avoid blocking the accept loop.
+ ///
+ /// A delegate that handles the accepted .
+ /// A token that cancels the accept loop when requested.
+ /// A task that completes when the loop is canceled or terminated due to error.
+ private async Task AcceptLoopAsync(Func clientHandler, CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ var client = await _socket!.AcceptAsync(cancellationToken);
+
+ client.NoDelay = true; // Disable Nagle's algorithm for low-latency communication
+
+ // Handle client without blocking accept loop
+ _ = HandleClientAsync(client, clientHandler, cancellationToken);
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Accept error: {ex.Message}");
+ await Task.Delay(100, cancellationToken); // Brief delay on error
+ }
+ }
+ }
+
+ ///
+ /// Invokes the specified handler for a connected client socket, ensuring proper error logging and disposal.
+ ///
+ /// The connected to handle.
+ /// The delegate responsible for processing the client connection.
+ /// The token used to cancel the operation.
+ /// A task that completes when client handling is finished.
+ private async Task HandleClientAsync(System.Net.Sockets.Socket client, Func clientHandler, CancellationToken stoppingToken)
+ {
+ try
+ {
+ await clientHandler(client, stoppingToken);
+ }
+ catch (Exception ex)
+ {
+ Logger?.LogTrace("Client could not be handled: {Exception}", ex);
+ }
+ finally
+ {
+ client.Dispose();
+ }
+ }
+
+ // -------------------------------------
+ // -------------------------------------
+
+ ///
+ /// Handles an incoming plain (non-TLS) TCP client connection.
+ /// Wraps the client socket in a and delegates request handling to .
+ ///
+ /// The connected TCP .
+ /// The used to cancel the operation.
+ private async Task HandlePlainClientAsync(System.Net.Sockets.Socket client, CancellationToken stoppingToken)
+ {
+ await using var networkStream = new PoolBufferedStream(new NetworkStream(client, ownsSocket: true), 65 * 1024);
+ await ((ISocketHttpHandler)HttpHandler).HandleClientAsync(
+ client,
+ networkStream,
+ Pipeline,
+ stoppingToken);
+ }
+
+ ///
+ /// Handles an incoming TLS (SSL) client connection.
+ /// Performs a TLS handshake using the configured and delegates secure stream handling to .
+ ///
+ /// The connected TCP .
+ /// The used to cancel the operation.
+ ///
+ /// Thrown if is not set.
+ ///
+ private async Task HandleTlsClientAsync(System.Net.Sockets.Socket client, CancellationToken stoppingToken)
+ {
+ if (SslServerAuthenticationOptions.ServerCertificate is null)
+ {
+ throw new SecurityException("SecurityOptions.ServerCertificate is null");
+ }
+
+ // Create and configure the SSL stream for the client connection
+ //
+ // TODO: Investigate leaveInnerStreamOpen flag
+ await using var stream = new PoolBufferedStream(new NetworkStream(client, ownsSocket: true), 65 * 1024);
+ await using var sslStream = new SslStream(stream,
+ false,
+ SslServerAuthenticationOptions.RemoteCertificateValidationCallback);
+
+ try
+ {
+ // Perform the TLS handshake
+ //
+ await sslStream.AuthenticateAsServerAsync(SslServerAuthenticationOptions, stoppingToken);
+ }
+ catch (Exception ex) when (HandleTlsException(ex))
+ {
+ // Unified handling of TLS failure message.
+ //
+ await SendTlsFailureMessageAsync(client);
+ }
+
+ // Handle the client connection securely
+ //
+ await ((ISocketHttpHandler)HttpHandler).HandleClientAsync(
+ client,
+ sslStream,
+ Pipeline,
+ stoppingToken);
+ }
+
+ ///
+ /// Handles and logs exceptions that occur during a TLS handshake.
+ /// Always returns true to allow usage in a catch when filter.
+ ///
+ /// The exception that was thrown during TLS negotiation.
+ /// true, indicating the exception should be handled.
+ private bool HandleTlsException(Exception ex)
+ {
+ switch (ex)
+ {
+ case AuthenticationException authEx:
+ Logger?.LogTrace("TLS Handshake failed due to authentication error: {Message}", authEx.Message);
+ break;
+ case InvalidOperationException invalidOpEx:
+ Logger?.LogTrace("TLS Handshake failed due to socket error: {Message}", invalidOpEx.Message);
+ break;
+ default:
+ Logger?.LogTrace("Unexpected error during TLS Handshake: {Message}", ex.Message);
+ break;
+ }
+
+ return true; // Ensure the exception is always caught
+ }
+
+ ///
+ /// Sends a simple plaintext error message to the client if the TLS handshake fails,
+ /// then closes the underlying socket.
+ ///
+ /// The TCP to notify and close.
+ /// A task representing the asynchronous operation.
+ private static async Task SendTlsFailureMessageAsync(System.Net.Sockets.Socket client)
+ {
+ var messageBytes = "TLS Handshake failed. Closing connection."u8.ToArray();
+
+ try
+ {
+ await client.SendAsync(messageBytes, SocketFlags.None);
+ }
+ catch
+ {
+ // Ignore errors while sending failure response
+ }
+ finally
+ {
+ client.Close();
+ }
+ }
+
+ public void Dispose()
+ {
+ _socket?.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/Wired.IO/Common/Extensions/SpanExtensions.cs b/Wired.IO/Utilities/Extensions/SpanExtensions.cs
similarity index 94%
rename from Wired.IO/Common/Extensions/SpanExtensions.cs
rename to Wired.IO/Utilities/Extensions/SpanExtensions.cs
index d98584c..e1389c2 100644
--- a/Wired.IO/Common/Extensions/SpanExtensions.cs
+++ b/Wired.IO/Utilities/Extensions/SpanExtensions.cs
@@ -1,7 +1,4 @@
-using System.Text;
-using Wired.IO.Utilities;
-
-namespace Wired.IO.Common.Extensions;
+namespace Wired.IO.Utilities.Extensions;
///
/// Provides extension methods for efficiently writing UTF-8 and ASCII text into a buffer.
diff --git a/Wired.IO/MemoryBuffers/PoolBufferedStream.cs b/Wired.IO/Utilities/MemoryBuffers/PoolBufferedStream.cs
similarity index 99%
rename from Wired.IO/MemoryBuffers/PoolBufferedStream.cs
rename to Wired.IO/Utilities/MemoryBuffers/PoolBufferedStream.cs
index 6675ca0..1c0ba82 100644
--- a/Wired.IO/MemoryBuffers/PoolBufferedStream.cs
+++ b/Wired.IO/Utilities/MemoryBuffers/PoolBufferedStream.cs
@@ -1,6 +1,6 @@
using System.Buffers;
-namespace Wired.IO.MemoryBuffers;
+namespace Wired.IO.Utilities.MemoryBuffers;
///
/// An output stream using a pooled array to buffer small writes.
diff --git a/Wired.IO/MemoryBuffers/PooledMemoryOwner.cs b/Wired.IO/Utilities/MemoryBuffers/PooledMemoryOwner.cs
similarity index 98%
rename from Wired.IO/MemoryBuffers/PooledMemoryOwner.cs
rename to Wired.IO/Utilities/MemoryBuffers/PooledMemoryOwner.cs
index 1c024b7..005d5ca 100644
--- a/Wired.IO/MemoryBuffers/PooledMemoryOwner.cs
+++ b/Wired.IO/Utilities/MemoryBuffers/PooledMemoryOwner.cs
@@ -1,6 +1,6 @@
using System.Buffers;
-namespace Wired.IO.MemoryBuffers;
+namespace Wired.IO.Utilities.MemoryBuffers;
///
/// A custom implementation of that uses an
diff --git a/Wired.IO/Wired.IO.csproj b/Wired.IO/Wired.IO.csproj
index 6d22fd0..14b316b 100644
--- a/Wired.IO/Wired.IO.csproj
+++ b/Wired.IO/Wired.IO.csproj
@@ -41,11 +41,12 @@
-
-
-
-
-
+
+
+
+
+
+