From bd2470f8b5d0e30b1f849ae0e29fee892ef3d2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Tue, 19 May 2026 17:46:35 +0200 Subject: [PATCH 1/3] Add experimental entry for GenHTTP 11 --- frameworks/genhttp-11/Dockerfile | 15 ++ .../genhttp-11/Infrastructure/Postgres.cs | 34 ++++ frameworks/genhttp-11/Model.cs | 59 ++++++ frameworks/genhttp-11/Program.cs | 27 +++ frameworks/genhttp-11/Project.cs | 68 +++++++ frameworks/genhttp-11/README.md | 30 +++ frameworks/genhttp-11/Tests/AsyncDatabase.cs | 49 +++++ frameworks/genhttp-11/Tests/Baseline.cs | 15 ++ frameworks/genhttp-11/Tests/Crud.cs | 175 ++++++++++++++++++ frameworks/genhttp-11/Tests/EchoHandler.cs | 23 +++ frameworks/genhttp-11/Tests/Json.cs | 60 ++++++ frameworks/genhttp-11/Tests/Upload.cs | 38 ++++ frameworks/genhttp-11/genhttp.csproj | 25 +++ frameworks/genhttp-11/meta.json | 27 +++ 14 files changed, 645 insertions(+) create mode 100644 frameworks/genhttp-11/Dockerfile create mode 100644 frameworks/genhttp-11/Infrastructure/Postgres.cs create mode 100644 frameworks/genhttp-11/Model.cs create mode 100644 frameworks/genhttp-11/Program.cs create mode 100644 frameworks/genhttp-11/Project.cs create mode 100644 frameworks/genhttp-11/README.md create mode 100644 frameworks/genhttp-11/Tests/AsyncDatabase.cs create mode 100644 frameworks/genhttp-11/Tests/Baseline.cs create mode 100644 frameworks/genhttp-11/Tests/Crud.cs create mode 100644 frameworks/genhttp-11/Tests/EchoHandler.cs create mode 100644 frameworks/genhttp-11/Tests/Json.cs create mode 100644 frameworks/genhttp-11/Tests/Upload.cs create mode 100644 frameworks/genhttp-11/genhttp.csproj create mode 100644 frameworks/genhttp-11/meta.json diff --git a/frameworks/genhttp-11/Dockerfile b/frameworks/genhttp-11/Dockerfile new file mode 100644 index 000000000..c86794a18 --- /dev/null +++ b/frameworks/genhttp-11/Dockerfile @@ -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"] diff --git a/frameworks/genhttp-11/Infrastructure/Postgres.cs b/frameworks/genhttp-11/Infrastructure/Postgres.cs new file mode 100644 index 000000000..8a38fc971 --- /dev/null +++ b/frameworks/genhttp-11/Infrastructure/Postgres.cs @@ -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; + } + } + +} diff --git a/frameworks/genhttp-11/Model.cs b/frameworks/genhttp-11/Model.cs new file mode 100644 index 000000000..264cf3eb5 --- /dev/null +++ b/frameworks/genhttp-11/Model.cs @@ -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? 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? 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(List items) +{ + + public List Items => items; + + public int Count => items.Count; + +} + + +public sealed class CrudListResponse +{ + public List 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; } +} diff --git a/frameworks/genhttp-11/Program.cs b/frameworks/genhttp-11/Program.cs new file mode 100644 index 000000000..ae6b9ea56 --- /dev/null +++ b/frameworks/genhttp-11/Program.cs @@ -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(); diff --git a/frameworks/genhttp-11/Project.cs b/frameworks/genhttp-11/Project.cs new file mode 100644 index 000000000..f5777d7cf --- /dev/null +++ b/frameworks/genhttp-11/Project.cs @@ -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("items"); + + var app = Layout.Create() + .Add("pipeline", Content.From(Resource.FromString("ok"))) + .AddService("baseline11") + .AddService("baseline2") + .AddService("upload") + .AddService("json") + .AddService("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); + } + +} diff --git a/frameworks/genhttp-11/README.md b/frameworks/genhttp-11/README.md new file mode 100644 index 000000000..c26cb329d --- /dev/null +++ b/frameworks/genhttp-11/README.md @@ -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 diff --git a/frameworks/genhttp-11/Tests/AsyncDatabase.cs b/frameworks/genhttp-11/Tests/AsyncDatabase.cs new file mode 100644 index 000000000..d92158905 --- /dev/null +++ b/frameworks/genhttp-11/Tests/AsyncDatabase.cs @@ -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> Compute(int min = 10, int max = 50, int limit = 50) + { + var pool = Postgres.Pool; + + if (pool == null) + { + return new ListWithCount(new List()); + } + + 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(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>(reader.GetString(6)), + rating = new { score = reader.GetInt32(7), count = reader.GetInt32(8) }, + }); + } + + return new ListWithCount(items); + } + +} diff --git a/frameworks/genhttp-11/Tests/Baseline.cs b/frameworks/genhttp-11/Tests/Baseline.cs new file mode 100644 index 000000000..1e31cbf42 --- /dev/null +++ b/frameworks/genhttp-11/Tests/Baseline.cs @@ -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; + +} diff --git a/frameworks/genhttp-11/Tests/Crud.cs b/frameworks/genhttp-11/Tests/Crud.cs new file mode 100644 index 000000000..1523ae925 --- /dev/null +++ b/frameworks/genhttp-11/Tests/Crud.cs @@ -0,0 +1,175 @@ +using System.Text.Json; + +using GenHTTP.Api.Content; +using GenHTTP.Api.Protocol; + +using genhttp.Infrastructure; + +using GenHTTP.Modules.Reflection; +using GenHTTP.Modules.Webservices; + +using Microsoft.Extensions.Caching.Memory; +using StringContent = GenHTTP.Modules.IO.Strings.StringContent; + +namespace genhttp.Tests; + +public class Crud +{ + private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web); + + private static readonly IMemoryCache ItemCache = new MemoryCache(new MemoryCacheOptions()); + + private static readonly MemoryCacheEntryOptions ItemCacheOptions = new() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMilliseconds(200) }; + + [ResourceMethod] + public async Task List(string category = "electronics", int page = 1, int limit = 10) + { + if (page < 1) page = 1; + if (limit is < 1 or > 50) limit = 10; + + var offset = (page - 1) * limit; + + await using var cmd = Postgres.Pool.CreateCommand( + "SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count " + + "FROM items WHERE category = $1 ORDER BY id LIMIT $2 OFFSET $3"); + + cmd.Parameters.AddWithValue(category); + cmd.Parameters.AddWithValue(limit); + cmd.Parameters.AddWithValue(offset); + cmd.CommandTimeout = 2; + + await using var reader = await cmd.ExecuteReaderAsync(); + + var items = new List(); + + while (await reader.ReadAsync()) + { + items.Add(new ProcessedItem + { + 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>(reader.GetString(6)), + Rating = new RatingInfo + { + Score = (int)reader.GetDouble(7), + Count = reader.GetInt32(8) + } + }); + } + + return new CrudListResponse + { + Items = items, + Total = items.Count, + Page = page, + Limit = limit + }; + } + + [ResourceMethod(":id")] + public async ValueTask Get(int id, IRequest request) + { + if (ItemCache.TryGetValue(id, out string cached)) + { + return request.Respond() + .Content(new StringContent(cached, ContentType.ApplicationJson)) + .Header("X-Cache", "HIT") + .Build(); + } + + var item = await FetchItemByIdAsync(id); + + if (item == null) + { + throw new ProviderException(ResponseStatus.NotFound, $"Item with ID {id} does not exist"); + } + + var json = JsonSerializer.Serialize(item, JsonOptions); + + ItemCache.Set(id, json, ItemCacheOptions); + + return request.Respond() + .Content(new StringContent(json, ContentType.ApplicationJson)) + .Header("X-Cache", "MISS") + .Build(); + } + + [ResourceMethod(Method.Post)] + public async Task> Create(CrudItem item) + { + await using var cmd = Postgres.Pool.CreateCommand( + "INSERT INTO items (id, name, category, price, quantity, active, tags, rating_score, rating_count) " + + "VALUES ($1, $2, $3, $4, $5, true, '[\"bench\"]', 0, 0) " + + "ON CONFLICT (id) DO UPDATE SET name = $2, price = $4, quantity = $5 " + + "RETURNING id"); + + cmd.Parameters.AddWithValue(item.Id); + cmd.Parameters.AddWithValue(item.Name ?? "New Product"); + cmd.Parameters.AddWithValue(item.Category ?? "test"); + cmd.Parameters.AddWithValue(item.Price); + cmd.Parameters.AddWithValue(item.Quantity); + cmd.CommandTimeout = 2; + + item.Id = (int)(await cmd.ExecuteScalarAsync())!; + + return new Result(item).Status(ResponseStatus.Created); + } + + [ResourceMethod(Method.Put, ":id")] + public async Task Update(int id, CrudItem item) + { + await using var cmd = Postgres.Pool.CreateCommand( + "UPDATE items SET name = $1, price = $2, quantity = $3 WHERE id = $4"); + + cmd.Parameters.AddWithValue(item.Name ?? "Updated"); + cmd.Parameters.AddWithValue(item.Price); + cmd.Parameters.AddWithValue(item.Quantity); + cmd.Parameters.AddWithValue(id); + cmd.CommandTimeout = 2; + + var affected = await cmd.ExecuteNonQueryAsync(); + + if (affected == 0) + { + throw new ProviderException(ResponseStatus.NotFound, $"Item with ID {id} does not exist"); + } + + ItemCache.Remove(id); + + return item; + } + + private static async Task FetchItemByIdAsync(int id) + { + await using var cmd = Postgres.Pool!.CreateCommand( + "SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count " + + "FROM items WHERE id = $1 LIMIT 1"); + + cmd.Parameters.AddWithValue(id); + cmd.CommandTimeout = 2; + + await using var reader = await cmd.ExecuteReaderAsync(); + if (!await reader.ReadAsync()) return null; + + return new ProcessedItem() + { + 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>(reader.GetString(6)), + Rating = new RatingInfo + { + Score = (int)reader.GetDouble(7), + Count = reader.GetInt32(8) + } + }; + } + +} diff --git a/frameworks/genhttp-11/Tests/EchoHandler.cs b/frameworks/genhttp-11/Tests/EchoHandler.cs new file mode 100644 index 000000000..7977a311a --- /dev/null +++ b/frameworks/genhttp-11/Tests/EchoHandler.cs @@ -0,0 +1,23 @@ +using GenHTTP.Modules.Websockets; +using GenHTTP.Modules.Websockets.Protocol; + +namespace genhttp.Tests; + +class EchoHandler : IImperativeHandler +{ + public async ValueTask HandleAsync(IImperativeConnection connection) + { + while (true) + { + var frame = await connection.ReadFrameAsync(); + + if (frame.Type == FrameType.Close) + break; + + if (frame.Type == FrameType.Text || frame.Type == FrameType.Binary) + { + await connection.WriteAsync(frame.Data, frame.Type); + } + } + } +} diff --git a/frameworks/genhttp-11/Tests/Json.cs b/frameworks/genhttp-11/Tests/Json.cs new file mode 100644 index 000000000..c15bc7494 --- /dev/null +++ b/frameworks/genhttp-11/Tests/Json.cs @@ -0,0 +1,60 @@ +using System.Text.Json; + +using GenHTTP.Api.Content; +using GenHTTP.Api.Protocol; +using GenHTTP.Modules.Webservices; + +namespace genhttp.Tests; + +public class Json +{ + private static readonly List? DatasetItems = LoadItems(); + + private static List? LoadItems() + { + var jsonOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + var datasetPath = Environment.GetEnvironmentVariable("DATASET_PATH") ?? "/data/dataset.json"; + + if (File.Exists(datasetPath)) + { + return JsonSerializer.Deserialize>(File.ReadAllText(datasetPath), jsonOptions); + } + + return null; + } + + [ResourceMethod(":count")] + public ListWithCount Compute(int count, int m = 1) + { + if (DatasetItems == null) + { + throw new ProviderException(ResponseStatus.InternalServerError, "No dataset"); + } + + if (count > DatasetItems.Count) count = DatasetItems.Count; + if (count < 0) count = 0; + + var processed = new List(count); + + for (var i = 0; i < count; i++) + { + var d = DatasetItems[i]; + + 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 = d.Price * d.Quantity * m + }); + } + + return new(processed); + } + +} diff --git a/frameworks/genhttp-11/Tests/Upload.cs b/frameworks/genhttp-11/Tests/Upload.cs new file mode 100644 index 000000000..9bf435cca --- /dev/null +++ b/frameworks/genhttp-11/Tests/Upload.cs @@ -0,0 +1,38 @@ +using GenHTTP.Modules.Reflection; +using GenHTTP.Modules.Webservices; + +namespace genhttp.Tests; + +public class Upload +{ + + [ResourceMethod(Method.Post)] + public ValueTask Compute(Stream input) + { + if (input.CanSeek) + { + // internal engine + return ValueTask.FromResult(input.Length); + } + + // kestrel + return ComputeManually(input); + } + + private async ValueTask ComputeManually(Stream input) + { + var buffer = new byte[8192]; + + long total = 0; + + var read = 0; + + while ((read = await input.ReadAsync(buffer)) > 0) + { + total += read; + } + + return total; + } + +} diff --git a/frameworks/genhttp-11/genhttp.csproj b/frameworks/genhttp-11/genhttp.csproj new file mode 100644 index 000000000..200e814ef --- /dev/null +++ b/frameworks/genhttp-11/genhttp.csproj @@ -0,0 +1,25 @@ + + + Exe + net10.0 + enable + true + enable + + + + + + + + + + + + + + + + + + diff --git a/frameworks/genhttp-11/meta.json b/frameworks/genhttp-11/meta.json new file mode 100644 index 000000000..2bfd893b9 --- /dev/null +++ b/frameworks/genhttp-11/meta.json @@ -0,0 +1,27 @@ +{ + "display_name": "genhttp-11", + "language": "C#", + "type": "tuned", + "engine": "genhttp", + "description": "Lightweight, embeddable and modular C# web server.", + "repo": "https://github.com/Kaliumhexacyanoferrat/GenHTTP", + "enabled": true, + "tests": [ + "baseline", + "pipelined", + "limited-conn", + "json", + "json-comp", + "json-tls", + "upload", + "async-db", + "crud", + "static", + "echo-ws", + "api-4", + "api-16" + ], + "maintainers": [ + "Kaliumhexacyanoferrat" + ] +} From 56cff351d518aab7c8e8a4fd8f0c1a01bbccd8c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Tue, 19 May 2026 17:53:44 +0200 Subject: [PATCH 2/3] Only enable working tests for now --- frameworks/genhttp-11/meta.json | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/frameworks/genhttp-11/meta.json b/frameworks/genhttp-11/meta.json index 2bfd893b9..3f5627d25 100644 --- a/frameworks/genhttp-11/meta.json +++ b/frameworks/genhttp-11/meta.json @@ -7,19 +7,9 @@ "repo": "https://github.com/Kaliumhexacyanoferrat/GenHTTP", "enabled": true, "tests": [ - "baseline", "pipelined", - "limited-conn", - "json", - "json-comp", - "json-tls", - "upload", "async-db", - "crud", - "static", - "echo-ws", - "api-4", - "api-16" + "echo-ws" ], "maintainers": [ "Kaliumhexacyanoferrat" From 6b7be670b4bea5a862b43256f0a6f79883b871e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20N=C3=A4geli?= Date: Tue, 19 May 2026 17:57:08 +0200 Subject: [PATCH 3/3] No WS for now --- frameworks/genhttp-11/meta.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frameworks/genhttp-11/meta.json b/frameworks/genhttp-11/meta.json index 3f5627d25..ee31c7a1f 100644 --- a/frameworks/genhttp-11/meta.json +++ b/frameworks/genhttp-11/meta.json @@ -8,8 +8,7 @@ "enabled": true, "tests": [ "pipelined", - "async-db", - "echo-ws" + "async-db" ], "maintainers": [ "Kaliumhexacyanoferrat"