소스 검색

Merge pull request #38 from TechEmpower/master

aa
三刀 8 달 전
부모
커밋
40ce60df32
100개의 변경된 파일1715개의 추가작업 그리고 746개의 파일을 삭제
  1. 5 3
      frameworks/CSharp/appmpower/appmpower-odbc-my.dockerfile
  2. 7 3
      frameworks/CSharp/appmpower/appmpower-odbc-pg.dockerfile
  3. 2 2
      frameworks/CSharp/appmpower/appmpower.dockerfile
  4. 59 0
      frameworks/CSharp/appmpower/src/appMpower.Orm/NativeMethods.cs
  5. 24 0
      frameworks/CSharp/appmpower/src/appMpower.Orm/Serializers/FortunesSerializer.cs
  6. 2 2
      frameworks/CSharp/appmpower/src/appMpower.Orm/appMpower.Orm.csproj
  7. 115 0
      frameworks/CSharp/appmpower/src/appMpower/Middleware/FortunesMiddleware.cs
  8. 22 0
      frameworks/CSharp/appmpower/src/appMpower/Objects/Fortune.cs
  9. 2 0
      frameworks/CSharp/appmpower/src/appMpower/Slices/Fortunes.cshtml
  10. 10 0
      frameworks/CSharp/appmpower/src/appMpower/Slices/_ViewImports.cshtml
  11. 2 1
      frameworks/CSharp/appmpower/src/appMpower/appMpower.csproj
  12. 3 0
      frameworks/CSharp/aspnetcore/src/Platform/Platform.csproj
  13. 36 38
      frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj
  14. 23 32
      frameworks/CSharp/genhttp/Benchmarks/Model/DatabaseContext.cs
  15. 10 18
      frameworks/CSharp/genhttp/Benchmarks/Model/Fortune.cs
  16. 9 12
      frameworks/CSharp/genhttp/Benchmarks/Model/World.cs
  17. 16 31
      frameworks/CSharp/genhttp/Benchmarks/Program.cs
  18. 0 7
      frameworks/CSharp/genhttp/Benchmarks/Resources/Fortunes.html
  19. 16 3
      frameworks/CSharp/genhttp/Benchmarks/Resources/Template.html
  20. 54 54
      frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs
  21. 10 18
      frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs
  22. 48 79
      frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs
  23. 11 12
      frameworks/CSharp/genhttp/Benchmarks/Tests/JsonResource.cs
  24. 27 30
      frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs
  25. 39 41
      frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs
  26. 4 8
      frameworks/CSharp/genhttp/Benchmarks/Utilities/ServerHeader.cs
  27. 23 36
      frameworks/CSharp/genhttp/Benchmarks/Utilities/ServerHeaderConcern.cs
  28. 4 12
      frameworks/CSharp/genhttp/Benchmarks/Utilities/ServerHeaderConcernBuilder.cs
  29. 2 2
      frameworks/CSharp/genhttp/README.md
  30. 1 1
      frameworks/CSharp/genhttp/benchmark_config.json
  31. 1 1
      frameworks/CSharp/genhttp/config.toml
  32. 13 13
      frameworks/CSharp/genhttp/genhttp.dockerfile
  33. 2 2
      frameworks/CSharp/reaper/reaper.dockerfile
  34. 3 3
      frameworks/CSharp/reaper/src/Benchmark/Benchmark.csproj
  35. 3 2
      frameworks/CSharp/reaper/src/Benchmark/JsonEndpoint.cs
  36. 3 2
      frameworks/CSharp/reaper/src/Benchmark/PlainTextEndpoint.cs
  37. 1 10
      frameworks/CSharp/reaper/src/Benchmark/Program.cs
  38. 1 1
      frameworks/CSharp/reaper/src/Benchmark/appsettings.Development.json
  39. 5 5
      frameworks/FSharp/oxpecker/src/App/App.fsproj
  40. 4 2
      frameworks/FSharp/oxpecker/src/App/Common.fs
  41. 18 13
      frameworks/FSharp/oxpecker/src/App/Db.fs
  42. 13 26
      frameworks/FSharp/oxpecker/src/App/Program.fs
  43. 20 9
      frameworks/FSharp/oxpecker/src/App/RenderHelpers.fs
  44. 11 0
      frameworks/Go/pine/README.md
  45. 26 0
      frameworks/Go/pine/benchmark_config.json
  46. 10 0
      frameworks/Go/pine/pine.dockerfile
  47. 5 0
      frameworks/Go/pine/src/go.mod
  48. 2 0
      frameworks/Go/pine/src/go.sum
  49. 28 0
      frameworks/Go/pine/src/main.go
  50. 39 0
      frameworks/Java/hserver-business/README.md
  51. 31 0
      frameworks/Java/hserver-business/benchmark_config.json
  52. 17 0
      frameworks/Java/hserver-business/config.toml
  53. 13 0
      frameworks/Java/hserver-business/hserver.dockerfile
  54. 52 0
      frameworks/Java/hserver-business/pom.xml
  55. 18 0
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/StartApp.java
  56. 24 0
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/bean/Fortune.java
  57. 3 11
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/bean/Message.java
  58. 24 0
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/bean/World.java
  59. 136 0
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/controller/TestController.java
  60. 25 0
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/db/DataSourceConfig.java
  61. 57 0
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/db/PostgresConfig.java
  62. 15 0
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/task/TimeAdd.java
  63. 25 0
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/util/DateUtil.java
  64. 18 0
      frameworks/Java/hserver-business/src/main/java/com/test/hserver/util/Util.java
  65. 6 0
      frameworks/Java/hserver-business/src/main/resources/app.properties
  66. 20 0
      frameworks/Java/hserver-business/src/main/resources/template/fortunes.ftl
  67. 5 5
      frameworks/Java/hserver/benchmark_config.json
  68. 3 3
      frameworks/Java/hserver/config.toml
  69. 3 3
      frameworks/Java/hserver/hserver.dockerfile
  70. 1 1
      frameworks/Java/hserver/pom.xml
  71. 7 4
      frameworks/Java/hserver/src/main/java/com/test/hserver/controller/TestController.java
  72. 48 2
      frameworks/Java/solon/pom.xml
  73. 0 4
      frameworks/Java/solon/src/main/java/hello/Main.java
  74. 17 0
      frameworks/Java/solon/src/main/java/hello/Utils.java
  75. 0 25
      frameworks/Java/solon/src/main/java/hello/controller/HelloController.java
  76. 16 0
      frameworks/Java/solon/src/main/java/hello/model/Fortune.java
  77. 12 0
      frameworks/Java/solon/src/main/java/hello/model/World.java
  78. 15 0
      frameworks/Java/solon/src/main/java/hello/repository/DbRepository.java
  79. 46 0
      frameworks/Java/solon/src/main/java/hello/repository/JdbcDbRepository.java
  80. 61 0
      frameworks/Java/solon/src/main/java/hello/web/DbHandler.java
  81. 10 0
      frameworks/Java/solon/src/main/java/hello/web/Fortunes.java
  82. 27 0
      frameworks/Java/solon/src/main/java/hello/web/JsonHandler.java
  83. 25 0
      frameworks/Java/solon/src/main/java/hello/web/TextHandler.java
  84. 29 0
      frameworks/Java/solon/src/main/java/hello/web/WebmvcRouter.java
  85. 0 1
      frameworks/Java/solon/src/main/resources/app.properties
  86. 23 0
      frameworks/Java/solon/src/main/resources/app.yml
  87. 20 0
      frameworks/Java/solon/src/main/resources/fortunes.mustache
  88. 1 1
      frameworks/Java/spring-webflux/src/main/resources/application.yml
  89. 1 1
      frameworks/Java/spring/src/main/resources/application.yml
  90. 1 1
      frameworks/JavaScript/express/package.json
  91. 0 30
      frameworks/JavaScript/fastify/README.md
  92. 2 7
      frameworks/JavaScript/fastify/benchmark_config.json
  93. 0 5
      frameworks/JavaScript/fastify/config.toml
  94. 5 0
      frameworks/JavaScript/fastify/create-server.js
  95. 0 47
      frameworks/JavaScript/fastify/db/mongo.js
  96. 23 29
      frameworks/JavaScript/fastify/db/mysql.js
  97. 25 28
      frameworks/JavaScript/fastify/db/postgres.js
  98. 5 1
      frameworks/JavaScript/fastify/fastify-mysql.dockerfile
  99. 5 1
      frameworks/JavaScript/fastify/fastify-postgres.dockerfile
  100. 1 2
      frameworks/JavaScript/fastify/fastify.dockerfile

+ 5 - 3
frameworks/CSharp/appmpower/appmpower-odbc-my.dockerfile

@@ -1,4 +1,4 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0.100 AS build
+FROM mcr.microsoft.com/dotnet/sdk:9.0.100 AS build
 RUN apt-get update
 RUN apt-get -yqq install clang zlib1g-dev
 RUN apt-get update
@@ -8,12 +8,12 @@ COPY src .
 RUN dotnet publish -c Release -o out /p:Database=mysql
 
 # Construct the actual image that will run
-FROM mcr.microsoft.com/dotnet/aspnet:8.0.0 AS runtime
+FROM mcr.microsoft.com/dotnet/aspnet:9.0.0 AS runtime
 
 RUN apt-get update
 # The following installs standard versions unixodbc and pgsqlodbc
 # unixodbc still needs to be installed even if compiled locally
-RUN apt-get install -y unixodbc wget curl
+RUN apt-get install -y unixodbc-dev unixodbc wget curl
 RUN apt-get update
 
 WORKDIR /odbc
@@ -45,6 +45,8 @@ WORKDIR /app
 COPY --from=build /app/out ./
 
 RUN cp /usr/lib/libm* /app
+#RUN cp /usr/lib/aarch64-linux-gnu/libodbc* /app
+RUN cp /usr/lib/x86_64-linux-gnu/libodbc* /app
 
 EXPOSE 8080
 

+ 7 - 3
frameworks/CSharp/appmpower/appmpower-odbc-pg.dockerfile

@@ -1,4 +1,4 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0.100 AS build
+FROM mcr.microsoft.com/dotnet/sdk:9.0.100 AS build
 RUN apt-get update
 RUN apt-get -yqq install clang zlib1g-dev libkrb5-dev libtinfo5
 
@@ -7,10 +7,10 @@ COPY src .
 RUN dotnet publish -c Release -o out /p:Database=postgresql
 
 # Construct the actual image that will run
-FROM mcr.microsoft.com/dotnet/aspnet:8.0.0 AS runtime
+FROM mcr.microsoft.com/dotnet/aspnet:9.0.0 AS runtime
 
 RUN apt-get update
-RUN apt-get install -y unixodbc odbc-postgresql
+RUN apt-get install -y unixodbc-dev unixodbc odbc-postgresql
 # unixodbc still needs to be installed even if compiled locally
 
 ENV PATH=/usr/local/unixODBC/bin:$PATH
@@ -27,6 +27,10 @@ ENV ASPNETCORE_URLS http://+:8080
 WORKDIR /app
 COPY --from=build /app/out ./
 
+#RUN cp /usr/lib/aarch64-linux-gnu/libodbc* /app
+RUN cp /usr/lib/x86_64-linux-gnu/libodbc* /app
+
+
 EXPOSE 8080
 
 ENTRYPOINT ["./appMpower"]

+ 2 - 2
frameworks/CSharp/appmpower/appmpower.dockerfile

@@ -1,4 +1,4 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0.100 AS build
+FROM mcr.microsoft.com/dotnet/sdk:9.0.100 AS build
 RUN apt-get update
 RUN apt-get -yqq install clang zlib1g-dev libkrb5-dev libtinfo5
 
@@ -8,7 +8,7 @@ COPY src .
 RUN dotnet publish -c Release -o out
 
 # Construct the actual image that will run
-FROM mcr.microsoft.com/dotnet/aspnet:8.0.0 AS runtime
+FROM mcr.microsoft.com/dotnet/aspnet:9.0.0 AS runtime
 # Full PGO
 ENV DOTNET_TieredPGO 1 
 ENV DOTNET_TC_QuickJitForLoops 1 

+ 59 - 0
frameworks/CSharp/appmpower/src/appMpower.Orm/NativeMethods.cs

@@ -17,6 +17,9 @@ public static class NativeMethods
 
     private readonly static WorldSerializer _worldSerializer = new WorldSerializer();
     private readonly static WorldsSerializer _worldsSerializer = new WorldsSerializer();
+    private readonly static FortunesSerializer _fortunesSerializer = new FortunesSerializer();
+    private static readonly byte[] _delimiter = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
+
 
     [UnmanagedCallersOnly(EntryPoint = "Dbms")]
     public static void Dbms(int dbms)
@@ -66,6 +69,7 @@ public static class NativeMethods
         */
     }
 
+    /*
     [UnmanagedCallersOnly(EntryPoint = "Fortunes")]
     public static unsafe IntPtr Fortunes(int* length, IntPtr* handlePointer)
     {
@@ -81,6 +85,61 @@ public static class NativeMethods
 
         return byteArrayPointer;
     }
+    */
+
+    [UnmanagedCallersOnly(EntryPoint = "Fortunes")]
+    public static unsafe IntPtr Fortunes(int* length, IntPtr* handlePointer)
+    {
+        List<Fortune> fortunes = RawDb.LoadFortunesRows().GetAwaiter().GetResult(); 
+
+        int totalSize = 0;
+
+        foreach (var fortune in fortunes)
+        {
+            totalSize += sizeof(int) // for Id
+                       + Encoding.UTF8.GetByteCount(fortune.Message ?? "") // for Message
+                       + _delimiter.Length; // for delimiter
+        }
+
+        // Allocate the total buffer
+        byte[] buffer = new byte[totalSize];
+        int offset = 0;
+
+        // Write each object to the buffer
+        foreach (var fortune in fortunes)
+        {
+            // Write Id
+            BitConverter.TryWriteBytes(buffer.AsSpan(offset, sizeof(int)), fortune.Id);
+            offset += sizeof(int);
+
+            // Write Message
+            int descriptionLength = Encoding.UTF8.GetBytes(fortune.Message ?? "", buffer.AsSpan(offset));
+            offset += descriptionLength;
+
+            // Write Delimiter
+            _delimiter.CopyTo(buffer, offset);
+            offset += _delimiter.Length;
+        }
+
+        byte[] byteArray = buffer.ToArray();
+        *length = byteArray.Length; 
+
+        /*
+        var memoryStream = new MemoryStream();
+        using var utf8JsonWriter = new Utf8JsonWriter(memoryStream, _jsonWriterOptions);
+
+        _fortunesSerializer.Serialize(utf8JsonWriter, fortunes);
+
+        byte[] byteArray = memoryStream.ToArray();
+        *length = (int)utf8JsonWriter.BytesCommitted; 
+        */
+
+        GCHandle handle = GCHandle.Alloc(byteArray, GCHandleType.Pinned);
+        IntPtr byteArrayPointer = handle.AddrOfPinnedObject();
+        *handlePointer = GCHandle.ToIntPtr(handle);
+
+        return byteArrayPointer;
+    }
 
     [UnmanagedCallersOnly(EntryPoint = "Query")]
     public static unsafe IntPtr Query(int queries, int* length, IntPtr* handlePointer)

