Skip to content

Commit 7ad4005

Browse files
authored
Merge pull request #610 from Kaliumhexacyanoferrat/service-stack-readd
[C#] ServiceStack: Re-Add framework
2 parents 20c2fa4 + 8b545d3 commit 7ad4005

49 files changed

Lines changed: 438 additions & 157 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frameworks/servicestack/AppHost.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,6 @@ public class AppHost() : AppHostBase("ServiceStack.Benchmark", typeof(AppHost).A
1010
public void Configure(IWebHostBuilder builder) => builder
1111
.ConfigureServices((_, services) =>
1212
{
13-
var dbPool = DbConnectionFactory.Open();
14-
15-
if (dbPool is not null)
16-
{
17-
services.AddSingleton(dbPool);
18-
}
19-
2013
var poolFactory = PgPoolFactory.Open();
2114

2215
if (poolFactory is not null)

frameworks/servicestack/BenchmarkServices.cs

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace ServiceStack.Benchmarks;
22

33
using System.Text.Json;
4-
using Microsoft.Data.Sqlite;
54
using Npgsql;
65
using ServiceStack;
76

@@ -53,60 +52,19 @@ public object Get(JsonGet req)
5352
int m = 1;
5453
if (Request?.QueryString["m"] is string mStr && int.TryParse(mStr, out var pm)) m = pm;
5554

56-
var count = Math.Clamp(req.Count, 0, Items.Count);
55+
var count = req.Count;
56+
57+
if (count > Items.Count) count = Items.Count;
58+
if (count < 0) count = 0;
59+
5760
var processed = new List<ProcessedItem>(count);
58-
for (int i = 0; i < count; i++)
61+
62+
for (var i = 0; i < count; i++)
5963
processed.Add(Items[i].ToProcessed(m));
6064

6165
return new ListWithCount<ProcessedItem>(processed);
6266
}
6367

64-
// ── /db (SQLite) ──────────────────────────────────────────────────────────
65-
public ListWithCount<ProcessedItem> Get(DbGet req)
66-
{
67-
var pool = TryResolve<SqlitePool>();
68-
if (pool == null) return new ListWithCount<ProcessedItem>(new());
69-
70-
var conn = pool.Rent();
71-
try
72-
{
73-
using var cmd = conn.CreateCommand();
74-
cmd.CommandText =
75-
"SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count " +
76-
"FROM items WHERE price BETWEEN @min AND @max LIMIT 50";
77-
cmd.Parameters.AddWithValue("@min", req.Min);
78-
cmd.Parameters.AddWithValue("@max", req.Max);
79-
80-
using var reader = cmd.ExecuteReader();
81-
var items = new List<ProcessedItem>();
82-
83-
while (reader.Read())
84-
{
85-
items.Add(new ProcessedItem
86-
{
87-
Id = reader.GetInt32(0),
88-
Name = reader.GetString(1),
89-
Category = reader.GetString(2),
90-
Price = reader.GetInt32(3),
91-
Quantity = reader.GetInt32(4),
92-
Active = reader.GetInt32(5) == 1,
93-
Tags = JsonSerializer.Deserialize<List<string>>(reader.GetString(6)),
94-
Rating = new RatingInfo
95-
{
96-
Score = reader.GetInt32(7),
97-
Count = reader.GetInt32(8)
98-
}
99-
});
100-
}
101-
102-
return new ListWithCount<ProcessedItem>(items);
103-
}
104-
finally
105-
{
106-
pool.Return(conn);
107-
}
108-
}
109-
11068
// ── /async-db (PostgreSQL) ────────────────────────────────────────────────
11169
public async Task<ListWithCount<object>> Get(AsyncDbGet req)
11270
{

frameworks/servicestack/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
22
WORKDIR /app
3-
COPY . .
3+
COPY frameworks/servicestack/ .
4+
COPY data/static/ wwwroot/static/
45
RUN dotnet publish -c Release -o out
56

67
FROM mcr.microsoft.com/dotnet/aspnet:10.0
Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,5 @@
1-
using System.Collections.Concurrent;
2-
using Microsoft.Data.Sqlite;
3-
41
namespace ServiceStack.Benchmarks;
52

6-
public sealed class SqlitePool
7-
{
8-
private readonly ConcurrentBag<SqliteConnection> _connections = new();
9-
10-
public SqlitePool(string connectionString, int size)
11-
{
12-
for (int i = 0; i < size; i++)
13-
{
14-
var conn = new SqliteConnection(connectionString);
15-
conn.Open();
16-
using var pragma = conn.CreateCommand();
17-
pragma.CommandText = "PRAGMA mmap_size=268435456";
18-
pragma.ExecuteNonQuery();
19-
_connections.Add(conn);
20-
}
21-
}
22-
23-
public SqliteConnection Rent()
24-
{
25-
SqliteConnection? conn;
26-
var spin = new SpinWait();
27-
while (!_connections.TryTake(out conn))
28-
spin.SpinOnce();
29-
return conn;
30-
}
31-
32-
public void Return(SqliteConnection conn) => _connections.Add(conn);
33-
}
34-
35-
public static class DbConnectionFactory
36-
{
37-
public static SqlitePool? Open()
38-
{
39-
const string path = "/data/benchmark.db";
40-
if (!File.Exists(path)) return null;
41-
return new SqlitePool($"Data Source={path};Mode=ReadOnly", Environment.ProcessorCount);
42-
}
43-
}
44-
453
public static class PgPoolFactory
464
{
475
public static Npgsql.NpgsqlDataSource? Open()
@@ -62,12 +20,3 @@ public static class PgPoolFactory
6220
catch { return null; }
6321
}
6422
}
65-
66-
public static class JsonOpts
67-
{
68-
public static readonly System.Text.Json.JsonSerializerOptions Default = new()
69-
{
70-
PropertyNameCaseInsensitive = true,
71-
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase
72-
};
73-
}

frameworks/servicestack/Program.cs

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,38 @@
1+
using System.Security.Cryptography.X509Certificates;
2+
using Microsoft.AspNetCore.Server.Kestrel.Core;
13
using ServiceStack;
24
using ServiceStack.Benchmarks;
35

6+
var certPath = Environment.GetEnvironmentVariable("TLS_CERT") ?? "/certs/server.crt";
7+
var keyPath = Environment.GetEnvironmentVariable("TLS_KEY") ?? "/certs/server.key";
8+
var hasCert = File.Exists(certPath) && File.Exists(keyPath);
9+
410
var builder = WebApplication.CreateBuilder(args);
5-
builder.WebHost.UseUrls("http://*:8080");
611
builder.Services.AddResponseCompression();
712
builder.Logging.ClearProviders();
813

9-
var app = builder.Build();
10-
11-
app.UseResponseCompression();
12-
13-
if (Directory.Exists("/data/static"))
14+
builder.WebHost.ConfigureKestrel(options =>
1415
{
15-
var mimeTypes = new Dictionary<string, string>
16+
options.ListenAnyIP(8080, lo =>
1617
{
17-
[".css"] = "text/css", [".js"] = "application/javascript", [".html"] = "text/html",
18-
[".woff2"] = "font/woff2", [".svg"] = "image/svg+xml", [".webp"] = "image/webp", [".json"] = "application/json",
19-
};
20-
var staticFiles = new Dictionary<string, (byte[] data, byte[]? br, byte[]? gz, string contentType)>();
21-
foreach (var file in Directory.GetFiles("/data/static"))
22-
{
23-
var name = Path.GetFileName(file);
24-
if (name.EndsWith(".br") || name.EndsWith(".gz")) continue;
25-
var ext = Path.GetExtension(name);
26-
var ct = mimeTypes.GetValueOrDefault(ext, "application/octet-stream");
27-
var brPath = file + ".br";
28-
var gzPath = file + ".gz";
29-
staticFiles[name] = (
30-
File.ReadAllBytes(file),
31-
File.Exists(brPath) ? File.ReadAllBytes(brPath) : null,
32-
File.Exists(gzPath) ? File.ReadAllBytes(gzPath) : null,
33-
ct
34-
);
35-
}
36-
app.MapGet("/static/{filename}", (string filename, HttpContext ctx) =>
18+
lo.Protocols = HttpProtocols.Http1;
19+
});
20+
21+
if (hasCert)
3722
{
38-
if (!staticFiles.TryGetValue(filename, out var sf))
39-
return Results.NotFound();
40-
var ae = ctx.Request.Headers.AcceptEncoding.ToString();
41-
if (sf.br != null && ae.Contains("br"))
23+
options.ListenAnyIP(8081, lo =>
4224
{
43-
ctx.Response.Headers.ContentEncoding = "br";
44-
return Results.Bytes(sf.br, sf.contentType);
45-
}
46-
if (sf.gz != null && ae.Contains("gzip"))
47-
{
48-
ctx.Response.Headers.ContentEncoding = "gzip";
49-
return Results.Bytes(sf.gz, sf.contentType);
50-
}
51-
return Results.Bytes(sf.data, sf.contentType);
52-
});
53-
}
25+
lo.Protocols = HttpProtocols.Http1;
26+
lo.UseHttps(X509Certificate2.CreateFromPemFile(certPath, keyPath));
27+
});
28+
}
29+
});
30+
31+
var app = builder.Build();
32+
33+
app.UseResponseCompression();
34+
35+
app.MapStaticAssets();
5436

