Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions frameworks/effinitive/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY app/ .
RUN dotnet publish -c Release -o /out

FROM mcr.microsoft.com/dotnet/runtime:10.0

ADD https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb /packages-microsoft-prod.deb

RUN dpkg -i /packages-microsoft-prod.deb && rm /packages-microsoft-prod.deb \
&& apt-get update \
&& apt-get install -y --no-install-recommends libmsquic \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
Comment thread
HBartosch marked this conversation as resolved.
WORKDIR /app
COPY --from=build /out .
EXPOSE 8080 8443/tcp 8443/udp
ENTRYPOINT ["dotnet", "effinitive-arena.dll"]
37 changes: 37 additions & 0 deletions frameworks/effinitive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# effinitive

Ultra-high-performance custom HTTP server for .NET 10 — built from scratch for maximum speed.

## Stack

- **Language:** C# / .NET 10
- **Framework:** Effinitive
- **Engine:** Effinitive
- **Build:** Framework-dependent publish, `mcr.microsoft.com/dotnet/runtime:10.0` runtime with `libmsquic` installed for HTTP/3

## Endpoints

| Endpoint | Method | Description |
|---|---|---|
| `/pipeline` | GET | Returns `ok` (plain text) |
| `/baseline11` | GET | Sums query parameter values |
| `/baseline11` | POST | Sums query parameters + request body |
| `/baseline2` | GET | Sums query parameter values (HTTP/2 variant) |
| `/json/{count}` | GET | Returns `count` items from the preloaded dataset |
| `/compression` | GET | Gzip-compressed large JSON response |
Comment thread
HBartosch marked this conversation as resolved.
| `/db` | GET | SQLite range query with JSON response |
| `/async-db` | GET | PostgreSQL async range query |
| `/upload` | POST | Streams request body, returns byte count |
| `/static/*` | GET | Serves files from `/data/static` with MIME types and ETag support |
| `/ws` | GET | WebSocket echo — reflects text, binary, and ping/pong frames |

## Notes

- HTTP/1.1 on port 8080, HTTP/1+2+3 on port 8443 (TCP **and** UDP for QUIC)
- HTTP/3 via MsQuic (`libmsquic` installed in the runtime image); ALPN negotiation handles h2/h3 upgrade
- TLS certs loaded from `$TLS_CERT` / `$TLS_KEY` (default `/certs/server.crt` + `/certs/server.key`)
- Static files served from the `/data/static` volume mount at runtime; no files baked into the image
- JSON responses use source-generated `JsonSerializerContext` (`AppJsonContext`) so the hot path avoids reflection
- Postgres pooled via `Npgsql.NpgsqlDataSource` with multiplexing, built once at startup from `DATABASE_URL`
- WebSocket endpoint at `/ws` handles text, binary, and ping/pong frames; non-upgrade requests to `/ws` return 400
- Source split: `Program.cs` (startup + routing), `Models.cs` (DTOs + JSON context), `Tests/` (one file per test profile)
57 changes: 57 additions & 0 deletions frameworks/effinitive/app/Models.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Text.Json.Serialization;

public sealed record ResponseDto<T>(IReadOnlyList<T> Items, int Count);

public sealed class DbResponseItemDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Category { get; set; } = "";
public double Price { get; set; }
public int Quantity { get; set; }
public bool Active { get; set; }
public List<string> Tags { get; set; } = [];
public RatingInfo Rating { get; set; } = new();
}

sealed class DatasetItem
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Category { get; set; } = "";
public double Price { get; set; }
public int Quantity { get; set; }
public bool Active { get; set; }
public List<string> Tags { get; set; } = [];
public RatingInfo Rating { get; set; } = new();
}

public sealed class ProcessedItem
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Category { get; set; } = "";
public double Price { get; set; }
public int Quantity { get; set; }
public bool Active { get; set; }
public List<string> Tags { get; set; } = [];
public RatingInfo Rating { get; set; } = new();
public double Total { get; set; }
}

public sealed class RatingInfo
{
public double Score { get; set; }
public int Count { get; set; }
}