+ 24 - 0
frameworks/CSharp/appmpower/src/appMpower.Orm/Serializers/FortunesSerializer.cs

@@ -0,0 +1,24 @@
+using System.Text.Json;
+using appMpower.Orm.Objects;
+
+namespace appMpower.Orm.Serializers
+{
+   public class FortunesSerializer : IJsonSerializer<List<Fortune>>
+   {
+      public void Serialize(Utf8JsonWriter utf8JsonWriter, List<Fortune> fortunes)
+      {
+         utf8JsonWriter.WriteStartArray();
+
+         foreach (Fortune fortune in fortunes)
+         {
+            utf8JsonWriter.WriteStartObject();
+            utf8JsonWriter.WriteNumber("id", fortune.Id);
+            utf8JsonWriter.WriteString("message", fortune.Message);
+            utf8JsonWriter.WriteEndObject();
+         }
+
+         utf8JsonWriter.WriteEndArray();
+         utf8JsonWriter.Flush();
+      }
+   }
+}

+ 2 - 2
frameworks/CSharp/appmpower/src/appMpower.Orm/appMpower.Orm.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
 
     <PublishAot>true</PublishAot>
@@ -36,7 +36,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="System.Data.Odbc" Version="8.0.0" />
+    <PackageReference Include="System.Data.Odbc" Version="9.0.0" />
   </ItemGroup>
 
 </Project>

+ 115 - 0
frameworks/CSharp/appmpower/src/appMpower/Middleware/FortunesMiddleware.cs

@@ -2,7 +2,11 @@ using System;
 using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using System.Text;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Unicode;
 using System.Threading.Tasks;
+using appMpower.Objects;
 using Microsoft.AspNetCore.Builder;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Primitives;
@@ -11,11 +15,21 @@ namespace appMpower;
 
 public class FortunesMiddleware
 {
+    static readonly HtmlEncoder htmlEncoder = CreateHtmlEncoder();
+    static HtmlEncoder CreateHtmlEncoder()
+    {
+        var settings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Katakana, UnicodeRanges.Hiragana);
+        settings.AllowCharacter('\u2014'); // allow EM DASH through
+        return HtmlEncoder.Create(settings);
+    }
+
     private readonly static KeyValuePair<string, StringValues> _headerServer =
          new KeyValuePair<string, StringValues>("Server", new StringValues("k"));
     private readonly static KeyValuePair<string, StringValues> _headerContentType =
          new KeyValuePair<string, StringValues>("Content-Type", new StringValues("text/html; charset=UTF-8"));
 
+    private static readonly byte[] _delimiter = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
+
     private readonly RequestDelegate _next;
 
     public FortunesMiddleware(RequestDelegate next)
@@ -27,6 +41,66 @@ public class FortunesMiddleware
     {
         if (httpContext.Request.Path.StartsWithSegments("/fortunes", StringComparison.Ordinal))
         {
+            int payloadLength;
+            IntPtr handlePointer; 
+
+            IntPtr bytePointer = NativeMethods.Fortunes(out payloadLength, out handlePointer);
+
+            /*
+            byte[] json = new byte[payloadLength];
+            Marshal.Copy(bytePointer, json, 0, payloadLength);
+            NativeMethods.FreeHandlePointer(handlePointer);
+
+            string s = Encoding.UTF8.GetString(json, 0, json.Length);
+
+            var options = new JsonSerializerOptions
+            {
+                PropertyNameCaseInsensitive = true
+            };
+
+            List<Fortune> fortunes = JsonSerializer.Deserialize<List<Fortune>>(s, options);
+
+            var response = httpContext.Response; 
+            response.Headers.Add(_headerServer);
+
+            var result = Results.Extensions.RazorSlice<Slices.Fortunes, List<Fortune>>(fortunes);
+            result.HtmlEncoder = htmlEncoder;
+
+            return result.ExecuteAsync(httpContext);
+            */
+
+            byte[] byteArray = new byte[payloadLength];
+            Marshal.Copy(bytePointer, byteArray, 0, payloadLength);
+
+            List<Fortune> fortunes = new List<Fortune>();
+
+            // Convert the byte array into segments split by the delimiter
+            int delimiterLength = _delimiter.Length;
+            int start = 0;
+            int index;
+
+            while ((index = FindDelimiterIndex(byteArray, _delimiter, start)) >= 0)
+            {
+                // Use a span over the segment of bytes for the current object
+                var objectDataSpan = new ReadOnlySpan<byte>(byteArray, start, index - start);
+                Fortune fortune = ConvertBytesToObject(objectDataSpan);
+                fortunes.Add(fortune);
+
+                // Move past the delimiter
+                start = index + delimiterLength;
+            }
+
+            NativeMethods.FreeHandlePointer(handlePointer);
+
+            var response = httpContext.Response; 
+            response.Headers.Add(_headerServer);
+
+            var result = Results.Extensions.RazorSlice<Slices.Fortunes, List<Fortune>>(fortunes);
+            result.HtmlEncoder = htmlEncoder;
+
+            return result.ExecuteAsync(httpContext);
+
+            /*
             var response = httpContext.Response; 
             response.Headers.Add(_headerServer);
             response.Headers.Add(_headerContentType);
@@ -43,10 +117,51 @@ public class FortunesMiddleware
                 new KeyValuePair<string, StringValues>("Content-Length", payloadLength.ToString()));
 
             return response.Body.WriteAsync(json, 0, payloadLength);
+            */
         }
 
         return _next(httpContext);
     }