5537
app.UseServiceStack(new AppHost(), options => {
5638
options.MapEndpoints();

frameworks/servicestack/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ One framework to power them all. Write your HTTP APIs once and take advantage of
1818
| `/baseline2` | GET | Sums query parameter values (HTTP/2 variant) |
1919
| `/json` | GET | Processes 50-item dataset, serializes JSON |
2020
| `/compression` | GET | Gzip-compressed large JSON response |
21-
| `/db` | GET | SQLite range query with JSON response |
2221
| `/upload` | POST | Receives 1 MB body, returns byte count |
2322
| `/static/{filename}` | GET | Serves preloaded static files with MIME types |
2423

frameworks/servicestack/build.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
4+
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
5+
docker build -t httparena-servicestack -f "$SCRIPT_DIR/Dockerfile" "$ROOT_DIR"

frameworks/servicestack/meta.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"display_name": "servicestack",
33
"language": "C#",
4-
"type": "tuned",
4+
"type": "production",
55
"engine": "kestrel",
66
"description": "One framework to power them all. Write your HTTP APIs once and take advantage of end to end typed integrations for all popular Web, Mobile and Desktop platforms.",
77
"repo": "https://github.com/ServiceStack/ServiceStack",
@@ -11,12 +11,15 @@
1111
"pipelined",
1212
"limited-conn",
1313
"json",
14+
"json-tls",
1415
"json-comp",
1516
"upload",
1617
"static",
1718
"async-db",
1819
"api-4",
1920
"api-16"
2021
],
21-
"maintainers": []
22+
"maintainers": [
23+
"Kaliumhexacyanoferrat"
24+
]
2225
}

