From c0998eebe00134e68a35e2ec431de31e01816080 Mon Sep 17 00:00:00 2001 From: Ali Yousefi Date: Wed, 25 Oct 2023 17:11:29 +0330 Subject: [PATCH] Support for hosting on linux --- .../EasyMicroservices.Laboratory.Tests.csproj | 18 ++ .../Engine/Net/BaseHandlerTest.cs | 44 ++-- .../Engine/Net/HostHttpHandlerTest.cs | 17 ++ .../EasyMicroservices.Laboratory.csproj | 18 +- .../Engine/Net/BaseHandler.cs | 25 ++- .../Engine/Net/Http/HostHttpHandler.cs | 69 ++++++ .../Engine/Net/Http/HostHttpHandlerBase.cs | 203 ++++++++++++++++++ .../Engine/Net/Http/HttpHandlerBase.cs | 4 + .../Engine/Net/TcpHandlerBase.cs | 4 + 9 files changed, 380 insertions(+), 22 deletions(-) create mode 100644 src/CSharp/EasyMicroservices.Laboratory.Tests/Engine/Net/HostHttpHandlerTest.cs create mode 100644 src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HostHttpHandler.cs create mode 100644 src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HostHttpHandlerBase.cs diff --git a/src/CSharp/EasyMicroservices.Laboratory.Tests/EasyMicroservices.Laboratory.Tests.csproj b/src/CSharp/EasyMicroservices.Laboratory.Tests/EasyMicroservices.Laboratory.Tests.csproj index e40f70f..1340ee8 100644 --- a/src/CSharp/EasyMicroservices.Laboratory.Tests/EasyMicroservices.Laboratory.Tests.csproj +++ b/src/CSharp/EasyMicroservices.Laboratory.Tests/EasyMicroservices.Laboratory.Tests.csproj @@ -19,4 +19,22 @@ + + + + 7.0.0 + + + + + + 7.0.0 + + + + + + 7.0.0 + + diff --git a/src/CSharp/EasyMicroservices.Laboratory.Tests/Engine/Net/BaseHandlerTest.cs b/src/CSharp/EasyMicroservices.Laboratory.Tests/Engine/Net/BaseHandlerTest.cs index 223d3f2..2eeb0bf 100644 --- a/src/CSharp/EasyMicroservices.Laboratory.Tests/Engine/Net/BaseHandlerTest.cs +++ b/src/CSharp/EasyMicroservices.Laboratory.Tests/Engine/Net/BaseHandlerTest.cs @@ -1,13 +1,12 @@ using EasyMicroservices.Laboratory.Constants; using EasyMicroservices.Laboratory.Engine; using EasyMicroservices.Laboratory.Engine.Net; -using EasyMicroservices.Laboratory.Engine.Net.Http; using EasyMicroservices.Laboratory.Models; using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; -using System.Text; using System.Threading.Tasks; using Xunit; @@ -15,7 +14,26 @@ namespace EasyMicroservice.Laboratory.Tests.Engine.Net { public abstract class BaseHandlerTest { + public HttpClient GetHttpClient() + { + HttpClient httpClient = default; +// if (System.Environment.OSVersion.Platform != PlatformID.Unix) +// { +//#if (NET452) +// httpClient = new HttpClient(); +//#else +// var handler = new WinHttpHandler(); +// httpClient = new HttpClient(handler); +//#endif +// } +// else + httpClient = new HttpClient(); +#if (!NET452 && !NET48) + httpClient.DefaultRequestVersion = HttpVersion.Version20; +#endif + return httpClient; + } protected abstract BaseHandler GetHandler(ResourceManager resourceManager); public string GetHttpResponseHeaders(string response) { @@ -49,7 +67,7 @@ public async Task CheckSimpleRequestAndResponse(string request, string response) resourceManager.Append(request, GetHttpResponseHeaders(response)); var port = await GetHandler(resourceManager).Start(); - HttpClient httpClient = new HttpClient(); + HttpClient httpClient = GetHttpClient(); var data = new StringContent(request); var httpResponse = await httpClient.PostAsync($"http://localhost:{port}", data); Assert.Equal(await httpResponse.Content.ReadAsStringAsync(), response); @@ -72,7 +90,7 @@ public async Task ConcurrentCheckSimpleRequestAndResponse(string request, string { all.Add(Task.Run(async () => { - HttpClient httpClient = new HttpClient(); + HttpClient httpClient = GetHttpClient(); var data = new StringContent(request); var httpResponse = await httpClient.PostAsync($"http://localhost:{port}", data); Assert.Equal(await httpResponse.Content.ReadAsStringAsync(), response); @@ -94,10 +112,10 @@ public async Task ConcurrentSingleHttpClientCheckSimpleRequestAndResponse(string ResourceManager resourceManager = new ResourceManager(); resourceManager.Append(request, GetHttpResponseHeaders(response)); var port = await GetHandler(resourceManager).Start(); - HttpClient httpClient = new HttpClient(); + HttpClient httpClient = GetHttpClient(); List> all = new List>(); - for (int i = 0; i < 100; i++) + for (int i = 0; i < 20; i++) { all.Add(Task.Run(async () => { @@ -120,7 +138,7 @@ public async Task CheckSimpleRequestToGiveMeFullRequestHeaderValue(string reques ResourceManager resourceManager = new ResourceManager(); var port = await GetHandler(resourceManager).Start(); response = response.Replace("*MyPort*", port.ToString()); - HttpClient httpClient = new HttpClient(); + HttpClient httpClient = GetHttpClient(); httpClient.DefaultRequestHeaders.ExpectContinue = false; httpClient.DefaultRequestHeaders.Add(RequestTypeHeaderConstants.RequestTypeHeader, RequestTypeHeaderConstants.GiveMeFullRequestHeaderValue); var data = new StringContent(request); @@ -140,14 +158,14 @@ public async Task CheckSimpleRequestToGiveMeLastFullRequestHeaderValue(string re ResourceManager resourceManager = new ResourceManager(); var port = await GetHandler(resourceManager).Start(); response = response.Replace("*MyPort*", port.ToString()); - HttpClient httpClient = new HttpClient(); + HttpClient httpClient = GetHttpClient(); httpClient.DefaultRequestHeaders.ExpectContinue = false; httpClient.DefaultRequestHeaders.Add(RequestTypeHeaderConstants.RequestTypeHeader, RequestTypeHeaderConstants.GiveMeFullRequestHeaderValue); var data = new StringContent(request); var httpResponse = await httpClient.PostAsync($"http://localhost:{port}", data); var textResponse = await httpResponse.Content.ReadAsStringAsync(); - httpClient = new HttpClient(); + httpClient = GetHttpClient(); httpClient.DefaultRequestHeaders.Add(RequestTypeHeaderConstants.RequestTypeHeader, RequestTypeHeaderConstants.GiveMeLastFullRequestHeaderValue); httpResponse = await httpClient.GetAsync($"http://localhost:{port}"); textResponse = await httpResponse.Content.ReadAsStringAsync(); @@ -185,7 +203,7 @@ public async Task CheckComplex(string request, string response, string simpleRes ResourceManager resourceManager = new ResourceManager(); resourceManager.Append(request, response); var port = await GetHandler(resourceManager).Start(); - HttpClient httpClient = new HttpClient(); + HttpClient httpClient = GetHttpClient(); httpClient.DefaultRequestHeaders.Add("x-amz-meta-title", "someTitle"); httpClient.DefaultRequestHeaders.Add("User-Agent", "aws-sdk-dotnet-coreclr/3.7.101.44 aws-sdk-dotnet-core/3.7.103.6 .NET_Core/6.0.11 OS/Microsoft_Windows_10.0.22000 ClientAsync"); httpClient.DefaultRequestHeaders.Add("amz-sdk-invocation-id", "guid"); @@ -259,19 +277,19 @@ public async Task CheckScope() client.DefaultRequestHeaders.Add("Authorization", "empty"); client.DefaultRequestHeaders.Add("Host", "s3.eu-west-1.amazonaws.com"); }; - HttpClient httpClient = new HttpClient(); + HttpClient httpClient = GetHttpClient(); addHeaders(httpClient); var httpResponse = await httpClient.PutAsync($"http://localhost:{port}", null); var textResponse = await httpResponse.Content.ReadAsStringAsync(); Assert.Equal("Ali", textResponse); - httpClient = new HttpClient(); + httpClient = GetHttpClient(); addHeaders(httpClient); httpResponse = await httpClient.PutAsync($"http://localhost:{port}", null); textResponse = await httpResponse.Content.ReadAsStringAsync(); Assert.Equal("Reza", textResponse); - httpClient = new HttpClient(); + httpClient = GetHttpClient(); addHeaders(httpClient); httpResponse = await httpClient.PutAsync($"http://localhost:{port}", null); textResponse = await httpResponse.Content.ReadAsStringAsync(); diff --git a/src/CSharp/EasyMicroservices.Laboratory.Tests/Engine/Net/HostHttpHandlerTest.cs b/src/CSharp/EasyMicroservices.Laboratory.Tests/Engine/Net/HostHttpHandlerTest.cs new file mode 100644 index 0000000..cea81f1 --- /dev/null +++ b/src/CSharp/EasyMicroservices.Laboratory.Tests/Engine/Net/HostHttpHandlerTest.cs @@ -0,0 +1,17 @@ +#if(NET6_0_OR_GREATER) +using EasyMicroservice.Laboratory.Tests.Engine.Net; +using EasyMicroservices.Laboratory.Engine; +using EasyMicroservices.Laboratory.Engine.Net; +using EasyMicroservices.Laboratory.Engine.Net.Http; + +namespace EasyMicroservices.Laboratory.Tests.Engine.Net +{ + public class HostHttpHandlerTest : BaseHandlerTest + { + protected override BaseHandler GetHandler(ResourceManager resourceManager) + { + return new HostHttpHandler(resourceManager); + } + } +} +#endif \ No newline at end of file diff --git a/src/CSharp/EasyMicroservices.Laboratory/EasyMicroservices.Laboratory.csproj b/src/CSharp/EasyMicroservices.Laboratory/EasyMicroservices.Laboratory.csproj index 5526dcb..b5508a1 100644 --- a/src/CSharp/EasyMicroservices.Laboratory/EasyMicroservices.Laboratory.csproj +++ b/src/CSharp/EasyMicroservices.Laboratory/EasyMicroservices.Laboratory.csproj @@ -4,7 +4,7 @@ AnyCPU;x64;x86 EasyMicroservices true - 0.0.0.14 + 0.0.0.15 Laboratory of http client. EasyMicroservices@gmail.com test,tests,http,https,httpclient,laboratory @@ -19,4 +19,20 @@ + + + 7.0.1 + + + + + 7.0.1 + + + + + 7.0.1 + + + \ No newline at end of file diff --git a/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/BaseHandler.cs b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/BaseHandler.cs index d7ed3e0..159f422 100644 --- a/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/BaseHandler.cs +++ b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/BaseHandler.cs @@ -1,12 +1,5 @@ -using EasyMicroservices.Laboratory.Constants; +using EasyMicroservices.Laboratory.Engine.Net.Http; using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; using System.Threading.Tasks; namespace EasyMicroservices.Laboratory.Engine.Net @@ -55,5 +48,21 @@ public int GetRandomPort() { return _random.Next(1111, 9999); } + /// + /// + /// + /// + /// + public static BaseHandler CreateOSHandler(ResourceManager resourceManager) + { +#if (NET6_0_OR_GREATER) + if (System.Environment.OSVersion.Platform == PlatformID.Unix) + return new HostHttpHandler(resourceManager); + else + return new HttpHandler(resourceManager); +#else + throw new NotSupportedException("Only support on net6.0 or grater!"); +#endif + } } } \ No newline at end of file diff --git a/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HostHttpHandler.cs b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HostHttpHandler.cs new file mode 100644 index 0000000..e33c3e6 --- /dev/null +++ b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HostHttpHandler.cs @@ -0,0 +1,69 @@ +#if(NET6_0_OR_GREATER) +using Microsoft.AspNetCore.Http; +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace EasyMicroservices.Laboratory.Engine.Net.Http +{ + /// + /// + /// + public class HostHttpHandler : HostHttpHandlerBase + { + /// + /// + /// + /// + public HostHttpHandler(ResourceManager resourceManager) : base(resourceManager) + { + + } + + /// + /// + /// + /// + /// + /// + protected override async Task HandleHttpClient(HttpContext httpClient) + { + var reader = new StreamReader(httpClient.Request.Body); + var requestBody = await reader.ReadToEndAsync(); + var firstLine = $"{httpClient.Request.Method} {httpClient.Request.Path} {httpClient.Request.Protocol}"; + var headers = httpClient.Request.Headers.ToDictionary((x) => x.Key, (v) => v.Value.FirstOrDefault()); + StringBuilder fullBody = new StringBuilder(); + fullBody.AppendLine(firstLine); + foreach (var item in headers.OrderBy(x => x.Key)) + { + fullBody.AppendLine($"{item.Key}: {item.Value}"); + } + fullBody.Append(requestBody); + var responseBody = await WriteResponseAsync(firstLine, headers, requestBody, fullBody); + using (var responseReader = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(responseBody)))) + { + await responseReader.ReadLineAsync(); + do + { + var line = await responseReader.ReadLineAsync(); + if (string.IsNullOrEmpty(line)) + break; + var header = line.Split(':'); + if (header[0].Equals("content-length", StringComparison.OrdinalIgnoreCase)) + continue; + httpClient.Response.Headers.Add(header[0], header[1]); + } + while (true); + var body = await responseReader.ReadToEndAsync(); + var bytes = Encoding.UTF8.GetBytes(body); + httpClient.Response.ContentLength = bytes.Length; + + await httpClient.Response.Body.WriteAsync(bytes, 0, bytes.Length); + } + } + } +} +#endif \ No newline at end of file diff --git a/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HostHttpHandlerBase.cs b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HostHttpHandlerBase.cs new file mode 100644 index 0000000..8e69bd3 --- /dev/null +++ b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HostHttpHandlerBase.cs @@ -0,0 +1,203 @@ +#if (NET6_0_OR_GREATER) +using EasyMicroservices.Laboratory.Constants; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace EasyMicroservices.Laboratory.Engine.Net.Http +{ + /// + /// + /// + public abstract class HostHttpHandlerBase : BaseHandler + { + /// + /// + /// + public HostHttpHandlerBase(ResourceManager resourceManager) : base(resourceManager) + { + } + + /// + /// Start the Http listener + /// + /// + /// + public override Task Start(int port) + { + return InternalStart(port); + } + + /// + /// start on any random port + /// + /// port to listen + public override async Task Start() + { + int port; + while (true) + { + port = GetRandomPort(); + try + { + await InternalStart(port); + break; + } + catch + { + } + } + return port; + } + + /// + /// + /// + public override void Stop() + { + + } + + /// + /// + /// + /// + /// + protected virtual async Task InternalStart(int port) + { + var builder = WebApplication.CreateBuilder(); + builder.WebHost.UseUrls($"http://*:{port}"); + var app = builder.Build(); + app.Use(async (context, next) => + { + await HandleHttpClient(context); + await next(context); + }); + await Task.WhenAny(app.RunAsync(null), Task.Delay(3000)); + } + + /// + /// Handle a Http client + /// + /// + /// + protected abstract Task HandleHttpClient(HttpContext httpClient); + + string _lastResponseBody = ""; + /// + /// + /// + /// + /// + /// + /// + /// + public async Task WriteResponseAsync(string firstLine, Dictionary requestHeaders, string requestBody, StringBuilder fullBody) + { + string responseBody = ""; + if (requestHeaders.TryGetValue(RequestTypeHeaderConstants.RequestTypeHeader, out string headerTypeValue)) + { + switch (headerTypeValue) + { + case RequestTypeHeaderConstants.GiveMeFullRequestHeaderValue: + { + responseBody = GetGiveMeFullRequestHeaderValueResponse(firstLine, requestHeaders, requestBody); + break; + } + case RequestTypeHeaderConstants.GiveMeLastFullRequestHeaderValue: + { + responseBody = _lastResponseBody; + break; + } + } + } + else + responseBody = await _requestHandler.FindResponseBody(fullBody.ToString()); + if (string.IsNullOrEmpty(responseBody)) + responseBody = GetNoResponse(firstLine, requestHeaders, requestBody); + _lastResponseBody = responseBody; + return responseBody; + } + + string GetGiveMeFullRequestHeaderValueResponse(string firstLine, Dictionary requestHeaders, string requestBody) + { + StringBuilder responseBuilder = new(); + StringBuilder bodyBuilder = new(); + responseBuilder.AppendLine(DefaultResponse()); + bodyBuilder.AppendLine(firstLine); + foreach (var header in requestHeaders.Where(x => !x.Key.Equals(RequestTypeHeaderConstants.RequestTypeHeader, StringComparison.OrdinalIgnoreCase)).OrderBy(x => x.Key)) + { + bodyBuilder.Append(header.Key); + bodyBuilder.Append(": "); + bodyBuilder.AppendLine(header.Value); + } + bodyBuilder.AppendLine(); + bodyBuilder.Append(requestBody); + + responseBuilder.AppendLine($"Content-Length: {bodyBuilder.Length}"); + responseBuilder.AppendLine(); + responseBuilder.Append(bodyBuilder); + + return responseBuilder.ToString(); + } + + string DefaultResponse() + { + return @$"HTTP/1.1 200 OK +Cache-Control: no-cache +Pragma: no-cache +Content-Type: text/plain; charset=utf-8 +Vary: Accept-Encoding"; + } + + string GetNoResponse(string firstLine, Dictionary requestHeaders, string requestBody) + { + StringBuilder stringBuilder = new StringBuilder(); + StringBuilder bodyBuilder = new(); + bodyBuilder.AppendLine(firstLine); + foreach (var header in requestHeaders.OrderBy(x => x.Key)) + { + bodyBuilder.Append(header.Key); + bodyBuilder.Append(": "); + bodyBuilder.AppendLine(header.Value); + } + bodyBuilder.AppendLine(); + bodyBuilder.Append(requestBody); + var defaultResponse = @$"HTTP/1.1 405 OK +Cache-Control: no-cache +Pragma: no-cache +Content-Type: text/plain; charset=utf-8 +Vary: Accept-Encoding"; + stringBuilder.AppendLine(defaultResponse); + stringBuilder.AppendLine($"Content-Length: {bodyBuilder.Length}"); + stringBuilder.AppendLine(); + stringBuilder.Append(bodyBuilder); + return stringBuilder.ToString(); + } + + string MergeRequest(string firstLine, Dictionary requestHeaders, string requestBody) + { + StringBuilder bodyBuilder = new(); + bodyBuilder.AppendLine(firstLine); + foreach (var header in requestHeaders) + { + bodyBuilder.Append(header.Key); + bodyBuilder.Append(": "); + bodyBuilder.AppendLine(header.Value); + } + bodyBuilder.AppendLine(); + bodyBuilder.Append(requestBody); + return bodyBuilder.ToString(); + } + } +} +#endif \ No newline at end of file diff --git a/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HttpHandlerBase.cs b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HttpHandlerBase.cs index c2123c0..afb9fb4 100644 --- a/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HttpHandlerBase.cs +++ b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/Http/HttpHandlerBase.cs @@ -38,6 +38,7 @@ public override Task Start(int port) public override async Task Start() { int port; + int retry = 0; while (true) { port = GetRandomPort(); @@ -48,6 +49,9 @@ public override async Task Start() } catch { + retry++; + if (retry > 5) + throw; } } return port; diff --git a/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/TcpHandlerBase.cs b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/TcpHandlerBase.cs index fbc0af7..0ba6346 100644 --- a/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/TcpHandlerBase.cs +++ b/src/CSharp/EasyMicroservices.Laboratory/Engine/Net/TcpHandlerBase.cs @@ -39,6 +39,7 @@ public override Task Start(int port) public override async Task Start() { int port; + int retry = 0; while (true) { port = GetRandomPort(); @@ -49,6 +50,9 @@ public override async Task Start() } catch { + retry++; + if (retry > 5) + throw; } } return port;