diff --git a/frameworks/CSharp/touchsocket/Benchmarks.sln b/frameworks/CSharp/touchsocket/Benchmarks.sln index 6f164942105..8d41045e044 100644 --- a/frameworks/CSharp/touchsocket/Benchmarks.sln +++ b/frameworks/CSharp/touchsocket/Benchmarks.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.0.11109.219 d18.0-oob +VisualStudioVersion = 18.0.11109.219 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TouchSocketWebApi", "src\TouchSocketWebApi\TouchSocketWebApi.csproj", "{6BD9363A-D77F-5D90-8444-2BC37495C920}" EndProject @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TouchSocketHttp31", "src\To EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TouchSocketWebApi31", "src\TouchSocketWebApi31\TouchSocketWebApi31.csproj", "{6B6A57CE-1F69-FA6E-7458-0A4B6AB60683}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TouchSocketHttpPlatform", "src\TouchSocketHttpPlatform\TouchSocketHttpPlatform.csproj", "{BC320C24-941D-2109-FBC8-92780569BBA4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {6B6A57CE-1F69-FA6E-7458-0A4B6AB60683}.Debug|Any CPU.Build.0 = Debug|Any CPU {6B6A57CE-1F69-FA6E-7458-0A4B6AB60683}.Release|Any CPU.ActiveCfg = Release|Any CPU {6B6A57CE-1F69-FA6E-7458-0A4B6AB60683}.Release|Any CPU.Build.0 = Release|Any CPU + {BC320C24-941D-2109-FBC8-92780569BBA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC320C24-941D-2109-FBC8-92780569BBA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC320C24-941D-2109-FBC8-92780569BBA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC320C24-941D-2109-FBC8-92780569BBA4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/frameworks/CSharp/touchsocket/README.md b/frameworks/CSharp/touchsocket/README.md index 22524d6f70a..8a520d40eec 100644 --- a/frameworks/CSharp/touchsocket/README.md +++ b/frameworks/CSharp/touchsocket/README.md @@ -2,9 +2,40 @@ See [touchsocket](https://touchsocket.net/) for more information. +## Variants Included + +* `touchsocket.webapi` (`TouchSocketWebApi`) – WebApi style. +* `touchsocket.webapi31` (`TouchSocketWebApi31`) – WebApi targeting .NET 8. +* `touchsocket.http` (`TouchSocketHttp`) – Minimal HTTP implementation. +* `touchsocket.http31` (`TouchSocketHttp31`) – Minimal HTTP targeting .NET 8. +* `touchsocket.httpplatform` (`TouchSocketHttpPlatform`) – High-performance custom pipeline-based HTTP server focusing on low-level parsing and zero-allocation response writing. + ## Infrastructure Software Versions -**Language** +**Language / Runtime** + +* C# / .NET (8.0 & 9.0 depending on variant) + +## Endpoints + +All variants implement: + +* `/plaintext` – Returns a plain text "Hello, World!" response. +* `/json` – Returns a JSON object `{"message": "Hello, World!"}`. + +The `httpplatform` variant manually parses request lines and headers via `System.IO.Pipelines` for maximum throughput. + +## Dockerfiles + +Each variant has a dedicated Dockerfile named: + +* `touchsocket.dockerfile` (webapi) +* `touchsocket-webapi31.dockerfile` +* `touchsocket-http.dockerfile` +* `touchsocket-http31.dockerfile` +* `touchsocket-httpplatform.dockerfile` + +## Notes -* C# 8.0 +The `httpplatform` variant is intended for benchmarking raw server performance. It omits higher-level abstractions to reduce overhead. diff --git a/frameworks/CSharp/touchsocket/benchmark_config.json b/frameworks/CSharp/touchsocket/benchmark_config.json index 8aa1e953f59..86036458c9a 100644 --- a/frameworks/CSharp/touchsocket/benchmark_config.json +++ b/frameworks/CSharp/touchsocket/benchmark_config.json @@ -77,6 +77,25 @@ "display_name": "touchsocket.webapi31", "notes": "", "versus": "aspnetcore-mvc" + }, + "httpplatform": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "port": 8080, + "approach": "Realistic", + "classification": "Platform", + "database": "Postgres", + "framework": "touchsocket.httpplatform", + "language": "C#", + "orm": "Micro", + "platform": ".NET", + "flavor": "CoreCLR", + "webserver": "touchsocket", + "os": "Linux", + "database_os": "Linux", + "display_name": "touchsocket.httpplatform", + "notes": "High-performance custom pipeline-based implementation variant.", + "versus": "aspnetcore" } } ] diff --git a/frameworks/CSharp/touchsocket/config.toml b/frameworks/CSharp/touchsocket/config.toml index 9af10d4a486..0d06cb4e331 100644 --- a/frameworks/CSharp/touchsocket/config.toml +++ b/frameworks/CSharp/touchsocket/config.toml @@ -52,3 +52,16 @@ orm = "Raw" platform = ".NET" webserver = "touchsocket.webapi31" versus = "aspcore-mvc" + +[httpplatform] +urls.plaintext = "/plaintext" +urls.json = "/json" +approach = "Realistic" +classification = "Micro" +database = "Postgres" +database_os = "Linux" +os = "Linux" +orm = "Raw" +platform = ".NET" +webserver = "touchsocket.httpplatform" +versus = "aspcore" diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketHttp/Program.cs b/frameworks/CSharp/touchsocket/src/TouchSocketHttp/Program.cs index d77a1b3249d..baa00f9da87 100644 --- a/frameworks/CSharp/touchsocket/src/TouchSocketHttp/Program.cs +++ b/frameworks/CSharp/touchsocket/src/TouchSocketHttp/Program.cs @@ -1,7 +1,6 @@ using System.Text; using TouchSocket.Core; using TouchSocket.Http; -using TouchSocket.Sockets; using HttpContent = TouchSocket.Http.HttpContent; namespace TouchSocketHttp; @@ -18,7 +17,7 @@ await service.SetupAsync(new TouchSocketConfig() .SetMaxCount(1000000) .SetTransportOption(options => { - options.BufferOnDemand = true; + options.BufferOnDemand = false; }) .ConfigureContainer(a => { diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketHttp/TouchSocketHttp.csproj b/frameworks/CSharp/touchsocket/src/TouchSocketHttp/TouchSocketHttp.csproj index ea6f43327a0..073c2d99413 100644 --- a/frameworks/CSharp/touchsocket/src/TouchSocketHttp/TouchSocketHttp.csproj +++ b/frameworks/CSharp/touchsocket/src/TouchSocketHttp/TouchSocketHttp.csproj @@ -1,18 +1,14 @@ - + net9.0 enable Exe enable - true - dotnet-TouchSocketWebApi-987c185f-10b1-452b-beb6-47d798a5a131 - - - + diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/DateHeader.cs b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/DateHeader.cs new file mode 100644 index 00000000000..9f0049fc54d --- /dev/null +++ b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/DateHeader.cs @@ -0,0 +1,73 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers.Text; +using System.Diagnostics; +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace TouchSocketHttpPlatform; + +internal static class DateHeader +{ + private const int dateTimeRLength = 29; + private const int prefixLength = 6; // "Date: ".Length is 6 in bytes for ASCII + private const int suffixIndex = dateTimeRLength + prefixLength; + + // Wed, 14 Mar 2018 14:20:00 GMT + private const int suffixLength = 2; // crlf + private static readonly Timer s_timer = new((s) => + { + SetDateValues(DateTimeOffset.UtcNow); + }, null, 1000, 1000); + + private static byte[] s_headerBytesMaster = new byte[prefixLength + dateTimeRLength + 2 * suffixLength]; + private static byte[] s_headerBytesScratch = new byte[prefixLength + dateTimeRLength + 2 * suffixLength]; + + static DateHeader() + { + var utf8 = "Date: "u8; + + utf8.CopyTo(s_headerBytesMaster); + utf8.CopyTo(s_headerBytesScratch); + s_headerBytesMaster[suffixIndex] = (byte)'\r'; + s_headerBytesMaster[suffixIndex + 1] = (byte)'\n'; + s_headerBytesMaster[suffixIndex + 2] = (byte)'\r'; + s_headerBytesMaster[suffixIndex + 3] = (byte)'\n'; + s_headerBytesScratch[suffixIndex] = (byte)'\r'; + s_headerBytesScratch[suffixIndex + 1] = (byte)'\n'; + s_headerBytesScratch[suffixIndex + 2] = (byte)'\r'; + s_headerBytesScratch[suffixIndex + 3] = (byte)'\n'; + + SetDateValues(DateTimeOffset.UtcNow); + SyncDateTimer(); + } + + public static ReadOnlySpan HeaderBytes => s_headerBytesMaster; + + public static void SyncDateTimer() + { + s_timer.Change(1000, 1000); + } + private static void SetDateValues(DateTimeOffset value) + { + lock (s_headerBytesScratch) + { + if (!Utf8Formatter.TryFormat(value, s_headerBytesScratch.AsSpan(prefixLength), out var written, 'R')) + { + throw new Exception("date time format failed"); + } + Debug.Assert(written == dateTimeRLength); + (s_headerBytesScratch, s_headerBytesMaster) = (s_headerBytesMaster, s_headerBytesScratch); + } + } +} diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/MyServer.cs b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/MyServer.cs new file mode 100644 index 00000000000..714b8ff367b --- /dev/null +++ b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/MyServer.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Sockets; + +namespace TouchSocketHttpPlatform; + +internal sealed class MyServer : TcpService +{ + /// + protected override MyTcpSessionClientBase NewClient() + { + return new MyTcpSessionClientBase(); + } +} diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/MyTcpSessionClientBase.cs b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/MyTcpSessionClientBase.cs new file mode 100644 index 00000000000..1b95c2ddd72 --- /dev/null +++ b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/MyTcpSessionClientBase.cs @@ -0,0 +1,299 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using System.Buffers; +using System.IO.Pipelines; +using System.Runtime.CompilerServices; +using TouchSocket.Sockets; + +namespace TouchSocketHttpPlatform; + +/// +/// 路由类型 +/// +internal enum RouteType +{ + /// + /// 未知路由 + /// + Unknown, + + /// + /// 纯文本路由 + /// + Plaintext, + + /// + /// JSON路由 + /// + Json +} + +internal sealed class MyTcpSessionClientBase : TcpSessionClient +{ + #region Paths + public static ReadOnlySpan Json => "/json"u8; + public static ReadOnlySpan Plaintext => "/plaintext"u8; + #endregion + + + private static ReadOnlySpan PlainTextBody => "Hello, World!"u8; + private static ReadOnlySpan JsonBody => "{\"message\":\"Hello, World!\"}"u8; + + private static ReadOnlySpan PlaintextPreamble => + "HTTP/1.1 200 OK\r\n"u8 + + "Server: T\r\n"u8 + + "Content-Type: text/plain\r\n"u8 + + "Content-Length: 13\r\n"u8; + + private static ReadOnlySpan JsonPreamble => + "HTTP/1.1 200 OK\r\n"u8 + + "Server: T\r\n"u8 + + "Content-Type: application/json\r\n"u8 + + "Content-Length: 27\r\n"u8; + + protected override async Task ReceiveLoopAsync(ITransport transport) + { + var pipeReader = transport.Reader; + var pipeWriter = transport.Writer; + + while (true) + { + var readResult = await pipeReader.ReadAsync(); + var bufferSequence = readResult.Buffer; + + var totalConsumed = ProcessRequests(bufferSequence, pipeWriter, out var responseCount); + + if (responseCount > 0) + { + await pipeWriter.FlushAsync(); + } + + if (totalConsumed > 0) + { + pipeReader.AdvanceTo(bufferSequence.GetPosition(totalConsumed)); + } + else + { + pipeReader.AdvanceTo(bufferSequence.Start, bufferSequence.End); + } + + if (readResult.IsCompleted) + { + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long ProcessRequests(ReadOnlySequence buffer, PipeWriter writer, out int responseCount) + { + var seqReader = new SequenceReader(buffer); + var totalConsumed = 0L; + responseCount = 0; + + while (!seqReader.End) + { + var startConsumed = seqReader.Consumed; + + // 请求行 + if (!TryReadLine(ref seqReader, out var requestLineLength)) + { + // 请求行不完整,不消费任何数据 + break; + } + + var requestLineConsumed = requestLineLength + 2; + + // 读取Headers,直到空行;若不完整,回退并等待更多数据 + var headersStartConsumed = seqReader.Consumed; + bool headersComplete = false; + while (!seqReader.End) + { + if (!TryReadLine(ref seqReader, out var headerLength)) + { + // 回退到读取Headers前的位置,等待更多数据 + var rewind = seqReader.Consumed - headersStartConsumed; + if (rewind > 0) + { + seqReader.Rewind(rewind); + } + headersComplete = false; + break; + } + + if (headerLength == 0) + { + headersComplete = true; + break; // headers 结束 + } + } + + if (!headersComplete) + { + // 不完整,等待更多数据 + break; + } + + // 解析URL - 直接在原始位置解析,避免Slice + var routeType = ParseUrlFast(buffer.Slice(startConsumed), requestLineConsumed); + + // 计算本次消费的字节数 + var consumed = seqReader.Consumed - startConsumed; + totalConsumed += consumed; + + // 写入响应 + WriteResponseSync(writer, routeType); + responseCount++; + } + + return totalConsumed; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteResponseSync(PipeWriter writer, RouteType routeType) + { + switch (routeType) + { + case RouteType.Plaintext: + writer.Write(PlaintextPreamble); + writer.Write(DateHeader.HeaderBytes); + writer.Write(PlainTextBody); + break; + case RouteType.Json: + writer.Write(JsonPreamble); + writer.Write(DateHeader.HeaderBytes); + writer.Write(JsonBody); + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryReadLine(ref SequenceReader reader, out long length) + { + var start = reader.Consumed; + + while (!reader.End) + { + if (reader.TryRead(out var b)) + { + if (b == '\r') + { + // 查看是否有'\n',若暂不可用则回退并判定不完整 + if (!reader.TryPeek(out var next)) + { + // 回退已读取的'\r' + reader.Rewind(1); + length = 0; + return false; + } + if (next == '\n') + { + // 消费'\n'并返回本行长度(不含CRLF) + reader.Advance(1); + length = reader.Consumed - start - 2; + return true; + } + } + } + } + + length = 0; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static RouteType ParseUrlFast(ReadOnlySequence sequence, long requestLineLength) + { + var reader = new SequenceReader(sequence); + + // 跳过方法 + var spaceCount = 0; + var urlStart = 0L; + var urlEnd = 0L; + + while (reader.Consumed < requestLineLength && !reader.End) + { + if (reader.TryRead(out var b)) + { + if (b == ' ') + { + spaceCount++; + if (spaceCount == 1) + { + urlStart = reader.Consumed; + } + else if (spaceCount == 2) + { + urlEnd = reader.Consumed - 1; + break; + } + } + } + } + + if (spaceCount < 2) + { + return RouteType.Unknown; + } + + var urlLength = urlEnd - urlStart; + + // 截取URL片段 + var startPos = sequence.GetPosition(urlStart); + var urlSlice = sequence.Slice(startPos, urlLength); + + // "/plaintext" + if (urlLength == Plaintext.Length) + { + if (urlSlice.IsSingleSegment) + { + if (urlSlice.FirstSpan.SequenceEqual(Plaintext)) + { + return RouteType.Plaintext; + } + } + else + { + Span tmp = stackalloc byte[(int)urlLength]; + urlSlice.CopyTo(tmp); + if (tmp.SequenceEqual(Plaintext)) + { + return RouteType.Plaintext; + } + } + } + // "/json" + else if (urlLength == Json.Length) + { + if (urlSlice.IsSingleSegment) + { + if (urlSlice.FirstSpan.SequenceEqual(Json)) + { + return RouteType.Json; + } + } + else + { + Span tmp = stackalloc byte[(int)urlLength]; + urlSlice.CopyTo(tmp); + if (tmp.SequenceEqual(Json)) + { + return RouteType.Json; + } + } + } + + return RouteType.Unknown; + } +} + diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/NuGet.Config b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/NuGet.Config new file mode 100644 index 00000000000..f05d5867888 --- /dev/null +++ b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/Program.cs b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/Program.cs new file mode 100644 index 00000000000..95576ee70b5 --- /dev/null +++ b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/Program.cs @@ -0,0 +1,39 @@ +// ------------------------------------------------------------------------------ +// 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 +// 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 +// CSDN博客:https://blog.csdn.net/qq_40374647 +// 哔哩哔哩视频:https://space.bilibili.com/94253567 +// Gitee源代码仓库:https://gitee.com/RRQM_Home +// Github源代码仓库:https://github.com/RRQM +// API首页:https://touchsocket.net/ +// 交流QQ群:234762506 +// 感谢您的下载和使用 +// ------------------------------------------------------------------------------ + +using TouchSocket.Core; +using TouchSocket.Sockets; + +namespace TouchSocketHttpPlatform; + +internal class Program +{ + private static async Task Main(string[] args) + { + var server = new MyServer(); + await server.SetupAsync(new TouchSocketConfig() + .SetListenIPHosts(8080) + .SetMaxCount(1000000) + .ConfigureContainer(a => + { + a.AddConsoleLogger(); + })); + + await server.StartAsync(); + Console.WriteLine("HTTP服务器已启动,端口: 8080"); + + while (true) + { + Console.ReadLine(); + } + } +} diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/TouchSocketHttpPlatform.csproj b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/TouchSocketHttpPlatform.csproj new file mode 100644 index 00000000000..8e9841293fc --- /dev/null +++ b/frameworks/CSharp/touchsocket/src/TouchSocketHttpPlatform/TouchSocketHttpPlatform.csproj @@ -0,0 +1,13 @@ + + + + Exe + net9.0 + enable + enable + + + + + + diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketWebApi/Program.cs b/frameworks/CSharp/touchsocket/src/TouchSocketWebApi/Program.cs index a7c9e6dcafe..4033ac03745 100644 --- a/frameworks/CSharp/touchsocket/src/TouchSocketWebApi/Program.cs +++ b/frameworks/CSharp/touchsocket/src/TouchSocketWebApi/Program.cs @@ -3,7 +3,6 @@ using TouchSocket.Core; using TouchSocket.Http; using TouchSocket.Rpc; -using TouchSocket.Sockets; using TouchSocket.WebApi; using HttpContent = TouchSocket.Http.HttpContent; @@ -21,7 +20,7 @@ public static void Main(string[] args) .SetMaxCount(1000000) .SetTransportOption(options => { - options.BufferOnDemand = true; + options.BufferOnDemand = false; }) .ConfigureContainer(a => { diff --git a/frameworks/CSharp/touchsocket/src/TouchSocketWebApi/TouchSocketWebApi.csproj b/frameworks/CSharp/touchsocket/src/TouchSocketWebApi/TouchSocketWebApi.csproj index d90651bb648..909c2f984ee 100644 --- a/frameworks/CSharp/touchsocket/src/TouchSocketWebApi/TouchSocketWebApi.csproj +++ b/frameworks/CSharp/touchsocket/src/TouchSocketWebApi/TouchSocketWebApi.csproj @@ -4,13 +4,12 @@ net9.0 enable enable - true dotnet-WorkerService1-19b37b17-6043-4334-ad9a-9e0e3c670da3 - - + + diff --git a/frameworks/CSharp/touchsocket/touchsocket-httpplatform.dockerfile b/frameworks/CSharp/touchsocket/touchsocket-httpplatform.dockerfile new file mode 100644 index 00000000000..c20b0434e76 --- /dev/null +++ b/frameworks/CSharp/touchsocket/touchsocket-httpplatform.dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /app +COPY src/TouchSocketHttpPlatform . +RUN dotnet publish -c Release -o out + +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS runtime + +WORKDIR /app +COPY --from=build /app/out ./ + +EXPOSE 8080 + +ENTRYPOINT ["dotnet", "TouchSocketHttpPlatform.dll"]