From baeef4f956a455003fc4d492893f7b194cc57bc6 Mon Sep 17 00:00:00 2001 From: HenryBartosch Date: Thu, 9 Apr 2026 12:08:36 +0200 Subject: [PATCH 1/4] Add Effinitive server implementation with Docker support --- Http11Probe.slnx | 1 + docs/content/servers/effinitive.md | 172 ++++++++++++++++++ src/Servers/EffinitiveServer/Dockerfile | 11 ++ .../EffinitiveServer/EffinitiveServer.csproj | 13 ++ src/Servers/EffinitiveServer/Program.cs | 108 +++++++++++ src/Servers/EffinitiveServer/probe.json | 1 + 6 files changed, 306 insertions(+) create mode 100644 docs/content/servers/effinitive.md create mode 100644 src/Servers/EffinitiveServer/Dockerfile create mode 100644 src/Servers/EffinitiveServer/EffinitiveServer.csproj create mode 100644 src/Servers/EffinitiveServer/Program.cs create mode 100644 src/Servers/EffinitiveServer/probe.json diff --git a/Http11Probe.slnx b/Http11Probe.slnx index 21fa1cd..c088d0f 100644 --- a/Http11Probe.slnx +++ b/Http11Probe.slnx @@ -13,5 +13,6 @@ + diff --git a/docs/content/servers/effinitive.md b/docs/content/servers/effinitive.md new file mode 100644 index 0000000..2fbc1a1 --- /dev/null +++ b/docs/content/servers/effinitive.md @@ -0,0 +1,172 @@ +--- +title: "Effinitive" +toc: false +breadcrumbs: false +--- + +**Language:** C# · [View source on GitHub](https://github.com/MDA2AV/Http11Probe/tree/main/src/Servers/EffinitiveServer) + +## Dockerfile + +```dockerfile +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src +COPY Directory.Build.props . +COPY src/Servers/EffinitiveServer/ src/Servers/EffinitiveServer/ +RUN dotnet restore src/Servers/EffinitiveServer/EffinitiveServer.csproj +RUN dotnet publish src/Servers/EffinitiveServer/EffinitiveServer.csproj -c Release -o /app --no-restore + +FROM mcr.microsoft.com/dotnet/runtime:10.0 +WORKDIR /app +COPY --from=build /app . +ENTRYPOINT ["dotnet", "EffinitiveServer.dll", "8080"] +``` + +## Source — `Program.cs` + +```csharp +using System.Text; +using EffinitiveFramework.Core; +using EffinitiveFramework.Core.Http; + +var port = args.Length > 0 && int.TryParse(args[0], out var p) ? p : 8080; + +var app = EffinitiveApp + .Create() + .UsePort(port) + .MapEndpoints() + .Build(); + +Console.WriteLine($"Effinitive listening on http://localhost:{port}"); +await app.RunAsync(); + +// ── GET / ────────────────────────────────────────────────────── + +sealed class GetRoot : NoRequestEndpointBase +{ + protected override string Method => "GET"; + protected override string Route => "/"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult("OK"); +} + +// ── POST / ───────────────────────────────────────────────────── + +sealed class PostRoot : NoRequestEndpointBase +{ + protected override string Method => "POST"; + protected override string Route => "/"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + { + var body = HttpContext?.Body; + return ValueTask.FromResult(body is { Length: > 0 } ? Encoding.UTF8.GetString(body) : ""); + } +} + +// ── GET/POST /echo ──────────────────────────────────────────── + +sealed class EchoGet : NoRequestEndpointBase +{ + protected override string Method => "GET"; + protected override string Route => "/echo"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); +} + +sealed class EchoPost : NoRequestEndpointBase +{ + protected override string Method => "POST"; + protected override string Route => "/echo"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); +} + +// ── GET/POST /cookie ────────────────────────────────────────── + +sealed class CookieGet : NoRequestEndpointBase +{ + protected override string Method => "GET"; + protected override string Route => "/cookie"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); +} + +sealed class CookiePost : NoRequestEndpointBase +{ + protected override string Method => "POST"; + protected override string Route => "/cookie"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); +} + +// ── Shared helpers ──────────────────────────────────────────── + +static class Helpers +{ + public static string EchoHeaders(HttpRequest? ctx) + { + if (ctx?.Headers is null) return ""; + var sb = new StringBuilder(); + foreach (var h in ctx.Headers) + sb.Append(h.Key).Append(": ").Append(h.Value).Append("\r\n"); + return sb.ToString(); + } + + public static string ParseCookies(HttpRequest? ctx) + { + if (ctx is null) return ""; + var sb = new StringBuilder(); + foreach (var c in ctx.Cookies) + sb.Append(c.Key).Append('=').Append(c.Value).Append("\r\n"); + return sb.ToString(); + } +} +``` + +## Test Results + +