frameworks/servicestack/servicestack-bench.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
</PropertyGroup>
99
<ItemGroup>
1010
<PackageReference Include="ServiceStack" Version="10.0.6" />
11-
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.5" />
1211
<PackageReference Include="Npgsql" Version="10.0.2" />
1312
</ItemGroup>
1413
</Project>

site/data/api-16-1024.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,32 @@
771771
"tpl_static": 0,
772772
"tpl_async_db": 183072
773773
},
774+
{
775+
"framework": "servicestack",
776+
"language": "C#",
777+
"rps": 23786,
778+
"avg_latency": "42.03ms",
779+
"p99_latency": "105.10ms",
780+
"cpu": "801.4%",
781+
"memory": "316MiB",
782+
"connections": 1024,
783+
"threads": 64,
784+
"duration": "5s",
785+
"pipeline": 1,
786+
"bandwidth": "85.86MB/s",
787+
"input_bw": "1.34MB/s",
788+
"reconnects": 71349,
789+
"status_2xx": 356795,
790+
"status_3xx": 0,
791+
"status_4xx": 0,
792+
"status_5xx": 0,
793+
"tpl_baseline": 133604,
794+
"tpl_json": 133585,
795+
"tpl_db": 0,
796+
"tpl_upload": 0,
797+
"tpl_static": 0,
798+
"tpl_async_db": 89606
799+
},
774800
{
775801
"framework": "sinatra",
776802
"language": "Ruby",

0 commit comments

Comments
 (0)