Skip to content

Commit 66181ae

Browse files
authored
Merge pull request #663 from Kaliumhexacyanoferrat/genhttp-kestrel-10.5.1
[C#] GenHTTP (Kestrel): Implement CRUD and improve static files implementation
2 parents 9bb0b17 + b66f477 commit 66181ae

36 files changed

Lines changed: 612 additions & 353 deletions
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Npgsql;
2+
3+
namespace genhttp.Infrastructure;
4+
5+
public static class Postgres
6+
{
7+
private static readonly NpgsqlDataSource? _pool = OpenPool();
8+
9+
public static NpgsqlDataSource? Pool { get => _pool; }
10+
11+
private static NpgsqlDataSource? OpenPool()
12+
{
13+
var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
14+
15+
if (string.IsNullOrEmpty(dbUrl)) return null;
16+
17+
try
18+
{
19+
var uri = new Uri(dbUrl);
20+
var userInfo = uri.UserInfo.Split(':');
21+
var maxConn = int.TryParse(Environment.GetEnvironmentVariable("DATABASE_MAX_CONN"), out var p) && p > 0 ? p : 256;
22+
var minConn = Math.Min(64, maxConn);
23+
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";
24+
var builder = new NpgsqlDataSourceBuilder(connStr);
25+
26+
return builder.Build();
27+
}
28+
catch
29+
{
30+
return null;
31+
}
32+
}
33+
34+
}

frameworks/genhttp-kestrel/Model.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
namespace genhttp;
22

3-
public class DatasetItem
3+
public sealed class DatasetItem
44
{
55
public int Id { get; set; }
66
public string Name { get; set; } = "";
@@ -12,7 +12,7 @@ public class DatasetItem
1212
public RatingInfo? Rating { get; set; }
1313
}
1414

15-
public class ProcessedItem
15+
public sealed class ProcessedItem
1616
{
1717
public int Id { get; set; }
1818
public string Name { get; set; } = "";
@@ -25,17 +25,35 @@ public class ProcessedItem
2525
public long Total { get; set; }
2626
}
2727

28-
public class RatingInfo
28+
public sealed class RatingInfo
2929
{
3030
public int Score { get; set; }
3131
public int Count { get; set; }
3232
}
3333

34-
public class ListWithCount<T>(List<T> items)
34+
public sealed class ListWithCount<T>(List<T> items)
3535
{
3636

3737
public List<T> Items => items;
38-
38+
3939
public int Count => items.Count;
4040

41-
}
41+
}
42+
43+
44+
public sealed class CrudListResponse
45+
{
46+
public List<ProcessedItem> Items { get; set; } = [];
47+
public long Total { get; set; }
48+
public int Page { get; set; }
49+
public int Limit { get; set; }
50+
}
51+
52+
public sealed class CrudItem
53+
{
54+
public int? Id { get; set; }
55+
public string? Name { get; set; }
56+
public string? Category { get; set; }
57+
public int Price { get; set; }
58+
public int Quantity { get; set; }
59+
}
Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,67 @@
1-
using GenHTTP.Api.Content;
2-
using GenHTTP.Modules.IO;
3-
using GenHTTP.Modules.Layouting;
4-
using GenHTTP.Modules.Layouting.Provider;
5-
using GenHTTP.Modules.Webservices;
6-
using GenHTTP.Modules.Websockets;
7-
8-
using genhttp.Tests;
9-
10-
namespace genhttp;
11-
12-
public static class Project
13-
{
14-
public static IHandlerBuilder Create()
15-
{
16-
var app = Layout.Create()
17-
.Add("pipeline", Content.From(Resource.FromString("ok")))
18-
.AddService<Baseline>("baseline11")
19-
.AddService<Baseline>("baseline2")
20-
.AddService<Upload>("upload")
21-
.AddService<Json>("json")
22-
.AddService<AsyncDatabase>("async-db")
23-
.AddStaticFiles()
24-
.AddWebsocket();
25-
26-
return app;
27-
}
28-
29-
private static LayoutBuilder AddStaticFiles(this LayoutBuilder app)
30-
{
31-
if (Directory.Exists("/data/static"))
32-
{
33-
app.Add("static", Resources.From(ResourceTree.FromDirectory("/data/static")));
34-
}
35-
36-
return app;
37-
}
38-
39-
private static LayoutBuilder AddWebsocket(this LayoutBuilder app)
40-
{
41-
var websocket = Websocket.Imperative()
42-
.DoNotAllocateFrameData()
43-
.Handler(new EchoHandler());
44-
45-
return app.Add("ws", websocket);
46-
}
47-
48-
}
1+
using System.IO.Compression;
2+
using GenHTTP.Api.Content;
3+
using GenHTTP.Modules.Compression;
4+
using GenHTTP.Modules.IO;
5+
using GenHTTP.Modules.Layouting;
6+
using GenHTTP.Modules.Layouting.Provider;
7+
using GenHTTP.Modules.ServerCaching;
8+
using GenHTTP.Modules.Webservices;
9+
using GenHTTP.Modules.Websockets;
10+
11+
using genhttp.Tests;
12+
13+
namespace genhttp;
14+
15+
public static class Project
16+
{
17+
public static IHandlerBuilder Create()
18+
{
19+
var crud = Layout.Create()
20+
.AddService<Crud>("items");
21+
22+
var app = Layout.Create()
23+
.Add("pipeline", Content.From(Resource.FromString("ok")))
24+
.AddService<Baseline>("baseline11")
25+
.AddService<Baseline>("baseline2")
26+
.AddService<Upload>("upload")
27+
.AddService<Json>("json")
28+
.AddService<AsyncDatabase>("async-db")
29+
.Add("crud", crud)
30+
.AddStaticFiles()
31+
.AddWebsocket();
32+
33+
return app;
34+
}
35+
36+
private static LayoutBuilder AddStaticFiles(this LayoutBuilder app)
37+
{
38+
if (Directory.Exists("/data/static"))
39+
{
40+
var tree = ResourceTree.FromDirectory("/data/static");
41+
42+
var compression = CompressedContent.Default()
43+
.Level(CompressionLevel.Optimal);
44+
45+
var cache = ServerCache.TemporaryFiles()
46+
.Invalidate(false);
47+
48+
var handler = Resources.From(tree) // serve static resources
49+
.Add(compression) // compress them on-the-fly
50+
.Add(cache); // cache the compressed results
51+
52+
app.Add("static", handler);
53+
}
54+
55+
return app;
56+
}
57+
58+
private static LayoutBuilder AddWebsocket(this LayoutBuilder app)
59+
{
60+
var websocket = Websocket.Imperative()
61+
.DoNotAllocateFrameData()
62+
.Handler(new EchoHandler());
63+
64+
return app.Add("ws", websocket);
65+
}
66+
67+
}
Lines changed: 49 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,49 @@
1-
using System.Text.Json;
2-
3-
using GenHTTP.Modules.Webservices;
4-
5-
using Npgsql;
6-
7-
namespace genhttp.Tests;
8-
9-
public class AsyncDatabase
10-
{
11-
private static readonly NpgsqlDataSource? PgDataSource = OpenPgPool();
12-
13-
private static NpgsqlDataSource? OpenPgPool()
14-
{
15-
var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
16-
if (string.IsNullOrEmpty(dbUrl)) return null;
17-
try
18-
{
19-
var uri = new Uri(dbUrl);
20-
var userInfo = uri.UserInfo.Split(':');
21-
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";
22-
var builder = new NpgsqlDataSourceBuilder(connStr);
23-
return builder.Build();
24-
}
25-
catch { return null; }
26-
}
27-
28-
[ResourceMethod]
29-
public async Task<ListWithCount<object>> Compute(int min = 10, int max = 50, int limit = 50)
30-
{
31-
if (PgDataSource == null)
32-
{
33-
return new ListWithCount<object>(new List<object>());
34-
}
35-
36-
await using var cmd = PgDataSource.CreateCommand(
37-
"SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3");
38-
39-
cmd.Parameters.AddWithValue(min);
40-
cmd.Parameters.AddWithValue(max);
41-
cmd.Parameters.AddWithValue(limit);
42-
43-
await using var reader = await cmd.ExecuteReaderAsync();
44-
45-
var items = new List<object>(limit);
46-
47-
while (await reader.ReadAsync())
48-
{
49-
items.Add(new
50-
{
51-
id = reader.GetInt32(0),
52-
name = reader.GetString(1),
53-
category = reader.GetString(2),
54-
price = reader.GetInt32(3),
55-
quantity = reader.GetInt32(4),
56-
active = reader.GetBoolean(5),
57-
tags = JsonSerializer.Deserialize<List<string>>(reader.GetString(6)),
58-
rating = new { score = reader.GetInt32(7), count = reader.GetInt32(8) },
59-
});
60-
}
61-
62-
return new ListWithCount<object>(items);
63-
}
64-
65-
}
1+
using System.Text.Json;
2+
using genhttp.Infrastructure;
3+
using GenHTTP.Modules.Webservices;
4+
5+
namespace genhttp.Tests;
6+
7+
public class AsyncDatabase
8+
{
9+
10+
[ResourceMethod]
11+
public async Task<ListWithCount<object>> Compute(int min = 10, int max = 50, int limit = 50)
12+
{
13+
var pool = Postgres.Pool;
14+
15+
if (pool == null)
16+
{
17+
return new ListWithCount<object>(new List<object>());
18+
}
19+
20+
await using var cmd = pool.CreateCommand(
21+
"SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3");
22+
23+
cmd.Parameters.AddWithValue(min);
24+
cmd.Parameters.AddWithValue(max);
25+
cmd.Parameters.AddWithValue(limit);
26+
27+
await using var reader = await cmd.ExecuteReaderAsync();
28+
29+
var items = new List<object>(limit);
30+
31+
while (await reader.ReadAsync())
32+
{
33+
items.Add(new
34+
{
35+
id = reader.GetInt32(0),
36+
name = reader.GetString(1),
37+
category = reader.GetString(2),
38+
price = reader.GetInt32(3),
39+
quantity = reader.GetInt32(4),
40+
active = reader.GetBoolean(5),
41+
tags = JsonSerializer.Deserialize<List<string>>(reader.GetString(6)),
42+
rating = new { score = reader.GetInt32(7), count = reader.GetInt32(8) },
43+
});
44+
}
45+
46+
return new ListWithCount<object>(items);
47+
}
48+
49+
}

0 commit comments

Comments
 (0)