Loading results...

+ +### Compliance + +
+ +### Smuggling + +
+ +### Malformed Input + +
+ +### Caching + +
+ +### Cookies + +
+ + + + diff --git a/src/Servers/EffinitiveServer/Dockerfile b/src/Servers/EffinitiveServer/Dockerfile new file mode 100644 index 0000000..775ab34 --- /dev/null +++ b/src/Servers/EffinitiveServer/Dockerfile @@ -0,0 +1,11 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src +COPY Directory.Build.props . +COPY src/Servers/EffinitiveServer/ src/Servers/EffinitiveServer/ +RUN dotnet restore src/Servers/EffinitiveServer/EffinitiveServer.csproj +RUN dotnet publish src/Servers/EffinitiveServer/EffinitiveServer.csproj -c Release -o /app --no-restore + +FROM mcr.microsoft.com/dotnet/runtime:10.0 +WORKDIR /app +COPY --from=build /app . +ENTRYPOINT ["dotnet", "EffinitiveServer.dll", "8080"] diff --git a/src/Servers/EffinitiveServer/EffinitiveServer.csproj b/src/Servers/EffinitiveServer/EffinitiveServer.csproj new file mode 100644 index 0000000..d357329 --- /dev/null +++ b/src/Servers/EffinitiveServer/EffinitiveServer.csproj @@ -0,0 +1,13 @@ + + + + Exe + net10.0 + false + + + + + + + diff --git a/src/Servers/EffinitiveServer/Program.cs b/src/Servers/EffinitiveServer/Program.cs new file mode 100644 index 0000000..145b001 --- /dev/null +++ b/src/Servers/EffinitiveServer/Program.cs @@ -0,0 +1,108 @@ +using System.Text; +using EffinitiveFramework.Core; +using EffinitiveFramework.Core.Http; + +var port = args.Length > 0 && int.TryParse(args[0], out var p) ? p : 8080; + +var app = EffinitiveApp + .Create() + .UsePort(port) + .MapEndpoints() + .Build(); + +Console.WriteLine($"Effinitive listening on http://localhost:{port}"); +await app.RunAsync(); + +// ── GET / ────────────────────────────────────────────────────── + +sealed class GetRoot : NoRequestEndpointBase +{ + protected override string Method => "GET"; + protected override string Route => "/"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult("OK"); +} + +// ── POST / ───────────────────────────────────────────────────── + +sealed class PostRoot : NoRequestEndpointBase +{ + protected override string Method => "POST"; + protected override string Route => "/"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + { + var body = HttpContext?.Body; + return ValueTask.FromResult(body is { Length: > 0 } ? Encoding.UTF8.GetString(body) : ""); + } +} + +// ── GET/POST /echo ──────────────────────────────────────────── + +sealed class EchoGet : NoRequestEndpointBase +{ + protected override string Method => "GET"; + protected override string Route => "/echo"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); +} + +sealed class EchoPost : NoRequestEndpointBase +{ + protected override string Method => "POST"; + protected override string Route => "/echo"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); +} + +// ── GET/POST /cookie ────────────────────────────────────────── + +sealed class CookieGet : NoRequestEndpointBase +{ + protected override string Method => "GET"; + protected override string Route => "/cookie"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); +} + +sealed class CookiePost : NoRequestEndpointBase +{ + protected override string Method => "POST"; + protected override string Route => "/cookie"; + protected override string ContentType => "text/plain"; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); +} + +// ── Shared helpers ──────────────────────────────────────────── + +static class Helpers +{ + public static string EchoHeaders(HttpRequest? ctx) + { + if (ctx?.Headers is null) return ""; + var sb = new StringBuilder(); + foreach (var h in ctx.Headers) + sb.Append(h.Key).Append(": ").Append(h.Value).Append("\r\n"); + return sb.ToString(); + } + + public static string ParseCookies(HttpRequest? ctx) + { + if (ctx is null) return ""; + var sb = new StringBuilder(); + foreach (var c in ctx.Cookies) + sb.Append(c.Key).Append('=').Append(c.Value).Append("\r\n"); + return sb.ToString(); + } +} diff --git a/src/Servers/EffinitiveServer/probe.json b/src/Servers/EffinitiveServer/probe.json new file mode 100644 index 0000000..66a0a17 --- /dev/null +++ b/src/Servers/EffinitiveServer/probe.json @@ -0,0 +1 @@ +{"name": "Effinitive", "language": "C#"} \ No newline at end of file From fa6c6a8cd734d7da0f41e98eb64a6f13f3b26551 Mon Sep 17 00:00:00 2001 From: HenryBartosch Date: Thu, 9 Apr 2026 12:43:01 +0200 Subject: [PATCH 2/4] Update Effinitive server documentation and refactor endpoint classes --- docs/content/servers/effinitive.md | 2 +- src/Servers/EffinitiveServer/Program.cs | 148 ++++++++++++------------ 2 files changed, 78 insertions(+), 72 deletions(-) diff --git a/docs/content/servers/effinitive.md b/docs/content/servers/effinitive.md index 2fbc1a1..33f8d94 100644 --- a/docs/content/servers/effinitive.md +++ b/docs/content/servers/effinitive.md @@ -167,6 +167,6 @@ static class Helpers document.getElementById('server-summary').innerHTML = '