+
+    private static int FindDelimiterIndex(byte[] array, byte[] delimiter, int startIndex)
+    {
+        int endIndex = array.Length - delimiter.Length;
+
+        for (int i = startIndex; i <= endIndex; i++)
+        {
+            bool isMatch = true;
+
+            for (int j = 0; j < delimiter.Length; j++)
+            {
+                if (array[i + j] != delimiter[j])
+                {
+                    isMatch = false;
+                    break;
+                }
+            }
+
+            if (isMatch)
+            {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    private static Fortune ConvertBytesToObject(ReadOnlySpan<byte> data)
+    {
+        int offset = 0;
+
+        // Read Id
+        int id = BitConverter.ToInt32(data.Slice(offset, sizeof(int)));
+        offset += sizeof(int);
+
+        // Read Message (remaining bytes in the span)
+        string message = Encoding.UTF8.GetString(data.Slice(offset));
+
+        return new Fortune(id, message);
+    }
 }
 
 public static class FortunesMiddlewareExtensions

+ 22 - 0
frameworks/CSharp/appmpower/src/appMpower/Objects/Fortune.cs

@@ -0,0 +1,22 @@
+using System;
+
+namespace appMpower.Objects
+{
+   public struct Fortune : IComparable<Fortune>, IComparable
+   {
+      public Fortune(int id, string message)
+      {
+         Id = id;
+         Message = message;
+      }
+
+      public int Id { get; set; }
+
+      public string Message { get; set; }
+
+      public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used");
+
+      // Performance critical, using culture insensitive comparison
+      public int CompareTo(Fortune other) => string.CompareOrdinal(Message, other.Message);
+   }
+}

+ 2 - 0
frameworks/CSharp/appmpower/src/appMpower/Slices/Fortunes.cshtml

@@ -0,0 +1,2 @@
+@inherits RazorSliceHttpResult<List<appMpower.Objects.Fortune>>
+<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>@foreach (var item in Model){<tr><td>@WriteNumber(item.Id, default, CultureInfo.InvariantCulture, false)</td><td>@item.Message</td></tr>}</table></body></html>

+ 10 - 0
frameworks/CSharp/appmpower/src/appMpower/Slices/_ViewImports.cshtml

@@ -0,0 +1,10 @@
+@inherits RazorSliceHttpResult
+
+@using System.Globalization;
+@using Microsoft.AspNetCore.Razor;
+@using Microsoft.AspNetCore.Http.HttpResults;
+@using RazorSlices;
+@using appMpower.Objects;
+
+@tagHelperPrefix __disable_tagHelpers__:
+@removeTagHelper *, Microsoft.AspNetCore.Mvc.Razor

+ 2 - 1
frameworks/CSharp/appmpower/src/appMpower/appMpower.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
-    <TargetFramework>net8.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
     <OutputType>Exe</OutputType>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
@@ -24,6 +24,7 @@
 
   <ItemGroup>
     <ProjectReference Include = "..\appMpower.Orm\appMpower.Orm.csproj" />
+    <PackageReference Include="RazorSlices" Version="0.8.1" />
   </ItemGroup>
 
   <PropertyGroup>

+ 3 - 0
frameworks/CSharp/aspnetcore/src/Platform/Platform.csproj

@@ -28,5 +28,8 @@
   <PropertyGroup Condition="$(PublishAot) == 'true'">
     <DefaultItemExcludes>$(MSBuildThisFileDirectory)Templates/**;$(DefaultItemExcludes)</DefaultItemExcludes>
   </PropertyGroup>
+  <ItemGroup Condition="$(PublishAot) == 'true'">
+    <RuntimeHostConfigurationOption Include="System.Threading.ThreadPool.HillClimbing.Disable" Value="true" />
+  </ItemGroup>
 
 </Project>

+ 36 - 38
frameworks/CSharp/genhttp/Benchmarks/Benchmarks.csproj

@@ -1,40 +1,38 @@
 <Project Sdk="Microsoft.NET.Sdk">
-  
-  <PropertyGroup>
-    
-    <TargetFramework>net8.0</TargetFramework>
-    <LangVersion>10.0</LangVersion>
-    
-    <AssemblyTitle>GenHTTP Benchmarks</AssemblyTitle>
-    <Description>Test suite to be executed with TechEmpower FrameworkBenchmarks.</Description>
-    
-    <StartupObject>Benchmarks.Program</StartupObject>    
-    <OutputType>Exe</OutputType>
-    
-    <ServerGarbageCollection>true</ServerGarbageCollection>
-	  <TieredPGO>true</TieredPGO>
-    
-  </PropertyGroup>
-  
-  <ItemGroup>
-    <None Remove="Resources\Fortunes.html" />
-    <None Remove="Resources\Template.html" />
-  </ItemGroup>
-  
-  <ItemGroup>
-    <EmbeddedResource Include="Resources\Template.html" />
-    <EmbeddedResource Include="Resources\Fortunes.html" />
-  </ItemGroup>
-    
-  <ItemGroup>
-	  
-    <PackageReference Include="GenHTTP.Core" Version="8.5.2" />
-    <PackageReference Include="GenHTTP.Modules.Razor" Version="8.5.0" />
-    <PackageReference Include="GenHTTP.Modules.Webservices" Version="8.5.0" />
-	  
-    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
-    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
-	  
-  </ItemGroup>
-  
+
+    <PropertyGroup>
+
+        <TargetFramework>net9.0</TargetFramework>
+        <LangVersion>13.0</LangVersion>
+        <ImplicitUsings>true</ImplicitUsings>
+        <OutputType>Exe</OutputType>
+
+        <AssemblyTitle>GenHTTP Benchmarks</AssemblyTitle>
+        <Description>Test suite to be executed with TechEmpower FrameworkBenchmarks.</Description>
+
+        <ServerGarbageCollection>true</ServerGarbageCollection>
+        <TieredPGO>true</TieredPGO>
+
+    </PropertyGroup>
+
+    <ItemGroup>
+        <None Remove="Resources\Fortunes.html"/>
+        <None Remove="Resources\Template.html"/>
+    </ItemGroup>
+
+    <ItemGroup>
+        <EmbeddedResource Include="Resources\Template.html"/>
+    </ItemGroup>
+
+    <ItemGroup>
+
+        <PackageReference Include="GenHTTP.Core.Kestrel" Version="9.0.0" />
+        <PackageReference Include="GenHTTP.Modules.Razor" Version="8.6.0" />
+        <PackageReference Include="GenHTTP.Modules.Webservices" Version="9.0.0" />
+
+        <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
+        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0" />
+
+    </ItemGroup>
+
 </Project>

+ 23 - 32
frameworks/CSharp/genhttp/Benchmarks/Model/DatabaseContext.cs

@@ -1,52 +1,43 @@
 using Microsoft.EntityFrameworkCore;
 
-namespace Benchmarks.Model
+namespace Benchmarks.Model;
+
+public sealed class DatabaseContext : DbContext
 {
+    private static DbContextOptions<DatabaseContext> _options;
 
-    public sealed class DatabaseContext : DbContext
-    {
-        private static DbContextOptions<DatabaseContext> _Options;
+    private static DbContextOptions<DatabaseContext> _noTrackingOptions;
 
-        private static DbContextOptions<DatabaseContext> _NoTrackingOptions;
+    #region Factory
 
-        #region Factory
+    public static DatabaseContext Create() => new(_options ??= GetOptions(true));
 
-        public static DatabaseContext Create()
-        {
-            return new DatabaseContext(_Options ??= GetOptions(true));
-        }
-
-        public static DatabaseContext CreateNoTracking()
-        {
-            return new DatabaseContext(_NoTrackingOptions ??= GetOptions(false));
-        }
+    public static DatabaseContext CreateNoTracking() => new(_noTrackingOptions ??= GetOptions(false));
 
-        private static DbContextOptions<DatabaseContext> GetOptions(bool tracking)
-        {
-            var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
-
-            optionsBuilder.UseNpgsql("Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;Maximum Pool Size=256;NoResetOnClose=true;Enlist=false;Max Auto Prepare=4");
+    private static DbContextOptions<DatabaseContext> GetOptions(bool tracking)
+    {
+        var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
 
-            if (!tracking)
-            {
-                optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
-            }
+        optionsBuilder.UseNpgsql("Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;SSL Mode=Disable;Maximum Pool Size=512;NoResetOnClose=true;Enlist=false;Max Auto Prepare=4");
 
-            return optionsBuilder.Options;
+        if (!tracking)
+        {
+            optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
         }
 
-        private DatabaseContext(DbContextOptions options) : base(options) { }
+        return optionsBuilder.Options;
+    }
 
-        #endregion
+    private DatabaseContext(DbContextOptions options) : base(options) { }
 
-        #region Entities
+    #endregion
 
-        public DbSet<World> World { get; set; }
+    #region Entities
 
-        public DbSet<Fortune> Fortune { get; set; }
+    public DbSet<World> World { get; set; }
 
-        #endregion
+    public DbSet<Fortune> Fortune { get; set; }
 
-    }
+    #endregion
 
 }

+ 10 - 18
frameworks/CSharp/genhttp/Benchmarks/Model/Fortune.cs

@@ -1,25 +1,17 @@
-using System;
-using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 
-namespace Benchmarks.Model
-{
-
-    [Table("fortune")]
-    public class Fortune : IComparable<Fortune>, IComparable
-    {
-
-        [Column("id")]
-        public int ID { get; set; }
+namespace Benchmarks.Model;
 
-        [Column("message")]
-        [StringLength(2048)]
-        public string Message { get; set; }
-
-        public int CompareTo(object obj) => CompareTo((Fortune)obj);
+[Table("fortune")]
+public class Fortune
+{
 
-        public int CompareTo(Fortune other) => string.CompareOrdinal(Message, other.Message);
+    [Column("id")]
+    public int Id { get; set; }
 
-    }
+    [Column("message")]
+    [StringLength(2048)]
+    public string Message { get; set; }
 
 }

+ 9 - 12
frameworks/CSharp/genhttp/Benchmarks/Model/World.cs

@@ -1,18 +1,15 @@
 using System.ComponentModel.DataAnnotations.Schema;
 
-namespace Benchmarks.Model
-{
-
-    [Table("world")]
-    public class World
-    {
+namespace Benchmarks.Model;
 
-        [Column("id")]
-        public int Id { get; set; }
+[Table("world")]
+public class World
+{
 
-        [Column("randomnumber")]
-        public int RandomNumber { get; set; }
+    [Column("id")]
+    public int Id { get; set; }
 
-    }
+    [Column("randomnumber")]
+    public int RandomNumber { get; set; }
 
-}
+}

+ 16 - 31
frameworks/CSharp/genhttp/Benchmarks/Program.cs

@@ -1,35 +1,20 @@
-using GenHTTP.Engine;
-
+using Benchmarks.Tests;
+using Benchmarks.Utilities;
+using GenHTTP.Engine.Kestrel;
 using GenHTTP.Modules.IO;
 using GenHTTP.Modules.Layouting;
 using GenHTTP.Modules.Webservices;
 
-using Benchmarks.Tests;
-using Benchmarks.Utilities;
-
-namespace Benchmarks
-{
-
-    public static class Program
-    {
-
-        public static int Main(string[] args)
-        { 
-            var tests = Layout.Create()
-                              .Add("plaintext", Content.From(Resource.FromString("Hello, World!")))
-                              .Add("fortunes", new FortuneHandlerBuilder())
-                              .AddService<JsonResource>("json")
-                              .AddService<DbResource>("db")
-                              .AddService<QueryResource>("queries")
-                              .AddService<UpdateResource>("updates")
-                              .AddService<CacheResource>("cached-worlds")
-                              .Add(ServerHeader.Create());
-
-            return Host.Create()
-                       .Handler(tests)
-                       .Run();
-        }
-
-    }
-
-}
+var tests = Layout.Create()
+                  .Add("plaintext", Content.From(Resource.FromString("Hello, World!")))
+                  .Add("fortunes", new FortuneHandler())
+                  .AddService<JsonResource>("json")
+                  .AddService<DbResource>("db")
+                  .AddService<QueryResource>("queries")
+                  .AddService<UpdateResource>("updates")
+                  .AddService<CacheResource>("cached-worlds")
+                  .Add(ServerHeader.Create());
+
+return await Host.Create()
+                 .Handler(tests)
+                 .RunAsync();

+ 0 - 7
frameworks/CSharp/genhttp/Benchmarks/Resources/Fortunes.html

@@ -1,7 +0,0 @@
-<table>
-    <tr><th>id</th><th>message</th></tr>
-    @foreach (var cookie in Model.Cookies)
-    {
-    <tr><td>@cookie.ID</td><td>@HttpUtility.HtmlEncode(cookie.Message)</td></tr>
-    }
-</table>

+ 16 - 3
frameworks/CSharp/genhttp/Benchmarks/Resources/Template.html

@@ -1,9 +1,22 @@
 <!DOCTYPE html>
 <html>
 <head>
-    <title>@Model.Meta.Title</title>
+    <title>Fortunes</title>
 </head>
 <body>
-    @Model.Content
+
+<table>
+    <tr>
+        <th>id</th>
+        <th>message</th>
+    </tr>
+    {for cookie in cookies:
+    <tr>
+        <td>{ cookie.id }</td>
+        <td>{ cookie.message }</td>
+    </tr>
+    }
+</table>
+
 </body>
-</html>
+</html>

+ 54 - 54
frameworks/CSharp/genhttp/Benchmarks/Tests/CacheResource.cs

@@ -1,84 +1,84 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
+using Benchmarks.Model;
+using GenHTTP.Modules.Webservices;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Caching.Memory;
 
-using Benchmarks.Model;
-
-using GenHTTP.Modules.Webservices;
+namespace Benchmarks.Tests;
 
-namespace Benchmarks.Tests
+public sealed class CacheResource
 {
+    private static readonly Random Random = new();
 
-    public sealed class CacheResource
+    private static readonly MemoryCache Cache = new(new MemoryCacheOptions
     {
-        private static readonly Random _Random = new Random();
-
-        private static readonly MemoryCache _Cache = new MemoryCache(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromMinutes(60) });
-
-        private static readonly object[] _CacheKeys = Enumerable.Range(0, 10001).Select((i) => new CacheKey(i)).ToArray();
-
-        public sealed class CacheKey : IEquatable<CacheKey>
-        {
-            private readonly int _value;
-
-            public CacheKey(int value) => _value = value;
+        ExpirationScanFrequency = TimeSpan.FromMinutes(60)
+    });
 
-            public bool Equals(CacheKey key) => key._value == _value;
+    private static readonly object[] CacheKeys = Enumerable.Range(0, 10001).Select(i => new CacheKey(i)).ToArray();
 
-            public override bool Equals(object obj) => ReferenceEquals(obj, this);
+    [ResourceMethod(":queries")]
+    public ValueTask<List<World>> GetWorldsFromPath(string queries) => GetWorlds(queries);
 
-            public override int GetHashCode() => _value;
+    [ResourceMethod]
+    public async ValueTask<List<World>> GetWorlds(string queries)
+    {
+        var count = 1;
 
-            public override string ToString() => _value.ToString();
+        int.TryParse(queries, out count);
 
+        if (count < 1)
+        {
+            count = 1;
         }
+        else if (count > 500)
+        {
+            count = 500;
+        }
+
+        var result = new List<World>(count);
 
-        [ResourceMethod(":queries")]
-        public ValueTask<List<World>> GetWorldsFromPath(string queries) => GetWorlds(queries);
+        await using var context = DatabaseContext.CreateNoTracking();
 
-        [ResourceMethod]
-        public async ValueTask<List<World>> GetWorlds(string queries)
+        for (var i = 0; i < count; i++)
         {
-            var count = 1;
+            var id = Random.Next(1, 10001);
 
-            int.TryParse(queries, out count);
+            var key = CacheKeys[id];
 
-            if (count < 1) count = 1;
-            else if (count > 500) count = 500;
+            var data = Cache.Get<World>(key);
 
-            var result = new List<World>(count);
+            if (data != null)
+            {
+                result.Add(data);
+            }
+            else
+            {
+                var resolved = await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
 
-            using var context = DatabaseContext.CreateNoTracking();
+                Cache.Set(key, resolved);
 
-            for (var i = 0; i < count; i++)
-            {
-                var id = _Random.Next(1, 10001);
+                result.Add(resolved);
+            }
+        }
 
-                var key = _CacheKeys[id];
+        return result;
+    }
 
-                var data = _Cache.Get<World>(key);
+    public sealed class CacheKey : IEquatable<CacheKey>
+    {
+        private readonly int _value;
 
-                if (data != null)
-                {
-                    result.Add(data);
-                }
-                else
-                {
-                    var resolved = await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
+        public CacheKey(int value)
+        {
+            _value = value;
+        }
 
-                    _Cache.Set(key, resolved);
+        public bool Equals(CacheKey key) => key._value == _value;
 
-                    result.Add(resolved);
-                }
-            }
+        public override bool Equals(object obj) => ReferenceEquals(obj, this);
 
-            return result;
-        }
+        public override int GetHashCode() => _value;
 
+        public override string ToString() => _value.ToString();
     }
-
 }

+ 10 - 18
frameworks/CSharp/genhttp/Benchmarks/Tests/DbResource.cs

@@ -1,29 +1,21 @@
-using System;
-using System.Threading.Tasks;
-
-using Microsoft.EntityFrameworkCore;
-
+using Benchmarks.Model;
 using GenHTTP.Modules.Webservices;
+using Microsoft.EntityFrameworkCore;
 
-using Benchmarks.Model;
+namespace Benchmarks.Tests;
 
-namespace Benchmarks.Tests
+public sealed class DbResource
 {
+    private static readonly Random Random = new();
 
-    public sealed class DbResource
+    [ResourceMethod]
+    public async ValueTask<World> GetRandomWorld()
     {
-        private static readonly Random _Random = new();
-
-        [ResourceMethod]
-        public async ValueTask<World> GetRandomWorld()
-        {
-            var id = _Random.Next(1, 10001);
-
-            using var context = DatabaseContext.CreateNoTracking();
+        var id = Random.Next(1, 10001);
 
-            return await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
-        }
+        await using var context = DatabaseContext.CreateNoTracking();
 
+        return await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
     }
 
 }

+ 48 - 79
frameworks/CSharp/genhttp/Benchmarks/Tests/FortuneHandler.cs

@@ -1,116 +1,85 @@
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using System.Linq;
-using System.Web;
-using System.IO;
-
-using Microsoft.EntityFrameworkCore;
-
+using System.Web;
+using Benchmarks.Model;
+using Cottle;
 using GenHTTP.Api.Content;
-using GenHTTP.Api.Content.Templating;
 using GenHTTP.Api.Protocol;
 
 using GenHTTP.Modules.IO;
-using GenHTTP.Modules.Razor;
-
-using Benchmarks.Model;
+using GenHTTP.Modules.Pages;
+using GenHTTP.Modules.Pages.Rendering;
 
-namespace Benchmarks.Tests
-{
+using Microsoft.EntityFrameworkCore;
 
-    #region Factory
+namespace Benchmarks.Tests;
 
-    public class FortuneHandlerBuilder : IHandlerBuilder
-    {
+public class FortuneHandler : IHandler
+{
 
-        public IHandler Build(IHandler parent)
-        {
-            return new FortuneHandler(parent);
-        }
+    #region Get-/Setters
 
-    }
+    private TemplateRenderer Template { get; }
 
     #endregion
 
-    #region Supporting data structures
+    #region Initialization
 
-    public sealed class FortuneModel : BasicModel
+    public FortuneHandler()
     {
+        var resource = Resource.FromAssembly("Template.html").Build();
 
-        public List<Fortune> Cookies { get; }
-
-        public FortuneModel(IRequest request, IHandler handler, List<Fortune> cookies) : base(request, handler)
-        {
-            Cookies = cookies;
-        }
-
+        Template = Renderer.From(resource);
     }
 
     #endregion
 
-    public class FortuneHandler : IHandler, IPageRenderer
-    {
-
-        #region Get-/Setters
-
-        public IHandler Parent { get; }
+    #region Functionality
 
-        private IHandler Page { get; }
+    public ValueTask PrepareAsync() => new();
 
-        private IRenderer<TemplateModel> Template { get; }
-
-        #endregion
-
-        #region Initialization
-
-        public FortuneHandler(IHandler parent)
+    public async ValueTask<IResponse> HandleAsync(IRequest request)
+    {
+        var data = new Dictionary<Value, Value>
         {
-            Parent = parent;
+            ["cookies"] = Value.FromEnumerable(await GetFortunes())
+        };
 
-            Page = ModRazor.Page(Resource.FromAssembly("Fortunes.html"), (r, h) => GetFortunes(r, h))
-                           .Title("Fortunes")
-                           .AddAssemblyReference<HttpUtility>()
-                           .AddUsing("System.Web")
-                           .Build(this);
+        return request.GetPage(await Template.RenderAsync(data)).Build();
+    }
 
-            Template = ModRazor.Template<TemplateModel>(Resource.FromAssembly("Template.html")).Build();
-        }
+    private static async ValueTask<List<Value>> GetFortunes()
+    {
+        await using var context = DatabaseContext.CreateNoTracking();
 
-        #endregion
+        var fortunes = await context.Fortune.ToListAsync().ConfigureAwait(false);
 
-        #region Functionality
+        var result = new List<Value>(fortunes.Count + 1);
 
-        public async ValueTask PrepareAsync()
+        foreach (var fortune in fortunes)
         {
-            await Page.PrepareAsync();
-            await Template.PrepareAsync();
+            result.Add(Value.FromDictionary(new Dictionary<Value, Value>()
+            {
+                ["id"] = fortune.Id,
+                ["message"] = HttpUtility.HtmlEncode(fortune.Message)
+            }));
         }
 
-        public ValueTask<ulong> CalculateChecksumAsync() => new(17);
-
-        public IAsyncEnumerable<ContentElement> GetContentAsync(IRequest request) => AsyncEnumerable.Empty<ContentElement>();
-
-        public ValueTask<string> RenderAsync(TemplateModel model) => Template.RenderAsync(model);
-
-        public ValueTask RenderAsync(TemplateModel model, Stream target) => Template.RenderAsync(model, target);
-
-        public ValueTask<IResponse> HandleAsync(IRequest request) => Page.HandleAsync(request);
-
-        private static async ValueTask<FortuneModel> GetFortunes(IRequest request, IHandler handler)
+        result.Add(Value.FromDictionary(new Dictionary<Value, Value>()
         {
-            using var context = DatabaseContext.CreateNoTracking();
-
-            var fortunes = await context.Fortune.ToListAsync().ConfigureAwait(false);
-
-            fortunes.Add(new Fortune() { Message = "Additional fortune added at request time." });
-
-            fortunes.Sort();
+            ["id"] = 0,
+            ["message"] = "Additional fortune added at request time."
+        }));
 
-            return new FortuneModel(request, handler, fortunes);
-        }
+        result.Sort((one, two) =>
+        {
+            var firstMessage = one.Fields["message"].AsString;
+            var secondMessage = two.Fields["message"].AsString;
 
-        #endregion
+            return string.Compare(firstMessage, secondMessage, StringComparison.Ordinal);
+        });
 
+        return result;
     }
 
+    #endregion
+
 }

+ 11 - 12
frameworks/CSharp/genhttp/Benchmarks/Tests/JsonResource.cs

@@ -1,21 +1,20 @@
 using GenHTTP.Modules.Webservices;
 
-namespace Benchmarks.Tests
-{
+namespace Benchmarks.Tests;
 
-    public sealed class JsonResult
-    {
+public sealed class JsonResult
+{
 
-        public string Message { get; set; }
+    public string Message { get; set; }
+}
 
-    }
+public sealed class JsonResource
+{
 
-    public sealed class JsonResource
+    [ResourceMethod]
+    public JsonResult GetMessage() => new()
     {
-
-        [ResourceMethod]
-        public JsonResult GetMessage() => new() { Message = "Hello, World!" };
-
-    }
+        Message = "Hello, World!"
+    };
 
 }

+ 27 - 30
frameworks/CSharp/genhttp/Benchmarks/Tests/QueryResource.cs

@@ -1,47 +1,44 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
+using Benchmarks.Model;
+using GenHTTP.Modules.Webservices;
 using Microsoft.EntityFrameworkCore;
 
-using Benchmarks.Model;
-
-using GenHTTP.Modules.Webservices;
+namespace Benchmarks.Tests;
 
-namespace Benchmarks.Tests
+public sealed class QueryResource
 {
+    private static readonly Random Random = new();
+
+    [ResourceMethod(":queries")]
+    public ValueTask<List<World>> GetWorldsFromPath(string queries) => GetWorlds(queries);
 
-    public sealed class QueryResource
+    [ResourceMethod]
+    public async ValueTask<List<World>> GetWorlds(string queries)
     {
-        private static readonly Random _Random = new Random();
+        var count = 1;
 
-        [ResourceMethod(":queries")]
-        public ValueTask<List<World>> GetWorldsFromPath(string queries) => GetWorlds(queries);
+        int.TryParse(queries, out count);
 
-        [ResourceMethod]
-        public async ValueTask<List<World>> GetWorlds(string queries)
+        if (count < 1)
         {
-            var count = 1;
-
-            int.TryParse(queries, out count);
-
-            if (count < 1) count = 1;
-            else if (count > 500) count = 500;
-
-            var result = new List<World>(count);
+            count = 1;
+        }
+        else if (count > 500)
+        {
+            count = 500;
+        }
 
-            using var context = DatabaseContext.CreateNoTracking();
+        var result = new List<World>(count);
 
-            for (int _ = 0; _ < count; _++)
-            {
-                var id = _Random.Next(1, 10001);
+        using var context = DatabaseContext.CreateNoTracking();
 
-                result.Add(await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false));
-            }
+        for (var _ = 0; _ < count; _++)
+        {
+            var id = Random.Next(1, 10001);
 
-            return result;
+            result.Add(await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false));
         }
 
+        return result;
     }
 
-}
+}

+ 39 - 41
frameworks/CSharp/genhttp/Benchmarks/Tests/UpdateResource.cs

@@ -1,66 +1,64 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
+using Benchmarks.Model;
+using GenHTTP.Modules.Webservices;
 using Microsoft.EntityFrameworkCore;
 
-using Benchmarks.Model;
+namespace Benchmarks.Tests;
 
-using GenHTTP.Modules.Webservices;
-
-namespace Benchmarks.Tests
+public sealed class UpdateResource
 {
+    private static readonly Random Random = new();
+
+    [ResourceMethod(":queries")]
+    public ValueTask<List<World>> UpdateWorldsFromPath(string queries) => UpdateWorlds(queries);
 
-    public sealed class UpdateResource
+    [ResourceMethod]
+    public async ValueTask<List<World>> UpdateWorlds(string queries)
     {
-        private static Random _Random = new Random();
+        var count = 1;
 
-        [ResourceMethod(":queries")]
-        public ValueTask<List<World>> UpdateWorldsFromPath(string queries) => UpdateWorlds(queries);
+        int.TryParse(queries, out count);
 
-        [ResourceMethod]
-        public async ValueTask<List<World>> UpdateWorlds(string queries)
+        if (count < 1)
         {
-            var count = 1;
-
-            int.TryParse(queries, out count);
-
-            if (count < 1) count = 1;
-            else if (count > 500) count = 500;
+            count = 1;
+        }
+        else if (count > 500)
+        {
+            count = 500;
+        }
 
-            var result = new List<World>(count);
+        var result = new List<World>(count);
 
-            var ids = Enumerable.Range(1, 10000).Select(x => _Random.Next(1, 10001)).Distinct().Take(count).ToArray();
+        var ids = Enumerable.Range(1, 10000).Select(x => Random.Next(1, 10001)).Distinct().Take(count).ToArray();
 
-            using (var context = DatabaseContext.Create())
+        using (var context = DatabaseContext.Create())
+        {
+            foreach (var id in ids)
             {
-                foreach (var id in ids)
-                {
-                    var record = await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
+                var record = await context.World.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
 
-                    var old = record.RandomNumber;
+                var old = record.RandomNumber;
 
-                    var current = old;
+                var current = old;
 
-                    for (int i = 0; i < 5; i++)
-                    {
-                        current = _Random.Next(1, 10001);
+                for (var i = 0; i < 5; i++)
+                {
+                    current = Random.Next(1, 10001);
 
-                        if (current != old) break;
+                    if (current != old)
+                    {
+                        break;
                     }
+                }
 
-                    record.RandomNumber = current;
+                record.RandomNumber = current;
 
-                    result.Add(record);
+                result.Add(record);
 
-                    await context.SaveChangesAsync();
-                }
+                await context.SaveChangesAsync();
             }
-
-            return result;
         }
 
+        return result;
     }
-
-}
+}

+ 4 - 8
frameworks/CSharp/genhttp/Benchmarks/Utilities/ServerHeader.cs

@@ -1,11 +1,7 @@
-namespace Benchmarks.Utilities
-{
-
-    public static class ServerHeader
-    {
+namespace Benchmarks.Utilities;
 
-        public static ServerHeaderConcernBuilder Create() => new ServerHeaderConcernBuilder();
-
-    }
+public static class ServerHeader
+{
 
+    public static ServerHeaderConcernBuilder Create() => new();
 }

+ 23 - 36
frameworks/CSharp/genhttp/Benchmarks/Utilities/ServerHeaderConcern.cs

@@ -1,55 +1,42 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-using GenHTTP.Api.Content;
+using GenHTTP.Api.Content;
 using GenHTTP.Api.Protocol;
 
-namespace Benchmarks.Utilities
-{
-
-    public sealed class ServerHeaderConcern : IConcern
-    {
+namespace Benchmarks.Utilities;
 
-        #region Get-/Setters
+public sealed class ServerHeaderConcern : IConcern
+{
 
-        public IHandler Content { get; }
+    #region Initialization
 
-        public IHandler Parent { get; }
+    public ServerHeaderConcern(IHandler content)
+    {
+        Content = content;
+    }
 
-        #endregion
+    #endregion
 
-        #region Initialization
+    #region Get-/Setters
 
-        public ServerHeaderConcern(IHandler parent, Func<IHandler, IHandler> contentFactory)
-        {
-            Parent = parent;
-            Content = contentFactory(this);
-        }
+    public IHandler Content { get; }
 
-        #endregion
+    #endregion
 
-        #region Functionality
+    #region Functionality
 
-        public IAsyncEnumerable<ContentElement> GetContentAsync(IRequest request) => Content.GetContentAsync(request);
+    public ValueTask PrepareAsync() => Content.PrepareAsync();
 
-        public ValueTask PrepareAsync() => Content.PrepareAsync();
+    public async ValueTask<IResponse> HandleAsync(IRequest request)
+    {
+        var response = await Content.HandleAsync(request);
 
-        public async ValueTask<IResponse> HandleAsync(IRequest request)
+        if (response != null)
         {
-            var response = await Content.HandleAsync(request);
-
-            if (response != null)
-            {
-                response.Headers.Add("Server", "TFB");
-            }
-
-            return response;
+            response.Headers.Add("Server", "TFB");
         }
 
-        #endregion
-
+        return response;
     }
 
+    #endregion
+
 }

+ 4 - 12
frameworks/CSharp/genhttp/Benchmarks/Utilities/ServerHeaderConcernBuilder.cs

@@ -1,18 +1,10 @@
-using System;
+using GenHTTP.Api.Content;
 
-using GenHTTP.Api.Content;
+namespace Benchmarks.Utilities;
 
-namespace Benchmarks.Utilities
+public sealed class ServerHeaderConcernBuilder : IConcernBuilder
 {
 
-    public sealed class ServerHeaderConcernBuilder : IConcernBuilder
-    {
-
-        public IConcern Build(IHandler parent, Func<IHandler, IHandler> contentFactory)
-        {
-            return new ServerHeaderConcern(parent, contentFactory);
-        }
-
-    }
+    public IConcern Build(IHandler content) => new ServerHeaderConcern(content);
 
 }

+ 2 - 2
frameworks/CSharp/genhttp/README.md

@@ -6,11 +6,11 @@ See the [GenHTTP website](https://genhttp.org) for more information.
 
 **Language**
 
-* C# 8.0
+* C# 13.0
 
 **Platforms**
 
-* .NET Core
+* .NET 8/9
 
 **Web Servers**
 

+ 1 - 1
frameworks/CSharp/genhttp/benchmark_config.json

@@ -17,7 +17,7 @@
       "language": "C#",
       "orm": "Raw",
       "platform": ".NET",
-      "webserver": "GenHTTP",
+      "webserver": "Kestrel",
       "os": "Linux",
       "database_os": "Linux",
       "display_name": "GenHTTP",

+ 1 - 1
frameworks/CSharp/genhttp/config.toml

@@ -16,5 +16,5 @@ database_os = "Linux"
 os = "Linux"
 orm = "Raw"
 platform = ".NET"
-webserver = "GenHTTP"
+webserver = "Kestrel"
 versus = "None"

+ 13 - 13
frameworks/CSharp/genhttp/genhttp.dockerfile

@@ -1,19 +1,19 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
-WORKDIR /app
-COPY Benchmarks .
-RUN dotnet publish -c Release -o out
+FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
+WORKDIR /source
 
-FROM mcr.microsoft.com/dotnet/runtime:8.0 AS runtime
-ENV DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS 1
+# copy csproj and restore as distinct layers
+COPY Benchmarks/*.csproj .
+RUN dotnet restore -r linux-musl-x64
 
-# Full PGO
-ENV DOTNET_TieredPGO 1 
-ENV DOTNET_TC_QuickJitForLoops 1 
-ENV DOTNET_ReadyToRun 0
+# copy and publish app and libraries
+COPY Benchmarks/ .
+RUN dotnet publish -c release -o /app -r linux-musl-x64 --no-restore --self-contained
 
+# final stage/image
+FROM mcr.microsoft.com/dotnet/runtime-deps:9.0-alpine
 WORKDIR /app
-COPY --from=build /app/out ./
+COPY --from=build /app .
 
-EXPOSE 8080
+ENTRYPOINT ["./Benchmarks"]
 
-ENTRYPOINT ["dotnet", "Benchmarks.dll"]
+EXPOSE 8080

+ 2 - 2
frameworks/CSharp/reaper/reaper.dockerfile

@@ -1,4 +1,4 @@
-FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
 WORKDIR /src
 COPY src .
 RUN apt-get update \
@@ -7,7 +7,7 @@ RUN apt-get update \
 WORKDIR "/src/Benchmark"
 RUN dotnet publish "Benchmark.csproj" -c Release -o /app/publish
 
-FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
+FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
 WORKDIR /app
 EXPOSE 8080
 COPY --from=build /app/publish .

+ 3 - 3
frameworks/CSharp/reaper/src/Benchmark/Benchmark.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
 
     <PropertyGroup>
-        <TargetFramework>net8.0</TargetFramework>
+        <TargetFramework>net9.0</TargetFramework>
         <Nullable>enable</Nullable>
         <ImplicitUsings>enable</ImplicitUsings>
         <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
@@ -18,8 +18,8 @@
     </ItemGroup>
     
     <ItemGroup>
-        <PackageReference Include="Reaper.Core" Version="0.1.0-alpha.0.6" />
-        <PackageReference Include="Reaper.SourceGenerator" Version="0.1.0-alpha.0.6" />
+        <PackageReference Include="Reaper.Core" Version="0.1.0-alpha.0.43" />
+        <PackageReference Include="Reaper.SourceGenerator" Version="0.1.0-alpha.0.43" />
     </ItemGroup>
 
 </Project>

+ 3 - 2
frameworks/CSharp/reaper/src/Benchmark/JsonEndpoint.cs

@@ -11,9 +11,10 @@ public class JsonResponse
 [ReaperRoute(HttpVerbs.Get, "/json")]
 public class JsonEndpoint : ReaperEndpointXR<JsonResponse>
 {
-    public override Task<JsonResponse> HandleAsync()
+    public override Task ExecuteAsync()
     {
         Context.Response.ContentLength = 27;
-        return Task.FromResult(new JsonResponse { Message = "Hello, World!" });
+        Result = new JsonResponse { Message = "Hello, World!" };
+        return Task.CompletedTask;
     }
 }

+ 3 - 2
frameworks/CSharp/reaper/src/Benchmark/PlainTextEndpoint.cs

@@ -6,11 +6,12 @@ namespace Benchmark;
 [ReaperRoute(HttpVerbs.Get, "/plaintext")]
 public class PlainTextEndpoint : ReaperEndpointXR<string>
 {
-    public override Task<string> HandleAsync()
+    public override Task ExecuteAsync()
     {
         Context.Response.StatusCode = 200;
         Context.Response.ContentType = "text/plain";
         Context.Response.ContentLength = 13;
-        return Task.FromResult("Hello, World!");
+        Result = "Hello, World!";
+        return Task.CompletedTask;
     }
 }

+ 1 - 10
frameworks/CSharp/reaper/src/Benchmark/Program.cs

@@ -1,14 +1,8 @@
-using System.Text.Json.Serialization;
-using Benchmark;
 using Reaper;
 
 var builder = WebApplication.CreateSlimBuilder(args);
 builder.Logging.ClearProviders();
 builder.Logging.Configure(o => o.ActivityTrackingOptions = ActivityTrackingOptions.None);
-builder.Services.ConfigureHttpJsonOptions(o =>
-{
-    o.SerializerOptions.TypeInfoResolverChain.Insert(0, SourceGenerationContext.Default);
-});
 builder.UseReaper();
 
 var app = builder.Build();
@@ -16,7 +10,4 @@ var app = builder.Build();
 app.UseReaperMiddleware();
 app.MapReaperEndpoints();
 
-app.Run();
-
-[JsonSerializable(typeof(JsonResponse))]
-internal partial class SourceGenerationContext : JsonSerializerContext { }
+app.Run();

+ 1 - 1
frameworks/CSharp/reaper/src/Benchmark/appsettings.Development.json

@@ -5,4 +5,4 @@
       "Microsoft.AspNetCore": "Warning"
     }
   }
-}
+}

+ 5 - 5
frameworks/FSharp/oxpecker/src/App/App.fsproj

@@ -13,10 +13,10 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Update="FSharp.Core" Version="8.0.400" />
-    <PackageReference Include="Oxpecker" Version="0.14.1" />
-    <PackageReference Include="Oxpecker.ViewEngine" Version="0.14.0" />
-    <PackageReference Include="Npgsql" Version="8.0.3" />
-    <PackageReference Include="SpanJson" Version="4.2.0" />
+    <PackageReference Update="FSharp.Core" Version="9.0.100" />
+    <PackageReference Include="Oxpecker" Version="1.0.0" />
+    <PackageReference Include="Oxpecker.ViewEngine" Version="1.0.0" />
+    <PackageReference Include="Npgsql" Version="9.0.1" />
+    <PackageReference Include="SpanJson" Version="4.2.1" />
   </ItemGroup>
 </Project>

+ 4 - 2
frameworks/FSharp/oxpecker/src/App/Common.fs

@@ -9,7 +9,7 @@ module Common =
     [<Struct>]
     [<CLIMutable>]
     type JsonMessage = {
-        message : string
+        message: string
     }
 
     [<CLIMutable>]
@@ -26,7 +26,9 @@ module Common =
     }
 
     [<Literal>]
-    let ConnectionString = "Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;Maximum Pool Size=1024;NoResetOnClose=true;Enlist=false;Max Auto Prepare=3"
+    let ConnectionString = "Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;Maximum Pool Size=1024;NoResetOnClose=true;Enlist=false"
+    [<Literal>]
+    let MultiplexedConnectionString = ConnectionString + ";Max Auto Prepare=3;Multiplexing=true"
 
     let FortuneComparer = {
         new IComparer<Fortune> with

+ 18 - 13
frameworks/FSharp/oxpecker/src/App/Db.fs

@@ -2,8 +2,7 @@ namespace App
 
 open System
 open System.Data
-open System.Data.Common
-open System.Text
+open Microsoft.Extensions.ObjectPool
 open Npgsql
 
 
@@ -15,6 +14,7 @@ module Db =
             use db = new NpgsqlConnection(ConnectionString)
             use cmd = db.CreateCommand(CommandText = "SELECT id, message FROM fortune")
             do! db.OpenAsync()
+            do! cmd.PrepareAsync()
             use! rdr = cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)
             while! rdr.ReadAsync() do
                 result.Add { id = rdr.GetInt32(0); message = rdr.GetString(1) }
@@ -31,7 +31,7 @@ module Db =
             TypedValue = Random.Shared.Next(1, 10001)
         )
         cmd.Parameters.Add(id) |> ignore
-        cmd
+        struct(cmd, id)
 
     let private readSingleRow (cmd: NpgsqlCommand) =
         task {
@@ -43,41 +43,46 @@ module Db =
     let loadSingleRow () =
         task {
             use db = new NpgsqlConnection(ConnectionString)
+            let struct(cmd', _) = createReadCommand db
+            use cmd = cmd'
             do! db.OpenAsync()
-            use cmd = createReadCommand db
+            do! cmd.PrepareAsync()
             return! readSingleRow cmd
         }
 
     let private readMultipleRows (count: int) (conn: NpgsqlConnection) =
         let result = Array.zeroCreate count
         task {
-            use cmd = createReadCommand conn
+            let struct(cmd', idParam) = createReadCommand conn
+            use cmd = cmd'
             for i in 0..result.Length-1 do
-                cmd.Parameters["@Id"].Value <- Random.Shared.Next(1, 10001)
                 let! row = readSingleRow cmd
                 result[i] <- row
+                idParam.TypedValue <- Random.Shared.Next(1, 10001)
             return result
         }
 
     let loadMultipleRows (count: int) =
         task {
-            use db = new NpgsqlConnection(ConnectionString)
+            use db = new NpgsqlConnection(MultiplexedConnectionString)
             do! db.OpenAsync()
             return! readMultipleRows count db
         }
 
     let private maxBatch = 500
     let private queries = Array.zeroCreate (maxBatch + 1)
+    let private stringBuilderPool = DefaultObjectPoolProvider().CreateStringBuilderPool()
     let private batchUpdateString batchSize =
         match queries[batchSize] with
         | null ->
             let lastIndex = batchSize - 1
-            let sb = StringBuilder()
+            let sb = stringBuilderPool.Get()
             sb.Append("UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ") |> ignore
             for i in 0..lastIndex-1 do
                 sb.AppendFormat("(@Id_{0}, @Rn_{0}), ", i) |> ignore
             sb.AppendFormat("(@Id_{0}, @Rn_{0}) ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id", lastIndex) |> ignore
             let result = sb.ToString()
+            stringBuilderPool.Return(sb)
             queries[batchSize] <- result
             result
         | q ->
@@ -95,15 +100,15 @@ module Db =
         for i in 0..results.Length-1 do
             let randomNumber = Random.Shared.Next(1, 10001)
             let struct(rnParamName, idParamName) = paramNames[i]
-            let random = NpgsqlParameter<int>(ParameterName = rnParamName, DbType = DbType.Int32, TypedValue = randomNumber)
-            command.Parameters.Add(random) |> ignore
-            let id = NpgsqlParameter<int>(ParameterName = idParamName, DbType = DbType.Int32, TypedValue = results[i].id)
-            command.Parameters.Add(id) |> ignore
+            command.Parameters.Add(NpgsqlParameter<int>(
+                ParameterName = rnParamName, DbType = DbType.Int32, TypedValue = randomNumber)) |> ignore
+            command.Parameters.Add(NpgsqlParameter<int>(
+                ParameterName = idParamName, DbType = DbType.Int32, TypedValue = results[i].id)) |> ignore
             results[i] <- { results[i] with randomnumber = randomNumber }
 
     let doMultipleUpdates (count: int) =
         task {
-            use conn = new NpgsqlConnection(ConnectionString)
+            use conn = new NpgsqlConnection(MultiplexedConnectionString)
             do! conn.OpenAsync()
             let! results = readMultipleRows count conn
             use cmd = conn.CreateCommand(CommandText = batchUpdateString count)

+ 13 - 26
frameworks/FSharp/oxpecker/src/App/Program.fs

@@ -2,13 +2,21 @@ namespace App
 
 open System
 open Oxpecker
-open System.Runtime.InteropServices
 
 [<RequireQualifiedAccess>]
-module HtmlViews =
+module HttpHandlers =
+    open System.Text
+    open Microsoft.AspNetCore.Http
+    open SpanJson
     open Oxpecker.ViewEngine
 
-    let private head, tail =
+    let private extra =
+        {
+            id = 0
+            message = "Additional fortune added at request time."
+        }
+
+    let private fortunesHeadAndTail =
         (fun (content: HtmlElement) ->
             html() {
                 head() {
@@ -26,32 +34,11 @@ module HtmlViews =
             } :> HtmlElement
         ) |> RenderHelpers.prerender
 
-    let fortunes (fortunesData: ResizeArray<Fortune>) =
-        let fragment = __()
-        for fortune in CollectionsMarshal.AsSpan fortunesData do
-            tr() {
-                td() { raw <| string fortune.id }
-                td() { fortune.message }
-            }
-            |> fragment.AddChild
-        RenderHelpers.combine head tail fragment
-
-[<RequireQualifiedAccess>]
-module HttpHandlers =
-    open System.Text
-    open Microsoft.AspNetCore.Http
-    open SpanJson
-
-    let private extra =
-        {
-            id      = 0
-            message = "Additional fortune added at request time."
-        }
-
     let rec private renderFortunes (ctx: HttpContext) (data: ResizeArray<Fortune>) =
         data.Add extra
         data.Sort FortuneComparer
-        data |> HtmlViews.fortunes |> ctx.WriteHtmlViewChunked
+        RenderHelpers.CombinedElement(fortunesHeadAndTail, data)
+        |> ctx.WriteHtmlViewChunked
 
     let fortunes : EndpointHandler =
         fun ctx ->

+ 20 - 9
frameworks/FSharp/oxpecker/src/App/RenderHelpers.fs

@@ -1,8 +1,27 @@
 module RenderHelpers
 
     open System.Text
+    open App
     open Oxpecker.ViewEngine
 
+    type HeadAndTail =
+        {
+            Head: string
+            Tail: string
+        }
+
+    [<Struct>]
+    type CombinedElement(ht: HeadAndTail, fortunesData: ResizeArray<Fortune>) =
+        interface HtmlElement with
+            member this.Render(sb) =
+                sb.Append(ht.Head) |> ignore
+                for fortune in fortunesData do
+                    (tr() {
+                        td() { raw <| string fortune.id }
+                        td() { fortune.message }
+                    }).Render(sb)
+                sb.Append(ht.Tail) |> ignore
+
     let prerender (view: HtmlElement -> HtmlElement) =
         let sb = StringBuilder()
         let mutable head = ""
@@ -13,12 +32,4 @@
                  sb.Clear() |> ignore }
         let readyView = view fakeHole
         readyView.Render(sb)
-        (head, sb.ToString())
-
-    let inline combine (head: string) (tail: string) (hole: HtmlElement) =
-        { new HtmlElement with
-            member this.Render(sb) =
-                sb.Append(head) |> ignore
-                hole.Render(sb)
-                sb.Append(tail) |> ignore
-        }
+        { Head = head; Tail = sb.ToString() }

+ 11 - 0
frameworks/Go/pine/README.md

@@ -0,0 +1,11 @@
+# Pine Benchmarking Test
+
+## Test URLs
+
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext

+ 26 - 0
frameworks/Go/pine/benchmark_config.json

@@ -0,0 +1,26 @@
+{
+  "framework": "pine",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "None",
+        "framework": "Pine",
+        "language": "Go",
+        "flavor": "None",
+        "orm": "None",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Pine",
+        "notes": "",
+        "versus": "go"
+      }
+    }
+  ]
+}

+ 10 - 0
frameworks/Go/pine/pine.dockerfile

@@ -0,0 +1,10 @@
+FROM docker.io/golang:1.23
+
+COPY ./src /pine
+WORKDIR /pine
+
+RUN go mod download
+
+EXPOSE 8080
+
+CMD go run .

+ 5 - 0
frameworks/Go/pine/src/go.mod

@@ -0,0 +1,5 @@
+module pine
+
+go 1.23.0
+
+require github.com/BryanMwangi/pine v1.0.6

+ 2 - 0
frameworks/Go/pine/src/go.sum

@@ -0,0 +1,2 @@
+github.com/BryanMwangi/pine v1.0.6 h1:35JN1FQkStoCikeVQJ2423mO5STLNEPkA/AgnjslAmg=
+github.com/BryanMwangi/pine v1.0.6/go.mod h1:j6+gT+N2HeeJHc9Z60rUOnEmNC+s/Gdmh2e9oB/eScI=

+ 28 - 0
frameworks/Go/pine/src/main.go

@@ -0,0 +1,28 @@
+package main
+
+import (
+	"log"
+
+	"github.com/BryanMwangi/pine"
+)
+
+func plaintextHandler(c *pine.Ctx) error {
+	c.Set("Server", "Pine")
+	return c.SendString("Hello, World!")
+}
+
+func jsonHandler(c *pine.Ctx) error {
+	c.Set("Server", "Pine")
+	return c.JSON(map[string]string{
+		"message": "Hello, World!",
+	})
+}
+
+func main() {
+	app := pine.New()
+	app.Get("/plaintext", plaintextHandler)
+	app.Get("/json", jsonHandler)
+
+	// Start the server on port 3000
+	log.Fatal(app.Start(":8080"))
+}

+ 39 - 0
frameworks/Java/hserver-business/README.md

@@ -0,0 +1,39 @@
+# HServer Benchmarking Test
+This is the HServer portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### Tests
+* [JSON test source](src/main/java/com/test/hserver/controller/TestController.java)
+* [Plaintext test source](src/main/java/com/test/hserver/controller/TestController.java)
+* [Data-Store test source](src/main/java/com/test/hserver/controller/TestController.java)
+* [Data-Update test source](src/main/java/com/test/hserver/controller/TestController.java)
+* [Fortunes test source](src/main/java/com/test/hserver/controller/TestController.java)
+
+## Infrastructure Software Versions
+
+* [HServer](https://gitee.com/HServer/HServer)
+* [Java OpenJDK 1.8](http://openjdk.java.net/)
+
+## Test URLs
+
+### JSON Encoding Test
+
+http://localhost:8888/json 
+
+### Plain Text Test
+
+http://localhost:8888/plaintext
+
+### Data-Store/Database Mapping Test
+
+http://localhost:8888/db?queries=2
+
+### Update Test
+
+http://localhost:8888/updates?queries=2
+
+### Fortunes Test
+
+http://localhost:8888/fortunes
+
+### Query Test
+http://localhost:8888/queries?queries=2

+ 31 - 0
frameworks/Java/hserver-business/benchmark_config.json

@@ -0,0 +1,31 @@
+
+{
+  "framework": "hserver",
+  "tests": [
+    {
+      "default": {
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
+        "plaintext_url": "/plaintext",
+        "json_url": "/json",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "port": 8888,
+        "approach": "Realistic",
+        "classification": "Fullstack",
+        "database": "Postgres",
+        "framework": "hserver",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Full",
+        "platform": "hserver",
+        "webserver": "hserver",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "hserver-business",
+        "notes": "",
+        "versus": "hserver"
+      }
+    }
+  ]
+}

+ 17 - 0
frameworks/Java/hserver-business/config.toml

@@ -0,0 +1,17 @@
+[framework]
+name = "hserver-business"
+
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Fullstack"
+os = "Linux"
+orm = "Full"
+platform = "hserver"
+webserver = "hserver"
+versus = "hserver"

+ 13 - 0
frameworks/Java/hserver-business/hserver.dockerfile

@@ -0,0 +1,13 @@
+FROM maven:3.8.4-openjdk-17-slim as maven
+WORKDIR /hserver
+COPY pom.xml pom.xml
+COPY src src
+RUN mvn package
+
+FROM openjdk:17.0.2
+WORKDIR /hserver
+COPY --from=maven /hserver/target/hserver-1.0.jar app.jar
+
+EXPOSE 8888
+
+CMD ["java", "-jar", "app.jar"]

+ 52 - 0
frameworks/Java/hserver-business/pom.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.test.hserver</groupId>
+    <artifactId>hserver</artifactId>
+    <version>1.0</version>
+
+    <parent>
+        <artifactId>hserver-parent</artifactId>
+        <groupId>cn.hserver</groupId>
+        <version>3.6.0</version>
+    </parent>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <version.hikaricp>3.3.1</version.hikaricp>
+        <version.postgres>42.7.2</version.postgres>
+    </properties>
+
+    <dependencies>
+        <!--    核心依赖-->
+        <dependency>
+            <artifactId>hserver</artifactId>
+            <groupId>cn.hserver</groupId>
+        </dependency>
+        <!--    web框架 -->
+        <dependency>
+            <artifactId>hserver-plugin-web</artifactId>
+            <groupId>cn.hserver</groupId>
+        </dependency>
+        <dependency>
+            <groupId>com.zaxxer</groupId>
+            <artifactId>HikariCP</artifactId>
+            <version>${version.hikaricp}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>${version.postgres}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>hserver-plugin-maven</artifactId>
+                <groupId>cn.hserver</groupId>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 18 - 0
frameworks/Java/hserver-business/src/main/java/com/test/hserver/StartApp.java

@@ -0,0 +1,18 @@
+package com.test.hserver;
+
+
+import cn.hserver.HServerApplication;
+import cn.hserver.core.ioc.annotation.HServerBoot;
+import cn.hserver.core.server.context.ConstConfig;
+
+
+/**
+ * @author hxm
+ */
+@HServerBoot
+public class StartApp {
+
+    public static void main(String[] args) {
+        HServerApplication.run(StartApp.class, 8888, args);
+    }
+}

+ 24 - 0
frameworks/Java/hserver-business/src/main/java/com/test/hserver/bean/Fortune.java

@@ -0,0 +1,24 @@
+package com.test.hserver.bean;
+
+public final class Fortune implements Comparable<Fortune> {
+  public final int id;
+
+  public final String message;
+
+  public Fortune(int id, String message) {
+    this.id = id;
+    this.message = message;
+  }
+  @Override
+  public int compareTo(Fortune other) {
+    return message.compareTo(other.message);
+  }
+
+  public int getId() {
+    return id;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+}

+ 3 - 11
frameworks/Java/solon/src/main/java/hello/model/Message.java → frameworks/Java/hserver-business/src/main/java/com/test/hserver/bean/Message.java

@@ -1,15 +1,7 @@
-package hello.model;
+package com.test.hserver.bean;
 
-/**
- * @author pmg1991
- * @version V1.0
- */
 public class Message {
-    private String message;
-
-    public Message(String message) {
-        this.message = message;
-    }
+    private String message = "Hello, World!";
 
     public String getMessage() {
         return message;
@@ -18,4 +10,4 @@ public class Message {
     public void setMessage(String message) {
         this.message = message;
     }
-}
+}

+ 24 - 0
frameworks/Java/hserver-business/src/main/java/com/test/hserver/bean/World.java

@@ -0,0 +1,24 @@
+package com.test.hserver.bean;
+
+public class World implements Comparable<World> {
+  private final int id;
+
+  private final int randomNumber;
+
+  public World(int id, int randomNumber) {
+    this.id = id;
+    this.randomNumber = randomNumber;
+  }
+
+  public int getId() {
+    return id;
+  }
+
+  public int getRandomNumber() {
+    return randomNumber;
+  }
+
+  @Override public int compareTo(World o) {
+    return id - o.id;
+  }
+}

+ 136 - 0
frameworks/Java/hserver-business/src/main/java/com/test/hserver/controller/TestController.java

@@ -0,0 +1,136 @@
+package com.test.hserver.controller;
+
+import cn.hserver.core.ioc.annotation.Autowired;
+import cn.hserver.plugin.web.annotation.Controller;
+import cn.hserver.plugin.web.annotation.GET;
+import cn.hserver.plugin.web.interfaces.HttpRequest;
+import cn.hserver.plugin.web.interfaces.HttpResponse;
+import com.test.hserver.bean.Fortune;
+import com.test.hserver.bean.Message;
+import com.test.hserver.bean.World;
+import com.test.hserver.util.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.*;
+
+import static com.test.hserver.util.Util.getQueries;
+import static com.test.hserver.util.Util.randomWorld;
+
+/**
+ * @author hxm
+ */
+@Controller
+public class TestController {
+    private static final String HELLO = "Hello, World!";
+    private static final String SELECT_WORLD = "select * from world where id=?";
+
+    @Autowired
+    private DataSource dataSource;
+
+    @GET("/json")
+    public Message json(HttpResponse response) {
+        response.setHeader("Date", DateUtil.getTime());
+        return new Message();
+    }
+
+    @GET("/plaintext")
+    public String plaintext(HttpResponse response) {
+        response.setHeader("Date", DateUtil.getTime());
+        return HELLO;
+    }
+
+    @GET("/db")
+    public void db(HttpResponse response) throws SQLException {
+        World result;
+        try (Connection conn = dataSource.getConnection()) {
+            try (final PreparedStatement statement = conn.prepareStatement(SELECT_WORLD)) {
+                statement.setInt(1, randomWorld());
+                try (ResultSet rs = statement.executeQuery()) {
+                    rs.next();
+                    result = new World(rs.getInt("id"), rs.getInt("randomNumber"));
+                }
+            }
+        }
+        response.setHeader("Date", DateUtil.getTime());
+        response.sendJson(result);
+    }
+
+    @GET("/queries")
+    public void queries(HttpRequest request,HttpResponse response) throws Exception {
+        World[] result = new World[getQueries(request.query("queries"))];
+        try (Connection conn = dataSource.getConnection()) {
+            for (int i = 0; i < result.length; i++) {
+                try (final PreparedStatement statement = conn.prepareStatement(SELECT_WORLD)) {
+                    statement.setInt(1, randomWorld());
+                    try (ResultSet rs = statement.executeQuery()) {
+                        rs.next();
+                        result[i] = new World(rs.getInt("id"), rs.getInt("randomNumber"));
+                    }
+                }
+            }
+        }
+        response.setHeader("Date", DateUtil.getTime());
+        response.sendJson(result);
+    }
+
+
+    @GET("/updates")
+    public void updates(HttpRequest request,HttpResponse response) throws Exception {
+        World[] result = new World[getQueries(request.query("queries"))];
+        StringJoiner updateSql = new StringJoiner(
+                ", ",
+                "UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ",
+                " ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id");
+
+        try (Connection connection = dataSource.getConnection()) {
+            try (PreparedStatement statement = connection.prepareStatement(SELECT_WORLD)) {
+                for (int i = 0; i < result.length; i++) {
+                    statement.setInt(1, randomWorld());
+                    try (ResultSet rs = statement.executeQuery()) {
+                        rs.next();
+                        result[i] = new World(rs.getInt("id"), randomWorld());
+                    }
+                    // prepare update query
+                    updateSql.add("(?, ?)");
+                }
+            }
+
+            try (PreparedStatement statement = connection.prepareStatement(updateSql.toString())) {
+                int i = 0;
+                for (World world : result) {
+                    statement.setInt(++i, world.getRandomNumber());
+                    statement.setInt(++i, world.getRandomNumber());
+                }
+                statement.executeUpdate();
+            }
+        }
+        response.setHeader("Date", DateUtil.getTime());
+        response.sendJson(result);
+    }
+
+    @GET("/fortunes")
+    public void fortunes(HttpResponse response) throws Exception {
+        List<Fortune> fortunes = new ArrayList<>();
+        try (Connection connection = dataSource.getConnection()) {
+            try (PreparedStatement stt = connection.prepareStatement("select * from fortune")) {
+                try (ResultSet rs = stt.executeQuery()) {
+                    while (rs.next()) {
+                        fortunes.add(new Fortune(rs.getInt("id"), rs.getString("message")));
+                    }
+                }
+            }
+        }
+        fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+        Collections.sort(fortunes);
+        response.setHeader("Date", DateUtil.getTime());
+        Map<String,Object> data=new HashMap<>();
+        data.put("data",fortunes);
+        response.sendTemplate("fortunes.ftl",data);
+    }
+}

+ 25 - 0
frameworks/Java/hserver-business/src/main/java/com/test/hserver/db/DataSourceConfig.java

@@ -0,0 +1,25 @@
+package com.test.hserver.db;
+
+import cn.hserver.core.ioc.annotation.Autowired;
+import cn.hserver.core.ioc.annotation.Bean;
+import cn.hserver.core.ioc.annotation.Configuration;
+import com.zaxxer.hikari.HikariDataSource;
+
+import javax.sql.DataSource;
+
+@Configuration
+public class DataSourceConfig {
+
+    @Autowired
+    private PostgresConfig postgresConfig;
+
+    @Bean
+    public DataSource initDataSource() {
+        HikariDataSource ds = new HikariDataSource();
+        ds.setJdbcUrl(postgresConfig.getJdbcUrl());
+        ds.setUsername(postgresConfig.getUsername());
+        ds.setPassword(postgresConfig.getPassword());
+        ds.setMaximumPoolSize(postgresConfig.getMaximumPoolSize());
+        return ds;
+    }
+}

+ 57 - 0
frameworks/Java/hserver-business/src/main/java/com/test/hserver/db/PostgresConfig.java

@@ -0,0 +1,57 @@
+package com.test.hserver.db;
+
+
+import cn.hserver.core.ioc.annotation.ConfigurationProperties;
+
+@ConfigurationProperties
+public class PostgresConfig {
+    private String jdbcUrl;
+    private String username;
+    private String password;
+    private int maximumPoolSize;
+
+    public PostgresConfig() {
+    }
+
+    public String getJdbcUrl() {
+        return jdbcUrl;
+    }
+
+    public void setJdbcUrl(String jdbcUrl) {
+        this.jdbcUrl = jdbcUrl;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public int getMaximumPoolSize() {
+        return maximumPoolSize;
+    }
+
+    public void setMaximumPoolSize(int maximumPoolSize) {
+        this.maximumPoolSize = maximumPoolSize;
+    }
+
+    @Override
+    public String toString() {
+        return "PostgresConfig{" +
+                "jdbcUrl='" + jdbcUrl + '\'' +
+                ", username='" + username + '\'' +
+                ", password='" + password + '\'' +
+                ", maximumPoolSize=" + maximumPoolSize +
+                '}';
+    }
+}

+ 15 - 0
frameworks/Java/hserver-business/src/main/java/com/test/hserver/task/TimeAdd.java

@@ -0,0 +1,15 @@
+package com.test.hserver.task;
+
+import cn.hserver.core.ioc.annotation.Bean;
+import cn.hserver.core.ioc.annotation.Task;
+import com.test.hserver.util.DateUtil;
+
+@Bean
+public class TimeAdd {
+
+    @Task(name = "时间计算", time = "1000")
+    public void add() {
+        DateUtil.time = DateUtil.getNow();
+    }
+
+}

+ 25 - 0
frameworks/Java/hserver-business/src/main/java/com/test/hserver/util/DateUtil.java

@@ -0,0 +1,25 @@
+package com.test.hserver.util;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * @author hxm
+ */
+public class DateUtil {
+    private static final DateTimeFormatter GMT_FMT = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+    private static final ZoneId zoneId = ZoneId.of("GMT");
+    public static String getNow() {
+        return GMT_FMT.format(LocalDateTime.now().atZone(zoneId));
+    }
+    public static String time;
+    public static String getTime(){
+        if (time==null){
+            time=getNow();
+            return time;
+        }
+        return time;
+    }
+}

+ 18 - 0
frameworks/Java/hserver-business/src/main/java/com/test/hserver/util/Util.java

@@ -0,0 +1,18 @@
+package com.test.hserver.util;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public class Util {
+  public static int randomWorld() {
+    return 1 + ThreadLocalRandom.current().nextInt(10000);
+  }
+
+  public static int getQueries(String queries) {
+    try {
+      int count = Integer.parseInt(queries);
+      return Math.min(500, Math.max(1, count));
+    } catch (Exception e) {
+      return 1;
+    }
+  }
+}

+ 6 - 0
frameworks/Java/hserver-business/src/main/resources/app.properties

@@ -0,0 +1,6 @@
+jdbcUrl= jdbc:postgresql://tfb-database:5432/hello_world
+username= benchmarkdbuser
+password= benchmarkdbpass
+maximumPoolSize= 256
+
+log=info

+ 20 - 0
frameworks/Java/hserver-business/src/main/resources/template/fortunes.ftl

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Fortunes</title>
+</head>
+<body>
+<table>
+  <tr>
+    <th>id</th>
+    <th>message</th>
+  </tr>
+  <#list data as fortune>
+    <tr>
+      <td>${fortune.id?html}</td>
+      <td>${fortune.message?html}</td>
+    </tr>
+  </#list>
+</table>
+</body>
+</html>

+ 5 - 5
frameworks/Java/hserver/benchmark_config.json

@@ -5,11 +5,11 @@
     {
       "default": {
         "db_url": "/db",
-        "query_url": "/queries?queries=",
         "fortune_url": "/fortunes",
         "plaintext_url": "/plaintext",
-        "update_url": "/updates?queries=",
         "json_url": "/json",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
         "port": 8888,
         "approach": "Realistic",
         "classification": "Fullstack",
@@ -18,8 +18,8 @@
         "language": "Java",
         "flavor": "None",
         "orm": "Full",
-        "platform": "None",
-        "webserver": "None",
+        "platform": "hserver",
+        "webserver": "hserver",
         "os": "Linux",
         "database_os": "Linux",
         "display_name": "hserver",
@@ -28,4 +28,4 @@
       }
     }
   ]
-}
+}

+ 3 - 3
frameworks/Java/hserver/config.toml

@@ -12,6 +12,6 @@ approach = "Realistic"
 classification = "Fullstack"
 os = "Linux"
 orm = "Full"
-platform = "None"
-webserver = "None"
-versus = "hserver"
+platform = "hserver"
+webserver = "hserver"
+versus = "hserver"

+ 3 - 3
frameworks/Java/hserver/hserver.dockerfile

@@ -1,13 +1,13 @@
-FROM maven:3.6.3-openjdk-8-slim as maven
+FROM maven:3.8.4-openjdk-17-slim as maven
 WORKDIR /hserver
 COPY pom.xml pom.xml
 COPY src src
 RUN mvn package
 
-FROM openjdk:8u275-jdk-slim
+FROM openjdk:17.0.2
 WORKDIR /hserver
 COPY --from=maven /hserver/target/hserver-1.0.jar app.jar
 
 EXPOSE 8888
 
-CMD ["java", "-jar", "app.jar"]
+CMD ["java", "-jar", "app.jar"]

+ 1 - 1
frameworks/Java/hserver/pom.xml

@@ -11,7 +11,7 @@
     <parent>
         <artifactId>hserver-parent</artifactId>
         <groupId>cn.hserver</groupId>
-        <version>3.5.M2</version>
+        <version>3.6.0</version>
     </parent>
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

+ 7 - 4
frameworks/Java/hserver/src/main/java/com/test/hserver/controller/TestController.java

@@ -3,11 +3,14 @@ package com.test.hserver.controller;
 import cn.hserver.core.ioc.annotation.Autowired;
 import cn.hserver.plugin.web.annotation.Controller;
 import cn.hserver.plugin.web.annotation.GET;
+import cn.hserver.plugin.web.interfaces.HttpRequest;
 import cn.hserver.plugin.web.interfaces.HttpResponse;
 import com.test.hserver.bean.Fortune;
 import com.test.hserver.bean.Message;
 import com.test.hserver.bean.World;
 import com.test.hserver.util.DateUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.sql.DataSource;
 import java.sql.Connection;
@@ -59,8 +62,8 @@ public class TestController {
     }
 
     @GET("/queries")
-    public void queries(String queries,HttpResponse response) throws Exception {
-        World[] result = new World[getQueries(queries)];
+    public void queries(HttpRequest request,HttpResponse response) throws Exception {
+        World[] result = new World[getQueries(request.query("queries"))];
         try (Connection conn = dataSource.getConnection()) {
             for (int i = 0; i < result.length; i++) {
                 try (final PreparedStatement statement = conn.prepareStatement(SELECT_WORLD)) {
@@ -78,8 +81,8 @@ public class TestController {
 
 
     @GET("/updates")
-    public void updates(String queries,HttpResponse response) throws Exception {
-        World[] result = new World[getQueries(queries)];
+    public void updates(HttpRequest request,HttpResponse response) throws Exception {
+        World[] result = new World[getQueries(request.query("queries"))];
         StringJoiner updateSql = new StringJoiner(
                 ", ",
                 "UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ",

+ 48 - 2
frameworks/Java/solon/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.noear</groupId>
         <artifactId>solon-parent</artifactId>
-        <version>3.0.2</version>
+        <version>3.0.3</version>
     </parent>
 
     <groupId>hello</groupId>
@@ -15,24 +15,70 @@
 
     <properties>
         <java.version>21</java.version>
+        <jstachio.version>1.3.6</jstachio.version>
     </properties>
 
     <dependencies>
         <dependency>
             <groupId>org.noear</groupId>
-            <artifactId>solon-boot-smarthttp</artifactId>
+            <artifactId>solon-web</artifactId>
         </dependency>
 
         <dependency>
             <groupId>org.noear</groupId>
             <artifactId>solon-serialization-jackson</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.noear</groupId>
+            <artifactId>solon-data-sqlutils</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jstach</groupId>
+            <artifactId>jstachio</artifactId>
+            <version>${jstachio.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jstach</groupId>
+            <artifactId>jstachio-apt</artifactId>
+            <version>${jstachio.version}</version>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>com.zaxxer</groupId>
+            <artifactId>HikariCP</artifactId>
+            <version>6.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>42.7.4</version>
+        </dependency>
     </dependencies>
 
     <build>
         <finalName>${project.artifactId}</finalName>
 
         <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>io.jstach</groupId>
+                            <artifactId>jstachio-apt</artifactId>
+                            <version>${jstachio.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-assembly-plugin</artifactId>

+ 0 - 4
frameworks/Java/solon/src/main/java/hello/Main.java

@@ -2,10 +2,6 @@ package hello;
 
 import org.noear.solon.Solon;
 
-/**
- * @author pmg1991
- * @version V1.0
- */
 public class Main {
 	public static void main(String[] args) {
 		Solon.start(Main.class, args);

+ 17 - 0
frameworks/Java/solon/src/main/java/hello/Utils.java

@@ -0,0 +1,17 @@
+package hello;
+
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.IntStream;
+
+abstract public class Utils {
+	private static final int MIN_WORLD_NUMBER = 1;
+	private static final int MAX_WORLD_NUMBER_PLUS_ONE = 10_001;
+
+	public static int randomWorldNumber() {
+		return ThreadLocalRandom.current().nextInt(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE);
+	}
+
+	public static IntStream randomWorldNumbers() {
+		return ThreadLocalRandom.current().ints(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE).distinct();
+	}
+}

+ 0 - 25
frameworks/Java/solon/src/main/java/hello/controller/HelloController.java

@@ -1,25 +0,0 @@
-package hello.controller;
-
-import org.noear.solon.annotation.Controller;
-import org.noear.solon.annotation.Get;
-import org.noear.solon.annotation.Mapping;
-import hello.model.Message;
-
-/**
- * @author noear
- * @version V1.0
- */
-@Controller
-public class HelloController {
-    @Get
-    @Mapping("plaintext")
-    public String plaintext() {
-        return "Hello, World!";
-    }
-
-    @Get
-    @Mapping("json")
-    public Message json() {
-        return new Message("Hello, World!");
-    }
-}

+ 16 - 0
frameworks/Java/solon/src/main/java/hello/model/Fortune.java

@@ -0,0 +1,16 @@
+package hello.model;
+
+public final class Fortune implements Comparable<Fortune>{
+	public final int id;
+	public final String message;
+
+	public Fortune(int id, String message) {
+		this.id = id;
+		this.message = message;
+	}
+
+	@Override
+	public int compareTo(final Fortune other) {
+		return message.compareTo(other.message);
+	}
+}

+ 12 - 0
frameworks/Java/solon/src/main/java/hello/model/World.java

@@ -0,0 +1,12 @@
+package hello.model;
+
+
+public final class World {
+	public int id;
+	public int randomNumber;
+	public World(int id, int randomNumber) {
+		this.id = id;
+		this.randomNumber = randomNumber;
+	}
+
+}

+ 15 - 0
frameworks/Java/solon/src/main/java/hello/repository/DbRepository.java

@@ -0,0 +1,15 @@
+package hello.repository;
+
+import hello.model.Fortune;
+import hello.model.World;
+
+import java.util.List;
+
+public interface DbRepository {
+
+    World getWorld(int id) throws Exception;
+
+    void updateWorlds(List<World> worlds) throws Exception;
+
+    List<Fortune> fortunes() throws Exception;
+}

+ 46 - 0
frameworks/Java/solon/src/main/java/hello/repository/JdbcDbRepository.java

@@ -0,0 +1,46 @@
+package hello.repository;
+
+import hello.model.Fortune;
+import hello.model.World;
+import org.noear.solon.annotation.Component;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.data.sql.SqlUtils;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class JdbcDbRepository implements DbRepository {
+    @Inject
+    SqlUtils sqlUtils;
+
+    @Override
+    public World getWorld(int id) {
+        try {
+            return sqlUtils.sql("SELECT id, randomnumber FROM world WHERE id = ?", id)
+                    .queryRow()
+                    .toBean(World.class, (r, t) -> new World((int) r.getObject(1), (int) r.getObject(2)));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    public void updateWorlds(List<World> worlds) throws SQLException {
+        List<Object[]> values = new ArrayList<>();
+        for (World w : worlds) {
+            values.add(new Object[]{w.randomNumber, w.id});
+        }
+
+        sqlUtils.sql("UPDATE world SET randomnumber = ? WHERE id = ?")
+                .updateBatch(values);
+    }
+
+    @Override
+    public List<Fortune> fortunes() throws SQLException {
+        return sqlUtils.sql("SELECT id, message FROM fortune")
+                .queryRowList()
+                .toBeanList(Fortune.class, (r, t) -> new Fortune((int) r.getObject(1), (String) r.getObject(2)));
+    }
+}

+ 61 - 0
frameworks/Java/solon/src/main/java/hello/web/DbHandler.java

@@ -0,0 +1,61 @@
+package hello.web;
+
+import hello.Utils;
+import hello.model.Fortune;
+import hello.model.World;
+import hello.repository.JdbcDbRepository;
+import org.noear.solon.annotation.Component;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.core.handle.Context;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+@Component
+public class DbHandler {
+    @Inject
+    JdbcDbRepository dbRepository;
+
+    public void db(Context ctx) throws Throwable {
+        ctx.render(dbRepository.getWorld(Utils.randomWorldNumber()));
+    }
+
+    public void queries(Context ctx) throws Throwable {
+        int queries = ctx.paramAsInt("queries", 0);
+
+        World[] worlds = Utils.randomWorldNumbers()
+                .mapToObj(dbRepository::getWorld).limit(queries)
+                .toArray(World[]::new);
+
+        ctx.render(worlds);
+    }
+
+    public void updates(Context ctx) throws Throwable {
+        int queries = ctx.paramAsInt("queries", 0);
+
+        List<World> worlds = Utils.randomWorldNumbers()
+                .mapToObj(id -> {
+                    World world = dbRepository.getWorld(id);
+                    int randomNumber;
+                    do {
+                        randomNumber = Utils.randomWorldNumber();
+                    } while (randomNumber == world.randomNumber);
+                    world.randomNumber = randomNumber;
+                    return world;
+                }).limit(queries)
+                .sorted(Comparator.comparingInt(w -> w.id))
+                .toList();
+        dbRepository.updateWorlds(worlds);
+
+        ctx.render(worlds);
+    }
+
+    public void fortunes(Context ctx) throws Throwable {
+        List<Fortune> fortunes = dbRepository.fortunes();
+        fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+        Collections.sort(fortunes);
+
+        ctx.render(new Fortunes(fortunes));
+    }
+}

+ 10 - 0
frameworks/Java/solon/src/main/java/hello/web/Fortunes.java

@@ -0,0 +1,10 @@
+package hello.web;
+
+import hello.model.Fortune;
+import io.jstach.jstache.JStache;
+
+import java.util.List;
+
+@JStache(path = "fortunes.mustache")
+public record Fortunes(List<Fortune> fortunes) {
+}

+ 27 - 0
frameworks/Java/solon/src/main/java/hello/web/JsonHandler.java

@@ -0,0 +1,27 @@
+package hello.web;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import org.noear.solon.annotation.Component;
+import org.noear.solon.boot.web.MimeType;
+import org.noear.solon.core.handle.Context;
+import org.noear.solon.core.handle.Handler;
+
+import java.util.Map;
+
+@Component
+public class JsonHandler implements Handler {
+    private final ObjectWriter writer;
+
+    public JsonHandler() {
+        this.writer = new ObjectMapper().writerFor(Map.class);
+    }
+
+    @Override
+    public void handle(Context ctx) throws Throwable {
+        byte[] body = this.writer.writeValueAsBytes(Map.of("message", "Hello, world!"));
+        ctx.contentLength(body.length);
+        ctx.contentType(MimeType.APPLICATION_JSON_VALUE);
+        ctx.output(body);
+    }
+}

+ 25 - 0
frameworks/Java/solon/src/main/java/hello/web/TextHandler.java

@@ -0,0 +1,25 @@
+package hello.web;
+
+import org.noear.solon.annotation.Component;
+import org.noear.solon.boot.web.MimeType;
+import org.noear.solon.core.handle.Context;
+import org.noear.solon.core.handle.Handler;
+
+import java.nio.charset.StandardCharsets;
+
+@Component
+public class TextHandler implements Handler {
+    private static final byte[] TEXT_BODY = "Hello, World!".getBytes(StandardCharsets.UTF_8);
+
+    private static final String TEXT_BODY_LENGTH = String.valueOf(TEXT_BODY.length);
+    private static final String CONTENT_LENGTH = "Content-Length";
+    private static final String CONTENT_TYPE = "Content-Type";
+
+    @Override
+    public void handle(Context ctx) throws Throwable {
+        ctx.headerSet(CONTENT_LENGTH, TEXT_BODY_LENGTH);
+        ctx.headerSet(CONTENT_TYPE, MimeType.TEXT_PLAIN_VALUE);
+        ctx.output("Hello, World!".getBytes());
+    }
+}
+

+ 29 - 0
frameworks/Java/solon/src/main/java/hello/web/WebmvcRouter.java

@@ -0,0 +1,29 @@
+package hello.web;
+
+import org.noear.solon.SolonApp;
+import org.noear.solon.annotation.Bean;
+import org.noear.solon.annotation.Configuration;
+
+@Configuration
+public class WebmvcRouter {
+    @Bean
+    public void initRouter(SolonApp app,
+                           DbHandler dbHandler,
+                           JsonHandler jsonHandler,
+                           TextHandler textHandler) {
+        app.handler().prev(ctx -> {
+            ctx.setHandled(true);
+            ctx.headerSet("Server", "Solon");
+
+            switch (ctx.path()) {
+                case "/plaintext" -> textHandler.handle(ctx);
+                case "/json" -> jsonHandler.handle(ctx);
+                case "/db" -> dbHandler.db(ctx);
+                case "/queries" -> dbHandler.queries(ctx);
+                case "/updates" -> dbHandler.updates(ctx);
+                case "/fortunes" -> dbHandler.fortunes(ctx);
+                default -> ctx.status(404);
+            }
+        });
+    }
+}

+ 0 - 1
frameworks/Java/solon/src/main/resources/app.properties

@@ -1 +0,0 @@
-server.http.ioBound=false

+ 23 - 0
frameworks/Java/solon/src/main/resources/app.yml

@@ -0,0 +1,23 @@
+database:
+  name: hello_world
+  host: tfb-database
+  port: 5432
+  username: benchmarkdbuser
+  password: benchmarkdbpass
+
+server.http:
+  ioBound: false
+
+solon.threads:
+  virtual:
+    enabled: true
+
+solon.dataSources:
+  test!:
+    class: "com.zaxxer.hikari.HikariDataSource"
+    driverClassName: "org.postgresql.Driver"
+    url: jdbc:postgresql://${database.host}:${database.port}/${database.name}?loggerLevel=OFF&disableColumnSanitiser=true&assumeMinServerVersion=16&sslmode=disable
+    username: ${database.username}
+    password: ${database.password}
+    hikari:
+      maximum-pool-size: 256

+ 20 - 0
frameworks/Java/solon/src/main/resources/fortunes.mustache

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Fortunes</title>
+</head>
+<body>
+<table>
+  <tr>
+    <th>id</th>
+    <th>message</th>
+  </tr>
+  {{#fortunes}}
+  <tr>
+    <td>{{id}}</td>
+    <td>{{message}}</td>
+  </tr>
+  {{/fortunes}}
+</table>
+</body>
+</html>

+ 1 - 1
frameworks/Java/spring-webflux/src/main/resources/application.yml

@@ -17,7 +17,7 @@ spring:
     password: ${database.password}
     url: r2dbc:postgresql://${database.host}:${database.port}/${database.name}?loggerLevel=OFF&disableColumnSanitiser=true&assumeMinServerVersion=16&sslmode=disable
     pool:
-      max-size: 512
+      max-size: 256
 
 ---
 spring:

+ 1 - 1
frameworks/Java/spring/src/main/resources/application.yml

@@ -15,7 +15,7 @@ spring:
     username: ${database.username}
     password: ${database.password}
     hikari:
-      maximum-pool-size: 512
+      maximum-pool-size: 256
 database:
   name: hello_world
   host: tfb-database

+ 1 - 1
frameworks/JavaScript/express/package.json

@@ -7,7 +7,7 @@
     "dateformat": "3.0.3",
     "escape-html": "1.0.3",
     "express": "4.18.2",
-    "mongoose": "8.3.2",
+    "mongoose": "8.8.3",
     "mysql2": "3.9.8",
     "pg": "8.5.0",
     "pg-promise": "10.7.3",

+ 0 - 30
frameworks/JavaScript/fastify/README.md

@@ -19,33 +19,3 @@ Information about Fastify can be found at https://github.com/fastify/fastify
 ### JSON Encoding Test
 
 http://TFB-server:8080/json
-
-### Data-Store/Database Mapping Test
-
-MongoDB:
-http://TFB-server:8080/mongoose/
-
-MySQL:
-http://TFB-server:8080/mysql-orm/
-
-### Variable Query Test
-
-MongoDB:
-http://TFB-server:8080/mongoose/2
-
-MySQL:
-http://TFB-server:8080/mysql-orm/2
-
-### Fortune Test
-
-MySQL:
-http://TFB-server:8080/fortune
-
-### Database Update Test
-
-MongoDB:
-http://TFB-server:8080/mongoose-update/2
-
-MySQL:
-http://TFB-server:8080/mysql-orm-update/2
-

+ 2 - 7
frameworks/JavaScript/fastify/benchmark_config.json

@@ -5,14 +5,9 @@
       "default": {
         "json_url": "/json",
         "plaintext_url": "/plaintext",
-        "db_url": "/db",
-        "query_url": "/queries?queries=",
-        "fortune_url": "/fortunes",
-        "update_url": "/updates?queries=",
         "port": 8080,
         "approach": "Realistic",
         "classification": "Micro",
-        "database": "MongoDB",
         "framework": "fastify",
         "language": "JavaScript",
         "flavor": "NodeJS",
@@ -42,7 +37,7 @@
         "webserver": "None",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "fastify",
+        "display_name": "fastify [mysql]",
         "notes": "",
         "versus": "nodejs"
       },
@@ -63,7 +58,7 @@
         "webserver": "None",
         "os": "Linux",
         "database_os": "Linux",
-        "display_name": "fastify",
+        "display_name": "fastify [postgres]",
         "notes": "",
         "versus": "nodejs"
       }

+ 0 - 5
frameworks/JavaScript/fastify/config.toml

@@ -4,13 +4,8 @@ name = "fastify"
 [main]
 urls.plaintext = "/plaintext"
 urls.json = "/json"
-urls.db = "/db"
-urls.query = "/queries?queries="
-urls.update = "/updates?queries="
-urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Micro"
-database = "MongoDB"
 database_os = "Linux"
 os = "Linux"
 orm = "Raw"

+ 5 - 0
frameworks/JavaScript/fastify/create-server.js

@@ -1,6 +1,11 @@
 const fastify = require("fastify")();
 const handlers = require("./handlers");
 
+fastify.setErrorHandler((error, request, reply) => {
+  console.log(error)
+  reply.status(500).send({ ok: false })
+})
+
 fastify.register(require("@fastify/view"), {
   engine: {
     handlebars: require("handlebars")

+ 0 - 47
frameworks/JavaScript/fastify/db/mongo.js

@@ -1,47 +0,0 @@
-const { MongoClient } = require("mongodb");
-
-const mongoUrl = "mongodb://tfb-database:27017";
-const dbName = "hello_world";
-
-let client;
-
-async function getCollection(name) {
-  if (!client) {
-    client = await new MongoClient(mongoUrl).connect();
-  }
-
-  const db = client.db(dbName);
-
-  return db.collection(name);
-}
-
-async function allFortunes() {
-  const collection = await getCollection("fortune");
-  const fortunes = await collection.find({}, { projection: { _id: 0 } });
-
-  return fortunes.toArray();
-}
-
-async function getWorld(id) {
-  const collection = await getCollection("world");
-
-  return collection.findOne({ id }, { projection: { _id: 0 } });
-}
-
-async function saveWorlds(worlds) {
-  const collection = await getCollection("world");
-
-  const bulk = collection.initializeUnorderedBulkOp();
-
-  worlds.forEach(world => {
-    bulk.find({ id: world.id }).updateOne(world);
-  });
-
-  return bulk.execute();
-}
-
-module.exports = {
-  getWorld,
-  saveWorlds,
-  allFortunes
-};

+ 23 - 29
frameworks/JavaScript/fastify/db/mysql.js

@@ -1,41 +1,35 @@
-const knex = require("knex")({
-  client: "mysql2",
-  connection: {
-    host: "tfb-database",
-    user: "benchmarkdbuser",
-    password: "benchmarkdbpass",
-    database: "hello_world"
-  }
-});
+const { createPool } = require("mariadb");
+
+const clientOpts = {
+  host: process.env.MYSQL_HOST,
+  user: process.env.MYSQL_USER,
+  password: process.env.MYSQL_PSWD,
+  database: process.env.MYSQL_DBNAME,
+};
+
+const pool = createPool({ ...clientOpts, connectionLimit: 1 });
+const execute = (text, values) => pool.execute(text, values || undefined);
 
 async function allFortunes() {
-  return knex("Fortune").select("*");
+  return execute("select id, message from fortune", []);
 }
 
 async function getWorld(id) {
-  return knex("World")
-    .first()
-    .where({ id });
+  return execute("select id, randomNumber from world where id = ?", [id]).then(
+    (arr) => arr[0]
+  );
 }
 
-async function saveWorlds(worlds) {
-  const updates = [];
-
-  worlds.forEach(world => {
-    const { id, randomNumber } = world;
-
-    updates.push(
-      knex("World")
-        .update({ randomNumber })
-        .where({ id })
-    );
-  });
-
-  return Promise.all(updates);
+async function bulkUpdate(worlds) {
+  const sql = "update world set randomNumber = ? where id = ?";
+  const values = worlds
+    .map((world) => [world.randomnumber, world.id])
+    .sort((a, b) => (a[0] < b[0] ? -1 : 1));
+  return pool.batch(sql, values);
 }
 
 module.exports = {
   getWorld,
-  saveWorlds,
-  allFortunes
+  bulkUpdate,
+  allFortunes,
 };

+ 25 - 28
frameworks/JavaScript/fastify/db/postgres.js

@@ -1,41 +1,38 @@
-const knex = require("knex")({
-  client: "pg",
-  connection: {
-    host: "tfb-database",
-    user: "benchmarkdbuser",
-    password: "benchmarkdbpass",
-    database: "hello_world"
-  }
-});
+const postgres = require("postgres");
+
+const clientOpts = {
+  host: process.env.PG_HOST,
+  user: process.env.PG_USER,
+  password: process.env.PG_PSWD,
+  database: process.env.PG_DBNAME,
+};
+
+const sql = postgres({ ...clientOpts, max: 1 });
 
 async function allFortunes() {
-  return knex("Fortune").select("*");
+  return sql`select id, message from fortune`;
 }
 
 async function getWorld(id) {
-  return knex("World")
-    .first()
-    .where({ id });
+  return sql`select id, randomNumber from world where id = ${id}`.then(
+    (arr) => arr[0]
+  );
 }
 
-async function saveWorlds(worlds) {
-  const updates = [];
-
-  worlds.forEach(world => {
-    const { id, randomNumber } = world;
-
-    updates.push(
-      knex("World")
-        .update({ randomnumber: randomNumber })
-        .where({ id })
-    );
-  });
+async function bulkUpdate(worlds) {
+  const values = sql(
+    worlds
+      .map((world) => [world.id, world.randomnumber])
+      .sort((a, b) => (a[0] < b[0] ? -1 : 1))
+  );
 
-  return Promise.all(updates);
+  return sql`update world set randomNumber = (update_data.randomNumber)::int
+    from (values ${values}) as update_data (id, randomNumber)
+    where world.id = (update_data.id)::int`;
 }
 
 module.exports = {
   getWorld,
-  saveWorlds,
-  allFortunes
+  bulkUpdate,
+  allFortunes,
 };

+ 5 - 1
frameworks/JavaScript/fastify/fastify-mysql.dockerfile

@@ -1,4 +1,4 @@
-FROM node:20.12.2-alpine
+FROM node:20.16-slim
 
 COPY ./ ./
 
@@ -6,6 +6,10 @@ RUN npm install
 
 ENV NODE_ENV production
 ENV DATABASE mysql
+ENV MYSQL_HOST tfb-database
+ENV MYSQL_USER benchmarkdbuser
+ENV MYSQL_PSWD benchmarkdbpass
+ENV MYSQL_DBNAME hello_world
 
 EXPOSE 8080
 

+ 5 - 1
frameworks/JavaScript/fastify/fastify-postgres.dockerfile

@@ -1,4 +1,4 @@
-FROM node:20.12.2-alpine
+FROM node:20.16-slim
 
 COPY ./ ./
 
@@ -6,6 +6,10 @@ RUN npm install
 
 ENV NODE_ENV production
 ENV DATABASE postgres
+ENV PG_HOST tfb-database
+ENV PG_USER benchmarkdbuser
+ENV PG_PSWD benchmarkdbpass
+ENV PG_DBNAME hello_world
 
 EXPOSE 8080
 

+ 1 - 2
frameworks/JavaScript/fastify/fastify.dockerfile

@@ -1,11 +1,10 @@
-FROM node:20.12.2-alpine
+FROM node:20.16-slim
 
 COPY ./ ./
 
 RUN npm install
 
 ENV NODE_ENV production
-ENV DATABASE mongo
 
 EXPOSE 8080
 

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.