|
| 1 | +using System.Diagnostics; |
1 | 2 | using System.Net.Http.Json; |
2 | | - |
3 | 3 | using System.Text.Json; |
4 | | -using sisk; |
5 | 4 | using Npgsql; |
| 5 | +using sisk; |
6 | 6 | using Sisk.Cadente.CoreEngine; |
7 | 7 | using Sisk.Core.Http; |
| 8 | +using Sisk.Core.Http.FileSystem; |
8 | 9 | using Sisk.Core.Routing; |
9 | 10 |
|
10 | | -var server = HttpServer.CreateBuilder() |
11 | | - .UseEngine<CadenteHttpServerEngine>() |
12 | | - .UseListeningPort(new ListeningPort(false, "0.0.0.0", 8080)) |
13 | | - .UseMinimalConfiguration(); |
14 | | - |
15 | | -// Pre-load static files with pre-compressed variants |
16 | | -var staticMimeTypes = new Dictionary<string, string> |
17 | | -{ |
18 | | - [".css"] = "text/css", [".js"] = "application/javascript", [".html"] = "text/html", |
19 | | - [".woff2"] = "font/woff2", [".svg"] = "image/svg+xml", [".webp"] = "image/webp", [".json"] = "application/json", |
20 | | -}; |
21 | | -var staticCache = new Dictionary<string, (byte[] data, byte[]? br, byte[]? gz, string contentType)>(); |
22 | | -if (Directory.Exists("/data/static")) |
23 | | -{ |
24 | | - foreach (var file in Directory.GetFiles("/data/static")) |
25 | | - { |
26 | | - var name = Path.GetFileName(file); |
27 | | - if (name.EndsWith(".br") || name.EndsWith(".gz")) continue; |
28 | | - var ext = Path.GetExtension(name); |
29 | | - var ct = staticMimeTypes.GetValueOrDefault(ext, "application/octet-stream"); |
30 | | - var brPath = file + ".br"; |
31 | | - var gzPath = file + ".gz"; |
32 | | - staticCache[name] = ( |
33 | | - File.ReadAllBytes(file), |
34 | | - File.Exists(brPath) ? File.ReadAllBytes(brPath) : null, |
35 | | - File.Exists(gzPath) ? File.ReadAllBytes(gzPath) : null, |
36 | | - ct |
37 | | - ); |
38 | | - } |
39 | | -} |
40 | | - |
41 | | -Router router = new Router(); |
42 | | - |
43 | | -router.MapGet("/static/<filename>", r => |
44 | | -{ |
45 | | - var filename = r.RouteParameters["filename"].ToString(); |
46 | | - if (!staticCache.TryGetValue(filename, out var sf)) |
47 | | - return new HttpResponse(404); |
48 | | - var ae = r.Headers.AcceptEncoding ?? ""; |
49 | | - byte[] body; |
50 | | - string? encoding = null; |
51 | | - if (sf.br != null && ae.Contains("br")) |
52 | | - { |
53 | | - body = sf.br; |
54 | | - encoding = "br"; |
55 | | - } |
56 | | - else if (sf.gz != null && ae.Contains("gzip")) |
57 | | - { |
58 | | - body = sf.gz; |
59 | | - encoding = "gzip"; |
60 | | - } |
61 | | - else |
62 | | - { |
63 | | - body = sf.data; |
64 | | - } |
65 | | - var resp = new HttpResponse(200); |
66 | | - resp.Content = new ByteArrayContent(body); |
67 | | - resp.Headers.Add("Content-Type", sf.contentType); |
68 | | - if (encoding != null) resp.Headers.Add("Content-Encoding", encoding); |
69 | | - return resp; |
70 | | -}); |
71 | | - |
72 | | -router.MapGet("/baseline11", r => new HttpResponse(Sum(r))); |
73 | | -router.MapPost("/baseline11", r => new HttpResponse(Sum(r))); |
| 11 | +var server = HttpServer.CreateBuilder () |
| 12 | + .UseEngine<CadenteHttpServerEngine> () |
| 13 | + .UseListeningPort ( new ListeningPort ( false, "0.0.0.0", 8080 ) ) |
| 14 | + .UseMinimalConfiguration () |
| 15 | + .UseConfiguration ( config => { |
| 16 | + config.EnableAutomaticResponseCompression = true; |
| 17 | + } ); |
74 | 18 |
|
75 | | -router.MapGet("/baseline2", r => new HttpResponse(Sum(r))); |
| 19 | +Router router = new Router (); |
76 | 20 |
|
77 | | -router.MapGet("/pipeline", r => new HttpResponse("ok")); |
| 21 | +var staticRoute = HttpFileServer.CreateServingRoute ( "/static", new HttpFileServerHandler () { |
| 22 | + RootDirectoryPath = "/data/static", |
| 23 | + AllowDirectoryListing = false |
| 24 | +} ); |
78 | 25 |
|
79 | | -router.MapPost("/upload", r => |
80 | | -{ |
81 | | - var buffer = new byte[8192]; |
| 26 | +router.SetRoute ( staticRoute ); |
82 | 27 |
|
83 | | - var body = r.GetRequestStream(); |
| 28 | +router.MapGet ( "/baseline11", r => new HttpResponse ( Sum ( r ) ) ); |
| 29 | +router.MapPost ( "/baseline11", r => new HttpResponse ( Sum ( r ) ) ); |
84 | 30 |
|
85 | | - var read = 0; |
| 31 | +router.MapGet ( "/baseline2", r => new HttpResponse ( Sum ( r ) ) ); |
86 | 32 |
|
87 | | - long total = 0; |
| 33 | +router.MapGet ( "/pipeline", r => new HttpResponse ( "ok" ) ); |
88 | 34 |
|
89 | | - while ((read = body.Read(buffer, 0, buffer.Length)) > 0) |
90 | | - { |
91 | | - total += read; |
92 | | - } |
| 35 | +router.MapPost ( "/upload", r => { |
| 36 | + var body = r.GetBodyContents (); |
| 37 | + return new HttpResponse ( body.Length.ToString () ); |
| 38 | +} ); |
93 | 39 |
|
94 | | - return new HttpResponse(total.ToString()); |
95 | | -}); |
| 40 | +var datasetItems = LoadItems (); |
96 | 41 |
|
97 | | -var datasetItems = LoadItems(); |
98 | | - |
99 | | -router.MapGet("/json/<count>", r => |
100 | | -{ |
101 | | - int count = Math.Clamp(int.Parse(r.RouteParameters["count"].ToString()), 0, datasetItems!.Count); |
| 42 | +router.MapGet ( "/json/<count>", r => { |
| 43 | + int count = Math.Clamp ( int.Parse ( r.RouteParameters [ "count" ].GetString () ), 0, datasetItems!.Count ); |
102 | 44 | int m = 1; |
103 | | - if (r.Query.TryGetValue("m", out var mStr)) { int.TryParse(mStr, out m); if (m == 0) m = 1; } |
104 | | - var processed = new ProcessedItem[count]; |
105 | | - |
106 | | - for (int i = 0; i < count; i++) |
107 | | - { |
108 | | - var d = datasetItems[i]; |
109 | | - processed[i] = new ProcessedItem |
110 | | - { |
| 45 | + if (r.Query.TryGetValue ( "m", out var mStr )) { int.TryParse ( mStr, out m ); if (m == 0) m = 1; } |
| 46 | + var processed = new ProcessedItem [ count ]; |
| 47 | + |
| 48 | + for (int i = 0; i < count; i++) { |
| 49 | + var d = datasetItems [ i ]; |
| 50 | + processed [ i ] = new ProcessedItem { |
111 | 51 | Id = d.Id, |
112 | 52 | Name = d.Name, |
113 | 53 | Category = d.Category, |
|
120 | 60 | }; |
121 | 61 | } |
122 | 62 |
|
123 | | - return new HttpResponse |
124 | | - { |
125 | | - Content = JsonContent.Create(new ListWithCount<ProcessedItem>(processed.ToList())) |
| 63 | + return new HttpResponse { |
| 64 | + Content = JsonContent.Create ( new ListWithCount<ProcessedItem> ( processed.ToList () ) ) |
126 | 65 | }; |
127 | | -}); |
128 | | - |
129 | | -var pgDataSource = OpenPgPool(); |
130 | | - |
131 | | -router.MapGet("/async-db", async (HttpRequest request) => |
132 | | -{ |
133 | | - var min = request.Query.TryGetValue("min", out var vmin) ? vmin.GetInteger() : 10; |
134 | | - var max = request.Query.TryGetValue("max", out var vmax) ? vmax.GetInteger() : 50; |
135 | | - var limit = request.Query.TryGetValue("limit", out var vlim) ? Math.Clamp(vlim.GetInteger(), 1, 50) : 50; |
136 | | - |
137 | | - await using var cmd = pgDataSource.CreateCommand( |
138 | | - "SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3"); |
139 | | - cmd.Parameters.AddWithValue(min); |
140 | | - cmd.Parameters.AddWithValue(max); |
141 | | - cmd.Parameters.AddWithValue(limit); |
142 | | - await using var reader = await cmd.ExecuteReaderAsync(); |
143 | | - |
144 | | - var items = new List<object>(); |
145 | | - |
146 | | - while (await reader.ReadAsync()) |
147 | | - { |
148 | | - items.Add(new |
149 | | - { |
150 | | - id = reader.GetInt32(0), |
151 | | - name = reader.GetString(1), |
152 | | - category = reader.GetString(2), |
153 | | - price = reader.GetInt32(3), |
154 | | - quantity = reader.GetInt32(4), |
155 | | - active = reader.GetBoolean(5), |
156 | | - tags = JsonSerializer.Deserialize<List<string>>(reader.GetString(6)), |
157 | | - rating = new { score = reader.GetInt32(7), count = reader.GetInt32(8) }, |
158 | | - }); |
| 66 | +} ); |
| 67 | + |
| 68 | +var pgDataSource = OpenPgPool (); |
| 69 | + |
| 70 | +router.MapGet ( "/async-db", async ( HttpRequest request ) => { |
| 71 | + var min = request.Query.TryGetValue ( "min", out var vmin ) ? vmin.GetInteger () : 10; |
| 72 | + var max = request.Query.TryGetValue ( "max", out var vmax ) ? vmax.GetInteger () : 50; |
| 73 | + var limit = request.Query.TryGetValue ( "limit", out var vlim ) ? Math.Clamp ( vlim.GetInteger (), 1, 50 ) : 50; |
| 74 | + |
| 75 | + Debug.Assert ( pgDataSource != null, "PostgreSQL data source is not available. Please set the DATABASE_URL environment variable." ); |
| 76 | + |
| 77 | + await using var cmd = pgDataSource.CreateCommand ( |
| 78 | + "SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3" ); |
| 79 | + |
| 80 | + cmd.Parameters.AddWithValue ( min ); |
| 81 | + cmd.Parameters.AddWithValue ( max ); |
| 82 | + cmd.Parameters.AddWithValue ( limit ); |
| 83 | + await using var reader = await cmd.ExecuteReaderAsync (); |
| 84 | + |
| 85 | + var items = new List<object> (); |
| 86 | + |
| 87 | + while (await reader.ReadAsync ()) { |
| 88 | + items.Add ( new { |
| 89 | + id = reader.GetInt32 ( 0 ), |
| 90 | + name = reader.GetString ( 1 ), |
| 91 | + category = reader.GetString ( 2 ), |
| 92 | + price = reader.GetInt32 ( 3 ), |
| 93 | + quantity = reader.GetInt32 ( 4 ), |
| 94 | + active = reader.GetBoolean ( 5 ), |
| 95 | + tags = JsonSerializer.Deserialize<List<string>> ( reader.GetString ( 6 ) ), |
| 96 | + rating = new { score = reader.GetInt32 ( 7 ), count = reader.GetInt32 ( 8 ) }, |
| 97 | + } ); |
159 | 98 | } |
160 | 99 |
|
161 | | - return new HttpResponse |
162 | | - { |
163 | | - Content = JsonContent.Create(new ListWithCount<object>(items)) |
| 100 | + return new HttpResponse { |
| 101 | + Content = JsonContent.Create ( new ListWithCount<object> ( items ) ) |
164 | 102 | }; |
165 | | -}); |
| 103 | +} ); |
166 | 104 |
|
167 | | -await server.UseRouter(router).Build().StartAsync(); |
| 105 | +await server.UseRouter ( router ).Build ().StartAsync (); |
168 | 106 |
|
169 | 107 | return; |
170 | 108 |
|
171 | | -static string Sum(HttpRequest request) |
172 | | -{ |
173 | | - var a = request.Query["a"].MaybeNullOrEmpty()?.GetInteger() ?? 0; |
174 | | - var b = request.Query["b"].MaybeNullOrEmpty()?.GetInteger() ?? 0; |
| 109 | +static string Sum ( HttpRequest request ) { |
| 110 | + var a = request.Query [ "a" ].MaybeNullOrEmpty ()?.GetInteger () ?? 0; |
| 111 | + var b = request.Query [ "b" ].MaybeNullOrEmpty ()?.GetInteger () ?? 0; |
175 | 112 |
|
176 | 113 | var c = 0; |
177 | 114 |
|
178 | | - if (request.Method == HttpMethod.Post) |
179 | | - { |
180 | | - c = int.Parse(request.Body); |
| 115 | + if (request.Method == HttpMethod.Post) { |
| 116 | + c = int.Parse ( request.Body ); |
181 | 117 | } |
182 | 118 |
|
183 | | - return (a + b + c).ToString(); |
| 119 | + return (a + b + c).ToString (); |
184 | 120 | } |
185 | 121 |
|
186 | | -static List<DatasetItem>? LoadItems() |
187 | | -{ |
188 | | - var jsonOptions = new JsonSerializerOptions |
189 | | - { |
| 122 | +static List<DatasetItem>? LoadItems () { |
| 123 | + var jsonOptions = new JsonSerializerOptions { |
190 | 124 | PropertyNameCaseInsensitive = true, |
191 | 125 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase |
192 | 126 | }; |
193 | 127 |
|
194 | | - var datasetPath = Environment.GetEnvironmentVariable("DATASET_PATH") ?? "/data/dataset.json"; |
| 128 | + var datasetPath = Environment.GetEnvironmentVariable ( "DATASET_PATH" ) ?? "/data/dataset.json"; |
195 | 129 |
|
196 | | - if (File.Exists(datasetPath)) |
197 | | - { |
198 | | - return JsonSerializer.Deserialize<List<DatasetItem>>(File.ReadAllText(datasetPath), jsonOptions); |
| 130 | + if (File.Exists ( datasetPath )) { |
| 131 | + return JsonSerializer.Deserialize<List<DatasetItem>> ( File.ReadAllText ( datasetPath ), jsonOptions ); |
199 | 132 | } |
200 | 133 |
|
201 | 134 | return null; |
202 | 135 | } |
203 | 136 |
|
204 | | -static NpgsqlDataSource? OpenPgPool() |
205 | | -{ |
206 | | - var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL"); |
207 | | - if (string.IsNullOrEmpty(dbUrl)) return null; |
208 | | - try |
209 | | - { |
210 | | - var uri = new Uri(dbUrl); |
211 | | - var userInfo = uri.UserInfo.Split(':'); |
212 | | - 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"; |
213 | | - var builder = new NpgsqlDataSourceBuilder(connStr); |
214 | | - return builder.Build(); |
| 137 | +static NpgsqlDataSource? OpenPgPool () { |
| 138 | + var dbUrl = Environment.GetEnvironmentVariable ( "DATABASE_URL" ); |
| 139 | + if (string.IsNullOrEmpty ( dbUrl )) |
| 140 | + return null; |
| 141 | + try { |
| 142 | + var uri = new Uri ( dbUrl ); |
| 143 | + var userInfo = uri.UserInfo.Split ( ':' ); |
| 144 | + 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"; |
| 145 | + var builder = new NpgsqlDataSourceBuilder ( connStr ); |
| 146 | + return builder.Build (); |
| 147 | + } |
| 148 | + catch { |
| 149 | + return null; |
215 | 150 | } |
216 | | - catch { return null; } |
217 | 151 | } |
218 | 152 |
|
0 commit comments