No probe data available yet. Run the Probe workflow on main to generate results.

'; return; } - ProbeRender.renderServerPage('Nginx'); + ProbeRender.renderServerPage('Effinitive'); })(); diff --git a/src/Servers/EffinitiveServer/Program.cs b/src/Servers/EffinitiveServer/Program.cs index 145b001..16cab31 100644 --- a/src/Servers/EffinitiveServer/Program.cs +++ b/src/Servers/EffinitiveServer/Program.cs @@ -1,6 +1,7 @@ using System.Text; using EffinitiveFramework.Core; using EffinitiveFramework.Core.Http; +using EffinitiveServer.Endpoints; var port = args.Length > 0 && int.TryParse(args[0], out var p) ? p : 8080; @@ -13,96 +14,101 @@ Console.WriteLine($"Effinitive listening on http://localhost:{port}"); await app.RunAsync(); -// ── GET / ────────────────────────────────────────────────────── - -sealed class GetRoot : NoRequestEndpointBase +namespace EffinitiveServer.Endpoints { - protected override string Method => "GET"; - protected override string Route => "/"; - protected override string ContentType => "text/plain"; + // ── GET / ────────────────────────────────────────────────────── - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult("OK"); -} + sealed class GetRoot : NoRequestEndpointBase + { + protected override string Method => "GET"; + protected override string Route => "/"; + protected override string ContentType => Helpers.TextPlain; -// ── POST / ───────────────────────────────────────────────────── + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult("OK"); + } -sealed class PostRoot : NoRequestEndpointBase -{ - protected override string Method => "POST"; - protected override string Route => "/"; - protected override string ContentType => "text/plain"; + // ── POST / ───────────────────────────────────────────────────── - public override ValueTask HandleAsync(CancellationToken ct = default) + sealed class PostRoot : NoRequestEndpointBase { - var body = HttpContext?.Body; - return ValueTask.FromResult(body is { Length: > 0 } ? Encoding.UTF8.GetString(body) : ""); + protected override string Method => "POST"; + protected override string Route => "/"; + protected override string ContentType => Helpers.TextPlain; + + public override ValueTask HandleAsync(CancellationToken ct = default) + { + var body = HttpContext?.Body; + return ValueTask.FromResult(body is { Length: > 0 } ? Encoding.UTF8.GetString(body) : ""); + } } -} -// ── GET/POST /echo ──────────────────────────────────────────── + // ── GET/POST /echo ──────────────────────────────────────────── -sealed class EchoGet : NoRequestEndpointBase -{ - protected override string Method => "GET"; - protected override string Route => "/echo"; - protected override string ContentType => "text/plain"; - - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); -} - -sealed class EchoPost : NoRequestEndpointBase -{ - protected override string Method => "POST"; - protected override string Route => "/echo"; - protected override string ContentType => "text/plain"; - - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); -} + sealed class EchoGet : NoRequestEndpointBase + { + protected override string Method => "GET"; + protected override string Route => "/echo"; + protected override string ContentType => Helpers.TextPlain; -// ── GET/POST /cookie ────────────────────────────────────────── + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); + } -sealed class CookieGet : NoRequestEndpointBase -{ - protected override string Method => "GET"; - protected override string Route => "/cookie"; - protected override string ContentType => "text/plain"; + sealed class EchoPost : NoRequestEndpointBase + { + protected override string Method => "POST"; + protected override string Route => "/echo"; + protected override string ContentType => Helpers.TextPlain; - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); -} + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); + } -sealed class CookiePost : NoRequestEndpointBase -{ - protected override string Method => "POST"; - protected override string Route => "/cookie"; - protected override string ContentType => "text/plain"; + // ── GET/POST /cookie ────────────────────────────────────────── - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); -} + sealed class CookieGet : NoRequestEndpointBase + { + protected override string Method => "GET"; + protected override string Route => "/cookie"; + protected override string ContentType => Helpers.TextPlain; -// ── Shared helpers ──────────────────────────────────────────── + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); + } -static class Helpers -{ - public static string EchoHeaders(HttpRequest? ctx) + sealed class CookiePost : NoRequestEndpointBase { - if (ctx?.Headers is null) return ""; - var sb = new StringBuilder(); - foreach (var h in ctx.Headers) - sb.Append(h.Key).Append(": ").Append(h.Value).Append("\r\n"); - return sb.ToString(); + protected override string Method => "POST"; + protected override string Route => "/cookie"; + protected override string ContentType => Helpers.TextPlain; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); } - public static string ParseCookies(HttpRequest? ctx) + // ── Shared helpers ──────────────────────────────────────────── + + static class Helpers { - if (ctx is null) return ""; - var sb = new StringBuilder(); - foreach (var c in ctx.Cookies) - sb.Append(c.Key).Append('=').Append(c.Value).Append("\r\n"); - return sb.ToString(); + public const string TextPlain = "text/plain"; + + public static string EchoHeaders(HttpRequest? ctx) + { + if (ctx?.Headers is null) return ""; + var sb = new StringBuilder(); + foreach (var h in ctx.Headers) + sb.Append(h.Key).Append(": ").Append(h.Value).Append("\r\n"); + return sb.ToString(); + } + + public static string ParseCookies(HttpRequest? ctx) + { + if (ctx is null) return ""; + var sb = new StringBuilder(); + foreach (var c in ctx.Cookies) + sb.Append(c.Key).Append('=').Append(c.Value).Append("\r\n"); + return sb.ToString(); + } } } From a7397b8df00c647808b8c0817d10f2231fdfd529 Mon Sep 17 00:00:00 2001 From: HenryBartosch Date: Thu, 9 Apr 2026 13:22:33 +0200 Subject: [PATCH 3/4] Refactor Dockerfile and update EffinitiveFramework.Core version in project file --- src/Servers/EffinitiveServer/Dockerfile | 1 + src/Servers/EffinitiveServer/EffinitiveServer.csproj | 2 +- src/Servers/EffinitiveServer/Program.cs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Servers/EffinitiveServer/Dockerfile b/src/Servers/EffinitiveServer/Dockerfile index 775ab34..ad3c2bc 100644 --- a/src/Servers/EffinitiveServer/Dockerfile +++ b/src/Servers/EffinitiveServer/Dockerfile @@ -8,4 +8,5 @@ RUN dotnet publish src/Servers/EffinitiveServer/EffinitiveServer.csproj -c Relea FROM mcr.microsoft.com/dotnet/runtime:10.0 WORKDIR /app COPY --from=build /app . +USER $APP_UID ENTRYPOINT ["dotnet", "EffinitiveServer.dll", "8080"] diff --git a/src/Servers/EffinitiveServer/EffinitiveServer.csproj b/src/Servers/EffinitiveServer/EffinitiveServer.csproj index d357329..dc4fd83 100644 --- a/src/Servers/EffinitiveServer/EffinitiveServer.csproj +++ b/src/Servers/EffinitiveServer/EffinitiveServer.csproj @@ -7,7 +7,7 @@ - +
diff --git a/src/Servers/EffinitiveServer/Program.cs b/src/Servers/EffinitiveServer/Program.cs index 16cab31..d1eba00 100644 --- a/src/Servers/EffinitiveServer/Program.cs +++ b/src/Servers/EffinitiveServer/Program.cs @@ -1,7 +1,6 @@ using System.Text; using EffinitiveFramework.Core; using EffinitiveFramework.Core.Http; -using EffinitiveServer.Endpoints; var port = args.Length > 0 && int.TryParse(args[0], out var p) ? p : 8080; From 18b924e4d0dcc01f8fcd75aabf75953b8c5663c0 Mon Sep 17 00:00:00 2001 From: HenryBartosch Date: Thu, 9 Apr 2026 13:25:45 +0200 Subject: [PATCH 4/4] Updated md document --- docs/content/servers/effinitive.md | 148 +++++++++++++++-------------- 1 file changed, 77 insertions(+), 71 deletions(-) diff --git a/docs/content/servers/effinitive.md b/docs/content/servers/effinitive.md index 33f8d94..1cf0e50 100644 --- a/docs/content/servers/effinitive.md +++ b/docs/content/servers/effinitive.md @@ -19,6 +19,7 @@ RUN dotnet publish src/Servers/EffinitiveServer/EffinitiveServer.csproj -c Relea FROM mcr.microsoft.com/dotnet/runtime:10.0 WORKDIR /app COPY --from=build /app . +USER $APP_UID ENTRYPOINT ["dotnet", "EffinitiveServer.dll", "8080"] ``` @@ -40,97 +41,102 @@ var app = EffinitiveApp Console.WriteLine($"Effinitive listening on http://localhost:{port}"); await app.RunAsync(); -// ── GET / ────────────────────────────────────────────────────── - -sealed class GetRoot : NoRequestEndpointBase +namespace EffinitiveServer.Endpoints { - protected override string Method => "GET"; - protected override string Route => "/"; - protected override string ContentType => "text/plain"; + // ── GET / ────────────────────────────────────────────────────── - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult("OK"); -} + sealed class GetRoot : NoRequestEndpointBase + { + protected override string Method => "GET"; + protected override string Route => "/"; + protected override string ContentType => Helpers.TextPlain; -// ── POST / ───────────────────────────────────────────────────── + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult("OK"); + } -sealed class PostRoot : NoRequestEndpointBase -{ - protected override string Method => "POST"; - protected override string Route => "/"; - protected override string ContentType => "text/plain"; + // ── POST / ───────────────────────────────────────────────────── - public override ValueTask HandleAsync(CancellationToken ct = default) + sealed class PostRoot : NoRequestEndpointBase { - var body = HttpContext?.Body; - return ValueTask.FromResult(body is { Length: > 0 } ? Encoding.UTF8.GetString(body) : ""); + protected override string Method => "POST"; + protected override string Route => "/"; + protected override string ContentType => Helpers.TextPlain; + + public override ValueTask HandleAsync(CancellationToken ct = default) + { + var body = HttpContext?.Body; + return ValueTask.FromResult(body is { Length: > 0 } ? Encoding.UTF8.GetString(body) : ""); + } } -} -// ── GET/POST /echo ──────────────────────────────────────────── + // ── GET/POST /echo ──────────────────────────────────────────── -sealed class EchoGet : NoRequestEndpointBase -{ - protected override string Method => "GET"; - protected override string Route => "/echo"; - protected override string ContentType => "text/plain"; - - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); -} - -sealed class EchoPost : NoRequestEndpointBase -{ - protected override string Method => "POST"; - protected override string Route => "/echo"; - protected override string ContentType => "text/plain"; - - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); -} + sealed class EchoGet : NoRequestEndpointBase + { + protected override string Method => "GET"; + protected override string Route => "/echo"; + protected override string ContentType => Helpers.TextPlain; -// ── GET/POST /cookie ────────────────────────────────────────── + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); + } -sealed class CookieGet : NoRequestEndpointBase -{ - protected override string Method => "GET"; - protected override string Route => "/cookie"; - protected override string ContentType => "text/plain"; + sealed class EchoPost : NoRequestEndpointBase + { + protected override string Method => "POST"; + protected override string Route => "/echo"; + protected override string ContentType => Helpers.TextPlain; - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); -} + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.EchoHeaders(HttpContext)); + } -sealed class CookiePost : NoRequestEndpointBase -{ - protected override string Method => "POST"; - protected override string Route => "/cookie"; - protected override string ContentType => "text/plain"; + // ── GET/POST /cookie ────────────────────────────────────────── - public override ValueTask HandleAsync(CancellationToken ct = default) - => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); -} + sealed class CookieGet : NoRequestEndpointBase + { + protected override string Method => "GET"; + protected override string Route => "/cookie"; + protected override string ContentType => Helpers.TextPlain; -// ── Shared helpers ──────────────────────────────────────────── + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); + } -static class Helpers -{ - public static string EchoHeaders(HttpRequest? ctx) + sealed class CookiePost : NoRequestEndpointBase { - if (ctx?.Headers is null) return ""; - var sb = new StringBuilder(); - foreach (var h in ctx.Headers) - sb.Append(h.Key).Append(": ").Append(h.Value).Append("\r\n"); - return sb.ToString(); + protected override string Method => "POST"; + protected override string Route => "/cookie"; + protected override string ContentType => Helpers.TextPlain; + + public override ValueTask HandleAsync(CancellationToken ct = default) + => ValueTask.FromResult(Helpers.ParseCookies(HttpContext)); } - public static string ParseCookies(HttpRequest? ctx) + // ── Shared helpers ──────────────────────────────────────────── + + static class Helpers { - if (ctx is null) return ""; - var sb = new StringBuilder(); - foreach (var c in ctx.Cookies) - sb.Append(c.Key).Append('=').Append(c.Value).Append("\r\n"); - return sb.ToString(); + public const string TextPlain = "text/plain"; + + public static string EchoHeaders(HttpRequest? ctx) + { + if (ctx?.Headers is null) return ""; + var sb = new StringBuilder(); + foreach (var h in ctx.Headers) + sb.Append(h.Key).Append(": ").Append(h.Value).Append("\r\n"); + return sb.ToString(); + } + + public static string ParseCookies(HttpRequest? ctx) + { + if (ctx is null) return ""; + var sb = new StringBuilder(); + foreach (var c in ctx.Cookies) + sb.Append(c.Key).Append('=').Append(c.Value).Append("\r\n"); + return sb.ToString(); + } } } ```