[JsonSerializable(typeof(ResponseDto<ProcessedItem>))]
[JsonSerializable(typeof(ResponseDto<DbResponseItemDto>))]
[JsonSerializable(typeof(DbResponseItemDto))]
[JsonSerializable(typeof(ProcessedItem))]
[JsonSerializable(typeof(RatingInfo))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(List<DatasetItem>))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
partial class AppJsonContext : JsonSerializerContext { }
59 changes: 59 additions & 0 deletions frameworks/effinitive/app/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Text.Json;
using EffinitiveFramework.Core;
using EffinitiveFramework.Core.WebSocket;

var certPath = Environment.GetEnvironmentVariable("TLS_CERT") ?? "/certs/server.crt";
var keyPath = Environment.GetEnvironmentVariable("TLS_KEY") ?? "/certs/server.key";
var hasCert = File.Exists(certPath) && File.Exists(keyPath);

// Static files: Docker mounts at /data/static, fallback to local wwwroot/static
var staticRoot = Directory.Exists("/data/static") ? "/data/static" : Path.Combine(AppContext.BaseDirectory, "wwwroot", "static");

var builder = EffinitiveApp.Create()
.UsePort(8080)
.UseResponseCompression()
.UseStaticFiles(staticRoot, "/static")
.MapWebSocket("/ws", async (ws, ct) =>
{
while (ws.IsOpen)
{
var msg = await ws.ReceiveAsync(ct);
if (msg == null) break;
await ws.SendAsync(msg.Value.Data, msg.Value.Type, ct);
}
})
.Configure(options =>
{
options.EnableDebugLogging = false;
options.MaxConcurrentConnections = 65536;
options.HeaderTimeout = TimeSpan.FromSeconds(30);
options.RequestTimeout = TimeSpan.FromSeconds(60);
options.IdleTimeout = TimeSpan.FromSeconds(120);
options.MaxRequestBodySize = 30 * 1024 * 1024;
options.JsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
WriteIndented = false
};
});

if (hasCert)
{
builder
.UseHttpsPort(8443)
.ConfigureTls(opts =>
{
opts.CertificatePath = certPath;
opts.KeyPath = keyPath;
});
}

var app = builder
.MapEndpoints()
.Build();

var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };

await app.RunAsync(cts.Token);
69 changes: 69 additions & 0 deletions frameworks/effinitive/app/Tests/AsyncDatabase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Text.Json;
using EffinitiveFramework.Core;
using EffinitiveFramework.Core.Http;
using Npgsql;

namespace effinitive.Tests;

public class AsyncDatabaseEndpoint : NoRequestAsyncEndpointBase<ResponseDto<DbResponseItemDto>>
{
protected override string Method => "GET";
protected override string Route => "/async-db";

private static readonly NpgsqlDataSource? PgDataSource = OpenPgPool();

private static NpgsqlDataSource? OpenPgPool()
{
var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
if (string.IsNullOrEmpty(dbUrl)) return null;
try
{
var uri = new Uri(dbUrl);
var userInfo = uri.UserInfo.Split(':');
var connStr = $"Host={uri.Host};Port={uri.Port};Username={userInfo[0]};Password={userInfo[1]};Database={uri.AbsolutePath.TrimStart('/')};Maximum Pool Size=256;Minimum Pool Size=64;Multiplexing=true;No Reset On Close=true;Max Auto Prepare=4;Auto Prepare Min Usages=1";
var builder = new NpgsqlDataSourceBuilder(connStr);
return builder.Build();
}
catch { return null; }
}

public override async Task<ResponseDto<DbResponseItemDto>> HandleAsync(CancellationToken ct)
{
if (PgDataSource == null)
{
throw new InvalidOperationException("Database not available: DATABASE_URL is not configured or invalid.");
}

var query = HttpContext?.Query ?? QueryCollection.Empty;
double min = query.GetDouble("min", 10);
double max = query.GetDouble("max", 50);
int limit = query.GetInt("limit", 50);

await using var cmd = PgDataSource.CreateCommand(
"SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3");

cmd.Parameters.AddWithValue(min);
cmd.Parameters.AddWithValue(max);
cmd.Parameters.AddWithValue(limit);

await using var reader = await cmd.ExecuteReaderAsync(ct);
var items = new List<DbResponseItemDto>(limit);

Comment thread
HBartosch marked this conversation as resolved.
while (await reader.ReadAsync(ct))
{
items.Add(new DbResponseItemDto
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Category = reader.GetString(2),
Price = reader.GetDouble(3),
Quantity = reader.GetInt32(4),
Active = reader.GetBoolean(5),
Tags = JsonSerializer.Deserialize(reader.GetString(6), AppJsonContext.Default.ListString)!,
Rating = new RatingInfo { Score = reader.GetDouble(7), Count = reader.GetInt32(8) }
});
}

return new ResponseDto<DbResponseItemDto>(items, items.Count);
}
}
66 changes: 66 additions & 0 deletions frameworks/effinitive/app/Tests/Baseline.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Text;
using EffinitiveFramework.Core;
using EffinitiveFramework.Core.Http;

