Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions frameworks/CSharp/appmpower/appmpower-odbc-my.dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
10 changes: 7 additions & 3 deletions frameworks/CSharp/appmpower/appmpower-odbc-pg.dockerfile
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand All @@ -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"]
4 changes: 2 additions & 2 deletions frameworks/CSharp/appmpower/appmpower.dockerfile
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand Down
59 changes: 59 additions & 0 deletions frameworks/CSharp/appmpower/src/appMpower.Orm/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -66,6 +69,7 @@ public static unsafe IntPtr Db(int* length, IntPtr* handlePointer)
*/
}

/*
[UnmanagedCallersOnly(EntryPoint = "Fortunes")]
public static unsafe IntPtr Fortunes(int* length, IntPtr* handlePointer)
{
Expand All @@ -81,6 +85,61 @@ public static unsafe IntPtr Fortunes(int* length, IntPtr* handlePointer)

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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>

<PublishAot>true</PublishAot>
Expand Down Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
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;
Expand All @@ -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)
Expand All @@ -27,6 +41,66 @@ public unsafe Task Invoke(HttpContext httpContext)
{
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);
Expand All @@ -43,10 +117,51 @@ public unsafe Task Invoke(HttpContext httpContext)
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
Expand Down
22 changes: 22 additions & 0 deletions frameworks/CSharp/appmpower/src/appMpower/Objects/Fortune.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading