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
15 changes: 15 additions & 0 deletions frameworks/genhttp-11/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /source

COPY genhttp.csproj ./
RUN dotnet restore

COPY . .
RUN dotnet publish -c Release --no-self-contained -o /app

FROM mcr.microsoft.com/dotnet/runtime:10.0
WORKDIR /app
COPY --from=build /app .

EXPOSE 8080 8081 8443
ENTRYPOINT ["dotnet", "genhttp.dll"]
34 changes: 34 additions & 0 deletions frameworks/genhttp-11/Infrastructure/Postgres.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Npgsql;

namespace genhttp.Infrastructure;

public static class Postgres
{
private static readonly NpgsqlDataSource? _pool = OpenPool();

public static NpgsqlDataSource? Pool { get => _pool; }

private static NpgsqlDataSource? OpenPool()
{
var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");

if (string.IsNullOrEmpty(dbUrl)) return null;

try
{
var uri = new Uri(dbUrl);
var userInfo = uri.UserInfo.Split(':');
var maxConn = int.TryParse(Environment.GetEnvironmentVariable("DATABASE_MAX_CONN"), out var p) && p > 0 ? p : 256;
var minConn = Math.Min(64, maxConn);
var connStr = $"Host={uri.Host};Port={uri.Port};Username={userInfo[0]};Password={userInfo[1]};Database={uri.AbsolutePath.TrimStart('/')};Maximum Pool Size={maxConn};Minimum Pool Size={minConn};Multiplexing=true;No Reset On Close=true;Max Auto Prepare=20;Auto Prepare Min Usages=1";
var builder = new NpgsqlDataSourceBuilder(connStr);

return builder.Build();
}
catch
{
return null;
}
}

}
59 changes: 59 additions & 0 deletions frameworks/genhttp-11/Model.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
namespace genhttp;

public sealed class DatasetItem
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Category { get; set; } = "";
public int Price { get; set; }
public int Quantity { get; set; }
public bool Active { get; set; }
public List<string>? Tags { get; set; }
public RatingInfo? Rating { get; set; }
}

public sealed class ProcessedItem
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Category { get; set; } = "";
public int Price { get; set; }
public int Quantity { get; set; }
public bool Active { get; set; }
public List<string>? Tags { get; set; }
public RatingInfo? Rating { get; set; }
public long Total { get; set; }
}

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

public sealed class ListWithCount<T>(List<T> items)
{

public List<T> Items => items;

public int Count => items.Count;

}


public sealed class CrudListResponse
{
public List<ProcessedItem> Items { get; set; } = [];
public long Total { get; set; }
public int Page { get; set; }
public int Limit { get; set; }
}

public sealed class CrudItem
{
public int? Id { get; set; }
public string? Name { get; set; }
public string? Category { get; set; }
public int Price { get; set; }
public int Quantity { get; set; }
}
27 changes: 27 additions & 0 deletions frameworks/genhttp-11/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Net;
using System.Security.Cryptography.X509Certificates;

using genhttp;

using GenHTTP.Engine.Internal;
using GenHTTP.Modules.Compression;

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

var app = Project.Create();

var host = Host.Create()
.Handler(app)
.Compression();

host.Bind(IPAddress.Any, 8080);

if (hasCert)
{
host.Bind(IPAddress.Any, 8081, X509Certificate2.CreateFromPemFile(certPath, keyPath));
host.Bind(IPAddress.Any, 8443, X509Certificate2.CreateFromPemFile(certPath, keyPath));
}

await host.RunAsync();
68 changes: 68 additions & 0 deletions frameworks/genhttp-11/Project.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.IO.Compression;

using GenHTTP.Api.Content;
using GenHTTP.Modules.Compression;
using GenHTTP.Modules.IO;
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.Layouting.Provider;
using GenHTTP.Modules.ServerCaching;
using GenHTTP.Modules.Webservices;
using GenHTTP.Modules.Websockets;

using genhttp.Tests;

namespace genhttp;

public static class Project
{
public static IHandlerBuilder Create()
{
var crud = Layout.Create()
.AddService<Crud>("items");

var app = Layout.Create()
.Add("pipeline", Content.From(Resource.FromString("ok")))
.AddService<Baseline>("baseline11")
.AddService<Baseline>("baseline2")
.AddService<Upload>("upload")
.AddService<Json>("json")
.AddService<AsyncDatabase>("async-db")
.Add("crud", crud)
.AddStaticFiles()
.AddWebsocket();

return app;
}

private static LayoutBuilder AddStaticFiles(this LayoutBuilder app)
{
if (Directory.Exists("/data/static"))
{
var tree = ResourceTree.FromDirectory("/data/static");

var compression = CompressedContent.Default()
.Level(CompressionLevel.Optimal);

var cache = ServerCache.TemporaryFiles()
.Invalidate(true);

var handler = Resources.From(tree) // serve static resources
.Add(compression) // compress them on-the-fly
.Add(cache); // cache the compressed results

app.Add("static", handler);
}

return app;
}

private static LayoutBuilder AddWebsocket(this LayoutBuilder app)
{
var websocket = Websocket.Imperative()
.DoNotAllocateFrameData()
.Handler(new EchoHandler());

return app.Add("ws", websocket);
}

}
30 changes: 30 additions & 0 deletions frameworks/genhttp-11/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# GenHTTP

Lightweight embeddable C# web server using the GenHTTP library on the internal engine.

## Stack

- **Language:** C# / .NET 10 (Alpine)
- **Framework:** GenHTTP
- **Engine:** GenHTTP
- **Build:** Self-contained musl publish, `runtime-deps:10.0-alpine`

## 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` | GET | Processes 50-item dataset, serializes JSON |
| `/compression` | GET | Gzip-compressed large JSON response |
| `/db` | GET | SQLite range query with JSON response |
| `/upload` | POST | Receives 1 MB body, returns byte count |
| `/static/{filename}` | GET | Serves preloaded static files with MIME types |

## Notes

- Implemented via web services and a layout router
- Compression and routing modules
- Self-contained single-file deployment
49 changes: 49 additions & 0 deletions frameworks/genhttp-11/Tests/AsyncDatabase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Text.Json;
using genhttp.Infrastructure;
using GenHTTP.Modules.Webservices;

namespace genhttp.Tests;

public class AsyncDatabase
{

[ResourceMethod]
public async Task<ListWithCount<object>> Compute(int min = 10, int max = 50, int limit = 50)
{
var pool = Postgres.Pool;

if (pool == null)
{
return new ListWithCount<object>(new List<object>());
}

await using var cmd = pool.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();

var items = new List<object>(limit);

while (await reader.ReadAsync())
{
items.Add(new
{
id = reader.GetInt32(0),
name = reader.GetString(1),
category = reader.GetString(2),
price = reader.GetInt32(3),
quantity = reader.GetInt32(4),
active = reader.GetBoolean(5),
tags = JsonSerializer.Deserialize<List<string>>(reader.GetString(6)),
rating = new { score = reader.GetInt32(7), count = reader.GetInt32(8) },
});
}

return new ListWithCount<object>(items);
}

}
15 changes: 15 additions & 0 deletions frameworks/genhttp-11/Tests/Baseline.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using GenHTTP.Modules.Reflection;
using GenHTTP.Modules.Webservices;

namespace genhttp.Tests;

public class Baseline
{

[ResourceMethod]
public int Sum(int a, int b) => a + b;

[ResourceMethod(Method.Post)]
public int Sum(int a, int b, [FromBody] int c) => a + b + c;

}
Loading