namespace effinitive.Tests;

public class PipelineEndpoint : NoRequestEndpointBase<string>
{
protected override string Method => "GET";
protected override string Route => "/pipeline";
protected override string ContentType => "text/plain";

public override ValueTask<string> HandleAsync(CancellationToken ct)
=> ValueTask.FromResult("ok");
}

public class BaselineGetEndpoint : NoRequestEndpointBase<string>
{
protected override string Method => "GET";
protected override string Route => "/baseline11";
protected override string ContentType => "text/plain";

public override ValueTask<string> HandleAsync(CancellationToken ct)
{
var query = HttpContext?.Query ?? QueryCollection.Empty;
int a = query.GetInt("a");
int b = query.GetInt("b");
return ValueTask.FromResult((a + b).ToString());
}
}

public class BaselinePostEndpoint : NoRequestEndpointBase<string>
{
protected override string Method => "POST";
protected override string Route => "/baseline11";
protected override string ContentType => "text/plain";

public override ValueTask<string> HandleAsync(CancellationToken ct)
{
var query = HttpContext?.Query ?? QueryCollection.Empty;
int a = query.GetInt("a");
int b = query.GetInt("b");
int bodyVal = 0;
if (HttpContext?.Body.Length > 0)
{
var bodyStr = Encoding.UTF8.GetString(HttpContext.Body.Span).Trim();
int.TryParse(bodyStr, out bodyVal);
}
return ValueTask.FromResult((a + b + bodyVal).ToString());
}
}

public class Baseline2GetEndpoint : NoRequestEndpointBase<string>
{
protected override string Method => "GET";
protected override string Route => "/baseline2";
protected override string ContentType => "text/plain";

public override ValueTask<string> HandleAsync(CancellationToken ct)
{
var query = HttpContext?.Query ?? QueryCollection.Empty;
int a = query.GetInt("a");
int b = query.GetInt("b");
return ValueTask.FromResult((a + b).ToString());
}
}
50 changes: 50 additions & 0 deletions frameworks/effinitive/app/Tests/Compression.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Text.Json;
using EffinitiveFramework.Core;

namespace effinitive.Tests;

public class CompressionEndpoint : NoRequestEndpointBase<byte[]>
{
protected override string Method => "GET";
protected override string Route => "/compression";

// Pre-serialize JSON at startup, same as ASP.NET's TypedResults.Bytes() approach.
// The compression middleware handles gzip per-request.
private static readonly byte[] PreSerializedJson = BuildJson();

private static byte[] BuildJson()
{
var path = "/data/dataset-large.json";
if (!File.Exists(path))
{
throw new FileNotFoundException($"Required dataset file not found: {path}", path);
}

var fileBytes = File.ReadAllBytes(path);
var items = JsonSerializer.Deserialize(fileBytes, AppJsonContext.Default.ListDatasetItem);
if (items == null)
{
throw new InvalidOperationException("Failed to deserialize /data/dataset-large.json");
}

var processed = new List<ProcessedItem>(items.Count);
foreach (var d in items)
{
processed.Add(new ProcessedItem
{
Id = d.Id, Name = d.Name, Category = d.Category,
Price = d.Price, Quantity = d.Quantity, Active = d.Active,
Tags = d.Tags, Rating = d.Rating,
Total = Math.Round(d.Price * d.Quantity, 2)
});
}

var dto = new ResponseDto<ProcessedItem>(processed, processed.Count);
return JsonSerializer.SerializeToUtf8Bytes(dto, AppJsonContext.Default.ResponseDtoProcessedItem);
}

public override ValueTask<byte[]> HandleAsync(CancellationToken ct)
{
return ValueTask.FromResult(PreSerializedJson);
}
}
Loading