From ac50d0079367bae370b68d5da1b845af50dbf357 Mon Sep 17 00:00:00 2001 From: Tyler Kendrick <145080887+Tyler-R-Kendrick@users.noreply.github.com> Date: Fri, 10 Jan 2025 17:25:51 +0000 Subject: [PATCH 01/10] Added tests and cleaned up with SRP --- src/HttpListenerResponseDecorator.cs | 29 ++ src/IHttpListenerResponse.cs | 12 + src/Program.cs | 178 ++++++----- src/ProxyStreamWriter.cs | 49 +++ src/ProxyWorker.cs | 301 ++++++++---------- src/RequestData.cs | 11 +- src/SimpleL7Proxy.csproj | 1 + src/SimpleL7Proxy.sln | 6 + .../FakeHttpListenerResponse.cs | 12 + .../ProxyStreamWriterTestFixture.cs | 38 +++ test/ProxyWorkerTests/ProxyWorkerTest.cs | 25 -- test/ProxyWorkerTests/Tests.csproj | 1 + 12 files changed, 384 insertions(+), 279 deletions(-) create mode 100644 src/HttpListenerResponseDecorator.cs create mode 100644 src/IHttpListenerResponse.cs create mode 100644 src/ProxyStreamWriter.cs create mode 100644 test/ProxyWorkerTests/FakeHttpListenerResponse.cs create mode 100644 test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs delete mode 100644 test/ProxyWorkerTests/ProxyWorkerTest.cs diff --git a/src/HttpListenerResponseDecorator.cs b/src/HttpListenerResponseDecorator.cs new file mode 100644 index 00000000..88d4995e --- /dev/null +++ b/src/HttpListenerResponseDecorator.cs @@ -0,0 +1,29 @@ +using System.Net; + +namespace SimpleL7Proxy; + +public class HttpListenerResponseDecorator(HttpListenerResponse response) + : IHttpListenerResponse +{ + public int StatusCode + { + get => response.StatusCode; + set => response.StatusCode = value; + } + + public bool KeepAlive + { + get => response.KeepAlive; + set => response.KeepAlive = value; + } + + public long ContentLength64 + { + get => response.ContentLength64; + set => response.ContentLength64 = value; + } + + public WebHeaderCollection Headers => response.Headers; + + public Stream OutputStream => response.OutputStream; +} diff --git a/src/IHttpListenerResponse.cs b/src/IHttpListenerResponse.cs new file mode 100644 index 00000000..1594dc8b --- /dev/null +++ b/src/IHttpListenerResponse.cs @@ -0,0 +1,12 @@ +using System.Net; + +namespace SimpleL7Proxy; + +public interface IHttpListenerResponse +{ + int StatusCode { get; set; } + bool KeepAlive { get; set; } + long ContentLength64 { get; set; } + WebHeaderCollection Headers { get; } + Stream OutputStream { get; } +} diff --git a/src/Program.cs b/src/Program.cs index 35b7aa6c..4c347ba6 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,17 +1,13 @@  using System.Net; using System.Text; -using OS = System; using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.Extensibility; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.ApplicationInsights.WorkerService; using Microsoft.Extensions.Logging; -using System.Threading.Tasks; -using Azure.Identity; -using Azure.Core; using Microsoft.Extensions.Configuration; +using SimpleL7Proxy; // This code serves as the entry point for the .NET application. @@ -35,7 +31,7 @@ public class Program public string OAuthAudience { get; set; } =""; - public static async Task Main(string[] args) + public static async Task Main(string[] args) { var cancellationToken = cancellationTokenSource.Token; var backendOptions = LoadBackendOptions(); @@ -50,109 +46,118 @@ public static async Task Main(string[] args) var logger = loggerFactory.CreateLogger(); Console.CancelKeyPress += (sender, e) => - { - Console.WriteLine("Shutdown signal received. Initiating shutdown..."); - e.Cancel = true; // Prevent the process from terminating immediately. - cancellationTokenSource.Cancel(); // Signal the application to shut down. - }; + { + Console.WriteLine("Shutdown signal received. Initiating shutdown..."); + e.Cancel = true; // Prevent the process from terminating immediately. + cancellationTokenSource.Cancel(); // Signal the application to shut down. + }; var hostBuilder = Host.CreateDefaultBuilder(args).ConfigureServices((hostContext, services) => - { - // Register the configured BackendOptions instance with DI - services.Configure(options => - { - options.Client = backendOptions.Client; - options.DefaultPriority = backendOptions.DefaultPriority; - options.DefaultTTLSecs = backendOptions.DefaultTTLSecs; - options.HostName = backendOptions.HostName; - options.Hosts = backendOptions.Hosts; - options.IDStr = backendOptions.IDStr; - options.LogHeaders = backendOptions.LogHeaders; - options.MaxQueueLength = backendOptions.MaxQueueLength; - options.OAuthAudience = backendOptions.OAuthAudience; - options.PriorityKeys = backendOptions.PriorityKeys; - options.PriorityValues = backendOptions.PriorityValues; - options.Port = backendOptions.Port; - options.PollInterval = backendOptions.PollInterval; - options.PollTimeout = backendOptions.PollTimeout; - options.SuccessRate = backendOptions.SuccessRate; - options.Timeout = backendOptions.Timeout; - options.UseOAuth = backendOptions.UseOAuth; - options.Workers = backendOptions.Workers; + { + // Register the configured BackendOptions instance with DI + services.Configure(options => + { + options.Client = backendOptions.Client; + options.DefaultPriority = backendOptions.DefaultPriority; + options.DefaultTTLSecs = backendOptions.DefaultTTLSecs; + options.HostName = backendOptions.HostName; + options.Hosts = backendOptions.Hosts; + options.IDStr = backendOptions.IDStr; + options.LogHeaders = backendOptions.LogHeaders; + options.MaxQueueLength = backendOptions.MaxQueueLength; + options.OAuthAudience = backendOptions.OAuthAudience; + options.PriorityKeys = backendOptions.PriorityKeys; + options.PriorityValues = backendOptions.PriorityValues; + options.Port = backendOptions.Port; + options.PollInterval = backendOptions.PollInterval; + options.PollTimeout = backendOptions.PollTimeout; + options.SuccessRate = backendOptions.SuccessRate; + options.Timeout = backendOptions.Timeout; + options.UseOAuth = backendOptions.UseOAuth; + options.Workers = backendOptions.Workers; + }); + + services.AddLogging(loggingBuilder => loggingBuilder.AddFilter("Category", LogLevel.Information)); + var aiConnectionString = Environment.GetEnvironmentVariable("APPINSIGHTS_CONNECTIONSTRING") ?? ""; + if (aiConnectionString != null) + { + services.AddApplicationInsightsTelemetryWorkerService((ApplicationInsightsServiceOptions options) => options.ConnectionString = aiConnectionString); + services.AddApplicationInsightsTelemetry(options => + { + options.EnableRequestTrackingTelemetryModule = true; }); + if (aiConnectionString != "") + Console.WriteLine("AppInsights initialized"); + } - services.AddLogging(loggingBuilder => loggingBuilder.AddFilter("Category", LogLevel.Information)); - var aiConnectionString = OS.Environment.GetEnvironmentVariable("APPINSIGHTS_CONNECTIONSTRING") ?? ""; - if (aiConnectionString != null) - { - services.AddApplicationInsightsTelemetryWorkerService((ApplicationInsightsServiceOptions options) => options.ConnectionString = aiConnectionString); - services.AddApplicationInsightsTelemetry(options => - { - options.EnableRequestTrackingTelemetryModule = true; - }); - if (aiConnectionString != "") - Console.WriteLine("AppInsights initialized"); - } - - var eventHubConnectionString = OS.Environment.GetEnvironmentVariable("EVENTHUB_CONNECTIONSTRING") ?? ""; - var eventHubName = OS.Environment.GetEnvironmentVariable("EVENTHUB_NAME") ?? ""; - var eventHubClient = new EventHubClient(eventHubConnectionString, eventHubName); - eventHubClient.StartTimer(); - - services.AddSingleton(provider => eventHubClient); - //services.AddHttpLogging(o => { }); - services.AddSingleton(backendOptions); - services.AddSingleton(); - services.AddSingleton(); - - - // Add other necessary service registrations here - }); - + var eventHubConnectionString = Environment.GetEnvironmentVariable("EVENTHUB_CONNECTIONSTRING") ?? ""; + var eventHubName = Environment.GetEnvironmentVariable("EVENTHUB_NAME") ?? ""; + EventHubClient eventHubClient = new(eventHubConnectionString, eventHubName); + eventHubClient.StartTimer(); + services.AddSingleton(provider => eventHubClient); + services.AddSingleton(backendOptions); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + }); + var frameworkHost = hostBuilder.Build(); - var serviceProvider =frameworkHost.Services; - + var serviceProvider = frameworkHost.Services; var backends = serviceProvider.GetRequiredService(); - //ILogger logger = serviceProvider.GetRequiredService>(); - try { - Program.telemetryClient = serviceProvider.GetRequiredService(); - if ( Program.telemetryClient != null) - Console.SetOut(new AppInsightsTextWriter(Program.telemetryClient, Console.Out)); - } catch (System.InvalidOperationException ) { + try + { + telemetryClient = serviceProvider.GetRequiredService(); + if (telemetryClient != null) + Console.SetOut(new AppInsightsTextWriter(telemetryClient, Console.Out)); + } + catch (InvalidOperationException) + { + //TODO: handle this. } backends.Start(cancellationToken); var server = serviceProvider.GetRequiredService(); var eventHubClient = serviceProvider.GetRequiredService(); - var tasks = new List(); + BlockingPriorityQueue queue; + IQueryable tasks; try { await backends.waitForStartup(20); // wait for up to 20 seconds for startup - var queue = server.Start(cancellationToken); + queue = server.Start(cancellationToken); queue.StartSignaler(cancellationToken); - + + var proxyStreamWriter = serviceProvider.GetRequiredService(); // startup Worker # of tasks - for (int i = 0; i < backendOptions.Workers; i++) - { - var pw = new ProxyWorker(cancellationToken, i, queue, backendOptions, backends, eventHubClient, telemetryClient); - tasks.Add( Task.Run(() => pw.TaskRunner(), cancellationToken)); - } - - } catch (Exception e) + tasks = Enumerable.Range(0, backendOptions.Workers) + .AsQueryable() + .Select(worker => new ProxyWorker( + worker, + proxyStreamWriter, + queue, + backendOptions, + backends, + eventHubClient, + telemetryClient, + cancellationToken)) + .Select(x => Task.Run(() => x.TaskRunner(), cancellationToken)); + } + catch (Exception e) { Console.WriteLine($"Exiting: {e.Message}"); ; - System.Environment.Exit(1); + Environment.Exit(1); + return 1; } - try { - await server.Run(); + try + { + var timeout = TimeSpan.FromSeconds(10); + var runTask = server.Run(); Console.WriteLine("Waiting for tasks to complete for maximum 10 seconds"); + await runTask; //.WaitAsync(timeout, cancellationToken); server.Queue().Stop(); - var timeoutTask = Task.Delay(10000); // 10 seconds timeout - var allTasks = Task.WhenAll(tasks); - var completedTask = await Task.WhenAny(allTasks, timeoutTask); + await Task.WhenAll(tasks).WaitAsync(timeout, cancellationToken); } catch (Exception e) { @@ -175,13 +180,14 @@ public static async Task Main(string[] args) // Handle other exceptions that might occur Console.WriteLine($"An unexpected error occurred: {e.Message}"); } + return 0; } // Rreads an environment variable and returns its value as an integer. // If the environment variable is not set, it returns the provided default value. private static int ReadEnvironmentVariableOrDefault(string variableName, int defaultValue) { - if (!int.TryParse(OS.Environment.GetEnvironmentVariable(variableName), out var value)) + if (!int.TryParse(Environment.GetEnvironmentVariable(variableName), out var value)) { Console.WriteLine($"Using default: {variableName}: {defaultValue}"); return defaultValue; diff --git a/src/ProxyStreamWriter.cs b/src/ProxyStreamWriter.cs new file mode 100644 index 00000000..3794051d --- /dev/null +++ b/src/ProxyStreamWriter.cs @@ -0,0 +1,49 @@ +using CommunityToolkit.Diagnostics; + +namespace SimpleL7Proxy; + +public class ProxyStreamWriter +{ + private void OverrideResponseHeaders( + IHttpListenerResponse response, + HttpResponseMessage responseMessage) + { + Guard.IsNotNull(response, nameof(response)); + Guard.IsNotNull(responseMessage, nameof(responseMessage)); + Guard.IsNotNull(responseMessage.Content, nameof(responseMessage.Content)); + Guard.IsNotNull(responseMessage.Content.Headers, nameof(responseMessage.Content.Headers)); + + // Set the response status code + response.StatusCode = (int)responseMessage.StatusCode; + //TODO: Why flag as false? + response.KeepAlive = false; + + var contentHeaders = responseMessage.Content.Headers; + foreach (var header in contentHeaders) + { + response.Headers[header.Key] = string.Join(", ", header.Value); + if (header.Key.ToLower().Equals("content-length")) + { + response.ContentLength64 = contentHeaders.ContentLength ?? 0; + } + } + } + + public async Task WriteResponseDataAsync( + IHttpListenerResponse response, + ProxyData proxyData, + CancellationToken token) + { + var responseMessage = proxyData.ResponseMessage; + OverrideResponseHeaders(response, responseMessage); + + var outputStream = response.OutputStream; + var content = responseMessage.Content; + if (content != null) + { + await using var responseStream = await content.ReadAsStreamAsync(token).ConfigureAwait(false); + await responseStream.CopyToAsync(outputStream, token).ConfigureAwait(false); + await outputStream.FlushAsync(token).ConfigureAwait(false); + } + } +} diff --git a/src/ProxyWorker.cs b/src/ProxyWorker.cs index 8111404b..3e12a165 100644 --- a/src/ProxyWorker.cs +++ b/src/ProxyWorker.cs @@ -1,24 +1,19 @@ using System.Collections.Specialized; using System.Net; using System.Net.Http.Headers; -using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; +using CommunityToolkit.Diagnostics; using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.DataContracts; +namespace SimpleL7Proxy; -// The ProxyWorker class has the following main objectives: -// 1. Read incoming requests from the queue, prioritizing the highest priority requests. -// 2. Proxy the request to the backend with the lowest latency. -// 3. Retry against the next backend if the current backend fails. -// 4. Return a 502 Bad Gateway if all backends fail. -// 5. Return a 200 OK with backend server stats if the request is for /health. -// 6. Log telemetry data for each request. public class ProxyWorker { private static bool _debug = false; private CancellationToken _cancellationToken; + private readonly ProxyStreamWriter _proxyStreamWriter; private BlockingPriorityQueue? _requestsQueue; private readonly IBackendService _backends; private readonly BackendOptions _options; @@ -26,9 +21,18 @@ public class ProxyWorker private readonly IEventHubClient? _eventHubClient; private readonly string IDstr = ""; - public ProxyWorker(CancellationToken cancellationToken, int ID, BlockingPriorityQueue requestsQueue, BackendOptions backendOptions, IBackendService? backends, IEventHubClient? eventHubClient, TelemetryClient? telemetryClient) + public ProxyWorker( + int ID, + ProxyStreamWriter proxyStreamWriter, + BlockingPriorityQueue requestsQueue, + BackendOptions backendOptions, + IBackendService? backends, + IEventHubClient? eventHubClient, + TelemetryClient? telemetryClient, + CancellationToken cancellationToken) { _cancellationToken = cancellationToken; + _proxyStreamWriter = proxyStreamWriter; _requestsQueue = requestsQueue ?? throw new ArgumentNullException(nameof(requestsQueue)); _backends = backends ?? throw new ArgumentNullException(nameof(backends)); _eventHubClient = eventHubClient; @@ -75,7 +79,6 @@ public async Task TaskRunner() eventData["x-RequestContentLength"] = incomingRequest?.Headers["Content-Length"] ?? "N/A"; eventData["x-RequestWorker"] = IDstr; - if (lcontext == null || incomingRequest == null) { // Task.Yield(); // Yield to the scheduler to allow other tasks to run @@ -91,7 +94,7 @@ public async Task TaskRunner() lcontext.Response.Headers.Add("Cache-Control", "no-cache"); lcontext.Response.KeepAlive = false; - Byte[]? healthMessage = Encoding.UTF8.GetBytes(_backends?.HostStatus() ?? "OK"); + byte[]? healthMessage = Encoding.UTF8.GetBytes(_backends?.HostStatus() ?? "OK"); lcontext.Response.ContentLength64 = healthMessage.Length; await lcontext.Response.OutputStream.WriteAsync(healthMessage, 0, healthMessage.Length).ConfigureAwait(false); @@ -152,7 +155,7 @@ public async Task TaskRunner() requestWasRetried = true; incomingRequest.SkipDispose = true; eventData["Url"] = e.pr.FullURL; - eventData["x-Status"] = ((int)503).ToString(); + eventData["x-Status"] = 503.ToString(); eventData["x-Response-Latency"] = (e.pr.ResponseDate - incomingRequest.DequeueTime).TotalMilliseconds.ToString("F3"); eventData["x-Total-Latency"] = (DateTime.UtcNow - incomingRequest.EnqueueTime).TotalMilliseconds.ToString("F3"); eventData["x-Backend-Host"] = e.pr?.BackendHostname ?? "N/A"; @@ -187,9 +190,7 @@ public async Task TaskRunner() SendEventData(eventData); } - SendEventData(eventData); - } catch (Exception ex) { @@ -231,12 +232,11 @@ public async Task TaskRunner() } finally { - // Let's not track the request if it was retried. if (!requestWasRetried) { // Track the status of the request for circuit breaker - _backends.TrackStatus((int)lcontext.Response.StatusCode); + _backends.TrackStatus(lcontext.Response.StatusCode); _telemetryClient?.TrackRequest($"{incomingRequest.Method} {incomingRequest.Path}", DateTimeOffset.UtcNow, new TimeSpan(0, 0, 0), $"{lcontext.Response.StatusCode}", true); @@ -244,44 +244,13 @@ public async Task TaskRunner() lcontext?.Response.Close(); } - } - } } Console.WriteLine($"Worker {IDstr} stopped."); } - private static async Task WriteResponseDataAsync(HttpListenerContext context, - ProxyData pr, CancellationToken token) - { - // Set the response status code - context.Response.StatusCode = (int)pr.ResponseMessage.StatusCode; - - if (pr.ResponseMessage.Content.Headers != null) - { - foreach (var header in pr.ResponseMessage.Content.Headers) - { - context.Response.Headers[header.Key] = string.Join(", ", header.Value); - - if (header.Key.ToLower().Equals("content-length")) - { - context.Response.ContentLength64 = pr.ResponseMessage.Content.Headers.ContentLength ?? 0; - } - } - } - - context.Response.KeepAlive = false; - // Stream the response body to the client - if (pr.ResponseMessage.Content != null) - { - await using var responseStream = await pr.ResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); - await responseStream.CopyToAsync(context.Response.OutputStream).ConfigureAwait(false); - await context.Response.OutputStream.FlushAsync().ConfigureAwait(false); - } - } - public async Task ReadProxyAsync(RequestData request, CancellationToken cancellationToken) //DateTime requestDate, string method, string path, WebHeaderCollection headers, Stream body)//HttpListenerResponse downStreamResponse) { if (request == null) throw new ArgumentNullException(nameof(request), "Request cannot be null."); @@ -290,16 +259,16 @@ public async Task ReadProxyAsync(RequestData request, CancellationTok if (request.Method == null) throw new ArgumentNullException(nameof(request.Method), "Request method cannot be null."); var activeHosts = _backends.GetActiveHosts(); - request.Debug = _debug || (request.Headers["S7PDEBUG"] != null && string.Equals(request.Headers["S7PDEBUG"], "true", StringComparison.OrdinalIgnoreCase)); + request.Debug = _debug || request.Headers["S7PDEBUG"] != null && string.Equals(request.Headers["S7PDEBUG"], "true", StringComparison.OrdinalIgnoreCase); - byte[] bodyBytes = await request.CachBodyAsync().ConfigureAwait(false); + byte[] bodyBytes = await request.CacheBodyAsync().ConfigureAwait(false); HttpStatusCode lastStatusCode = HttpStatusCode.ServiceUnavailable; async Task WriteProxyDataAsync(ProxyData data, CancellationToken token) { var context = request.Context ?? throw new NullReferenceException("Request context cannot be null."); - data.ResponseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError); + data.ResponseMessage = new(HttpStatusCode.InternalServerError); await WriteResponseDataAsync(context, data, token); return data; @@ -309,13 +278,13 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke { if (!CalculateTTL(request)) { - HttpResponseMessage ttlResponseMessage = new(HttpStatusCode.InternalServerError); - - ttlResponseMessage.Content = new StringContent("Invalid TTL format: " + request.Headers["S7PTTL"], Encoding.UTF8); return await WriteProxyDataAsync(new() { StatusCode = HttpStatusCode.BadRequest, - ResponseMessage = ttlResponseMessage + ResponseMessage = new(HttpStatusCode.InternalServerError) + { + Content = new StringContent("Invalid TTL format: " + request.Headers["S7PTTL"], Encoding.UTF8) + } }, cancellationToken); } } @@ -323,15 +292,18 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke // Check ttlSeconds against current time .. keep in mind, client may have disconnected already if (request.TTLSeconds < DateTimeOffset.UtcNow.ToUnixTimeSeconds()) { - HandleProxyRequestError(null, null, request.Timestamp, request.FullURL, HttpStatusCode.Gone, "Request has expired: " + DateTimeOffset.UtcNow.ToLocalTime()); - HttpResponseMessage invalidTTLResponseMessage = new(HttpStatusCode.InternalServerError); - - invalidTTLResponseMessage.Content = new StringContent("Invalid TTL format: " + request.Headers["S7PTTL"], Encoding.UTF8); - + HandleProxyRequestError(null, null, + request.Timestamp, + request.FullURL, + HttpStatusCode.Gone, + "Request has expired: " + DateTimeOffset.UtcNow.ToLocalTime()); return await WriteProxyDataAsync(new() { StatusCode = HttpStatusCode.Gone, - ResponseMessage = invalidTTLResponseMessage + ResponseMessage = new(HttpStatusCode.InternalServerError) + { + Content = new StringContent("Invalid TTL format: " + request.Headers["S7PTTL"], Encoding.UTF8) + } }, cancellationToken); } @@ -347,7 +319,6 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke request.Headers.Set("Authorization", $"Bearer {token}"); } - //TODO: Parallelize this foreach (var host in activeHosts) { await Task.Yield(); @@ -355,121 +326,121 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke { request.Headers.Set("Host", host.host); var urlWithPath = new UriBuilder(host.url) { Path = request.Path }.Uri.AbsoluteUri; - request.FullURL = System.Net.WebUtility.UrlDecode(urlWithPath); + request.FullURL = WebUtility.UrlDecode(urlWithPath); - using (var bodyContent = new ByteArrayContent(bodyBytes)) - using (var proxyRequest = new HttpRequestMessage(new HttpMethod(request.Method), request.FullURL)) - { - proxyRequest.Content = bodyContent; - CopyHeaders(request.Headers, proxyRequest, true); + using ByteArrayContent bodyContent = new(bodyBytes); + using HttpRequestMessage proxyRequest = new(new(request.Method), request.FullURL); + proxyRequest.Content = bodyContent; + CopyHeaders(request.Headers, proxyRequest, true); - if (bodyBytes.Length > 0) - { - proxyRequest.Content.Headers.ContentLength = bodyBytes.Length; + if (bodyBytes.Length > 0) + { + proxyRequest.Content.Headers.ContentLength = bodyBytes.Length; - // Preserve the content type if it was provided - string contentType = request.Context?.Request.ContentType ?? "application/octet-stream"; // Default to application/octet-stream if not specified - var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); + // Preserve the content type if it was provided + string contentType = request.Context?.Request.ContentType ?? "application/octet-stream"; // Default to application/octet-stream if not specified + var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); - // Preserve the encoding type if it was provided - if (request.Context?.Request.ContentType != null && request.Context.Request.ContentType.Contains("charset")) - { - var charset = request.Context.Request.ContentType.Split(';').LastOrDefault(s => s.Trim().StartsWith("charset")); - if (charset != null) - { - mediaTypeHeaderValue.CharSet = charset.Split('=').Last().Trim(); - } - } - else + // Preserve the encoding type if it was provided + if (request.Context?.Request.ContentType != null && request.Context.Request.ContentType.Contains("charset")) + { + var charset = request.Context.Request.ContentType.Split(';').LastOrDefault(s => s.Trim().StartsWith("charset")); + if (charset != null) { - mediaTypeHeaderValue.CharSet = "utf-8"; + mediaTypeHeaderValue.CharSet = charset.Split('=').Last().Trim(); } - - proxyRequest.Content.Headers.ContentType = mediaTypeHeaderValue; } - - proxyRequest.Headers.ConnectionClose = true; - - if (request.Debug) + else { - Console.WriteLine($"> {request.Method} {request.FullURL} {bodyBytes.Length} bytes"); - LogHeaders(proxyRequest.Headers, ">"); - LogHeaders(proxyRequest.Content.Headers, " >"); + mediaTypeHeaderValue.CharSet = "utf-8"; } - var ProxyStartDate = DateTime.UtcNow; - using (var proxyResponse = await _options.Client.SendAsync(proxyRequest, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) - { - var responseDate = DateTime.UtcNow; - lastStatusCode = proxyResponse.StatusCode; + proxyRequest.Content.Headers.ContentType = mediaTypeHeaderValue; + } - if (((int)proxyResponse.StatusCode > 300 && (int)proxyResponse.StatusCode < 400) || (int)proxyResponse.StatusCode >= 500) - { - if (request.Debug) - { - try - { - //Why do this? - ProxyData temp_pr = new() - { - ResponseDate = responseDate, - StatusCode = proxyResponse.StatusCode, - FullURL = request.FullURL, - }; - bodyBytes = []; - Console.WriteLine($"Got: {temp_pr.StatusCode} {temp_pr.FullURL} {temp_pr.ContentHeaders["Content-Length"]} bytes"); - Console.WriteLine($"< {temp_pr?.Body}"); - } - catch (Exception e) - { - Console.WriteLine($"Error reading from backend host: {e.Message}"); - } - - Console.WriteLine($"Trying next host: Response: {proxyResponse.StatusCode}"); - } - continue; - } + proxyRequest.Headers.ConnectionClose = true; - host.AddPxLatency((responseDate - ProxyStartDate).TotalMilliseconds); + if (request.Debug) + { + Console.WriteLine($"> {request.Method} {request.FullURL} {bodyBytes.Length} bytes"); + LogHeaders(proxyRequest.Headers, ">"); + LogHeaders(proxyRequest.Content.Headers, " >"); + } - ProxyData pr = new() - { - ResponseDate = responseDate, - StatusCode = proxyResponse.StatusCode, - ResponseMessage = proxyResponse, - FullURL = request.FullURL, - CalculatedHostLatency = host.calculatedAverageLatency, - BackendHostname = host.host - }; - - if ((int)proxyResponse.StatusCode == 429 && proxyResponse.Headers.TryGetValues("S7PREQUEUE", out var values)) - { - // Requeue the request if the response is a 429 and the S7PREQUEUE header is set - var s7PrequeueValue = values.FirstOrDefault(); + var proxyStartDate = DateTime.UtcNow; + using var proxyResponse = await _options.Client!.SendAsync( + proxyRequest, + HttpCompletionOption.ResponseHeadersRead, + _cancellationToken) + .ConfigureAwait(false); + var responseDate = DateTime.UtcNow; + lastStatusCode = proxyResponse.StatusCode; - if (s7PrequeueValue != null && string.Equals(s7PrequeueValue, "true", StringComparison.OrdinalIgnoreCase)) + if ((int)proxyResponse.StatusCode > 300 && (int)proxyResponse.StatusCode < 400 || (int)proxyResponse.StatusCode >= 500) + { + if (request.Debug) + { + try + { + //Why do this? + ProxyData temp_pr = new() { - throw new S7PRequeueException("Requeue request", pr); - } + ResponseDate = responseDate, + StatusCode = proxyResponse.StatusCode, + FullURL = request.FullURL, + }; + bodyBytes = []; + Console.WriteLine($"Got: {temp_pr.StatusCode} {temp_pr.FullURL} {temp_pr.ContentHeaders["Content-Length"]} bytes"); + Console.WriteLine($"< {temp_pr?.Body}"); } - else + catch (Exception e) { - // request was successful, so we can disable the skip - request.SkipDispose = false; + Console.WriteLine($"Error reading from backend host: {e.Message}"); } - var context = request.Context - ?? throw new NullReferenceException("Request context cannot be null."); - // TODO: Move to caller to handle writing errors? - await WriteResponseDataAsync(context, pr, cancellationToken); + Console.WriteLine($"Trying next host: Response: {proxyResponse.StatusCode}"); + } + continue; + } - if (request.Debug) - { - Console.WriteLine($"Got: {pr.StatusCode} {pr.FullURL}"); - } - return pr; + host.AddPxLatency((responseDate - proxyStartDate).TotalMilliseconds); + + ProxyData pr = new() + { + ResponseDate = responseDate, + StatusCode = proxyResponse.StatusCode, + ResponseMessage = proxyResponse, + FullURL = request.FullURL, + CalculatedHostLatency = host.calculatedAverageLatency, + BackendHostname = host.host + }; + + if ((int)proxyResponse.StatusCode == 429 && proxyResponse.Headers.TryGetValues("S7PREQUEUE", out var values)) + { + // Requeue the request if the response is a 429 and the S7PREQUEUE header is set + var s7PrequeueValue = values.FirstOrDefault(); + + if (s7PrequeueValue != null && string.Equals(s7PrequeueValue, "true", StringComparison.OrdinalIgnoreCase)) + { + throw new S7PRequeueException("Requeue request", pr); } } + else + { + // request was successful, so we can disable the skip + request.SkipDispose = false; + } + + var context = request.Context + ?? throw new NullReferenceException("Request context cannot be null."); + // TODO: Move to caller to handle writing errors? + await WriteResponseDataAsync(context, pr, cancellationToken); + + if (request.Debug) + { + Console.WriteLine($"Got: {pr.StatusCode} {pr.FullURL}"); + } + return pr; } catch (S7PRequeueException) { @@ -504,19 +475,27 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke Console.WriteLine($"Error: {e.Message}"); lastStatusCode = HandleProxyRequestError(host, e, request.Timestamp, request.FullURL, HttpStatusCode.InternalServerError); } - } - HttpResponseMessage responseMessage = new(HttpStatusCode.InternalServerError); - - responseMessage.Content = new StringContent("No active hosts were able to handle the request.", Encoding.UTF8); return await WriteProxyDataAsync(new() { StatusCode = HttpStatusCode.BadGateway, - ResponseMessage = responseMessage + ResponseMessage = new(HttpStatusCode.InternalServerError) + { + Content = new StringContent("No active hosts were able to handle the request.", Encoding.UTF8) + } }, cancellationToken); } + private Task WriteResponseDataAsync( + HttpListenerContext context, + ProxyData data, + CancellationToken token) + { + HttpListenerResponseDecorator responseWrapper = new(context.Response); + return _proxyStreamWriter.WriteResponseDataAsync(responseWrapper, data, token); + } + // Returns false if the TTL is invalid else returns true private bool CalculateTTL(RequestData request) { @@ -560,7 +539,7 @@ private void CopyHeaders(NameValueCollection sourceHeaders, HttpRequestMessage? foreach (string? key in sourceHeaders.AllKeys) { if (key == null) continue; - if (!ignoreHeaders || (!key.StartsWith("S7P") && !key.StartsWith("X-MS-CLIENT", StringComparison.OrdinalIgnoreCase) && !key.Equals("content-length", StringComparison.OrdinalIgnoreCase))) + if (!ignoreHeaders || !key.StartsWith("S7P") && !key.StartsWith("X-MS-CLIENT", StringComparison.OrdinalIgnoreCase) && !key.Equals("content-length", StringComparison.OrdinalIgnoreCase)) { targetMessage?.Headers.TryAddWithoutValidation(key, sourceHeaders[key]); } @@ -613,4 +592,4 @@ private HttpStatusCode HandleProxyRequestError(BackendHost? host, Exception? e, host?.AddError(); return statusCode; } -} +} \ No newline at end of file diff --git a/src/RequestData.cs b/src/RequestData.cs index 9bc1552e..d610a456 100644 --- a/src/RequestData.cs +++ b/src/RequestData.cs @@ -48,7 +48,7 @@ public RequestData(HttpListenerContext context, string mid) MID = mid; } - public async Task CachBodyAsync() { + public async Task CacheBodyAsync() { if (BodyBytes != null) { @@ -61,12 +61,9 @@ public async Task CachBodyAsync() { } // Read the body stream once and reuse it - using (MemoryStream ms = new MemoryStream()) - { - await Body.CopyToAsync(ms); - BodyBytes = ms.ToArray(); - } - + using MemoryStream ms = new(); + await Body.CopyToAsync(ms); + BodyBytes = ms.ToArray(); return BodyBytes; } diff --git a/src/SimpleL7Proxy.csproj b/src/SimpleL7Proxy.csproj index 5421dcb9..2149feeb 100644 --- a/src/SimpleL7Proxy.csproj +++ b/src/SimpleL7Proxy.csproj @@ -13,6 +13,7 @@ + diff --git a/src/SimpleL7Proxy.sln b/src/SimpleL7Proxy.sln index 8aeeda2b..6da97ef9 100644 --- a/src/SimpleL7Proxy.sln +++ b/src/SimpleL7Proxy.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleL7Proxy", "SimpleL7Pr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "..\TestClient\TestClient.csproj", "{DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "..\test\ProxyWorkerTests\Tests.csproj", "{D510F7CC-3744-435D-B24B-6F04545E1D9D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Debug|Any CPU.Build.0 = Debug|Any CPU {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Release|Any CPU.ActiveCfg = Release|Any CPU {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Release|Any CPU.Build.0 = Release|Any CPU + {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/test/ProxyWorkerTests/FakeHttpListenerResponse.cs b/test/ProxyWorkerTests/FakeHttpListenerResponse.cs new file mode 100644 index 00000000..bcd5317c --- /dev/null +++ b/test/ProxyWorkerTests/FakeHttpListenerResponse.cs @@ -0,0 +1,12 @@ +using System.Net; + +namespace Tests; + +public class FakeHttpListenerResponse : IHttpListenerResponse +{ + public int StatusCode { get; set; } = 200; + public bool KeepAlive { get; set; } = false; + public long ContentLength64 { get; set; } = 1024; + public WebHeaderCollection Headers { get; } = []; + public Stream OutputStream { get; } = new MemoryStream(); +} \ No newline at end of file diff --git a/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs b/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs new file mode 100644 index 00000000..db71561e --- /dev/null +++ b/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs @@ -0,0 +1,38 @@ +using System.Net; +using System.Text; + +namespace Tests; + +[TestClass] +public class ProxyStreamWriterTestFixture +{ + [TestMethod] + public async Task StreamingYieldsPerChunkTest() + { + // Arrange + ProxyStreamWriter concern = new(); + var listenerResponse = new FakeHttpListenerResponse(); + const string proxyBody = "Hello, World!"; + ProxyData proxyData = new() + { + Body = Encoding.UTF8.GetBytes(proxyBody), + ResponseMessage = new(HttpStatusCode.OK) + { + Content = new StringContent(proxyBody) + } + }; + var token = CancellationToken.None; + + // Act + await concern.WriteResponseDataAsync(listenerResponse, proxyData, token); + + // Assert + var stream = listenerResponse.OutputStream; + Assert.IsTrue(stream.CanRead); + + stream.Seek(0, SeekOrigin.Begin); + using StreamReader streamReader = new(stream); + var results = await streamReader.ReadToEndAsync(); + Assert.AreEqual(proxyBody, results); + } +} diff --git a/test/ProxyWorkerTests/ProxyWorkerTest.cs b/test/ProxyWorkerTests/ProxyWorkerTest.cs deleted file mode 100644 index cd2154eb..00000000 --- a/test/ProxyWorkerTests/ProxyWorkerTest.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Tests; - -[TestClass] -public class ProxyWorkerTestFixture -{ - [TestMethod] - public async Task TestMethod() - { - var taskSignaler = new TaskSignaler(); - var task1 = taskSignaler.WaitForSignalAsync("task1"); - var task2 = taskSignaler.WaitForSignalAsync("task2"); - - var signalTask1 = taskSignaler.SignalRandomTask(42); - var signalTask2 = taskSignaler.SignalRandomTask(43); - - Assert.IsTrue(signalTask1); - Assert.IsTrue(signalTask2); - - var result1 = await task1; - var result2 = await task2; - - Assert.AreEqual(42, result1); - Assert.AreEqual(43, result2); - } -} \ No newline at end of file diff --git a/test/ProxyWorkerTests/Tests.csproj b/test/ProxyWorkerTests/Tests.csproj index 8573f9ac..f66af1ca 100644 --- a/test/ProxyWorkerTests/Tests.csproj +++ b/test/ProxyWorkerTests/Tests.csproj @@ -7,5 +7,6 @@ + \ No newline at end of file From 60ffab7f9b28dda4b87950cca886842f454f0da8 Mon Sep 17 00:00:00 2001 From: Tyler Kendrick <145080887+Tyler-R-Kendrick@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:11:44 +0000 Subject: [PATCH 02/10] cleaned program.cs --- src/Program.cs | 200 ++++++++++++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 94 deletions(-) diff --git a/src/Program.cs b/src/Program.cs index 4c347ba6..2e3e5b72 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -30,8 +30,7 @@ public class Program static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); public string OAuthAudience { get; set; } =""; - - public static async Task Main(string[] args) + public static async Task Main(string[] args) { var cancellationToken = cancellationTokenSource.Token; var backendOptions = LoadBackendOptions(); @@ -46,62 +45,62 @@ public static async Task Main(string[] args) var logger = loggerFactory.CreateLogger(); Console.CancelKeyPress += (sender, e) => - { - Console.WriteLine("Shutdown signal received. Initiating shutdown..."); - e.Cancel = true; // Prevent the process from terminating immediately. - cancellationTokenSource.Cancel(); // Signal the application to shut down. - }; - - var hostBuilder = Host.CreateDefaultBuilder(args).ConfigureServices((hostContext, services) => - { - // Register the configured BackendOptions instance with DI - services.Configure(options => { - options.Client = backendOptions.Client; - options.DefaultPriority = backendOptions.DefaultPriority; - options.DefaultTTLSecs = backendOptions.DefaultTTLSecs; - options.HostName = backendOptions.HostName; - options.Hosts = backendOptions.Hosts; - options.IDStr = backendOptions.IDStr; - options.LogHeaders = backendOptions.LogHeaders; - options.MaxQueueLength = backendOptions.MaxQueueLength; - options.OAuthAudience = backendOptions.OAuthAudience; - options.PriorityKeys = backendOptions.PriorityKeys; - options.PriorityValues = backendOptions.PriorityValues; - options.Port = backendOptions.Port; - options.PollInterval = backendOptions.PollInterval; - options.PollTimeout = backendOptions.PollTimeout; - options.SuccessRate = backendOptions.SuccessRate; - options.Timeout = backendOptions.Timeout; - options.UseOAuth = backendOptions.UseOAuth; - options.Workers = backendOptions.Workers; - }); + Console.WriteLine("Shutdown signal received. Initiating shutdown..."); + e.Cancel = true; // Prevent the process from terminating immediately. + cancellationTokenSource.Cancel(); // Signal the application to shut down. + }; - services.AddLogging(loggingBuilder => loggingBuilder.AddFilter("Category", LogLevel.Information)); - var aiConnectionString = Environment.GetEnvironmentVariable("APPINSIGHTS_CONNECTIONSTRING") ?? ""; - if (aiConnectionString != null) - { - services.AddApplicationInsightsTelemetryWorkerService((ApplicationInsightsServiceOptions options) => options.ConnectionString = aiConnectionString); - services.AddApplicationInsightsTelemetry(options => - { - options.EnableRequestTrackingTelemetryModule = true; + var hostBuilder = Host.CreateDefaultBuilder(args).ConfigureServices((hostContext, services) => + { + // Register the configured BackendOptions instance with DI + services.Configure(options => + { + options.Client = backendOptions.Client; + options.DefaultPriority = backendOptions.DefaultPriority; + options.DefaultTTLSecs = backendOptions.DefaultTTLSecs; + options.HostName = backendOptions.HostName; + options.Hosts = backendOptions.Hosts; + options.IDStr = backendOptions.IDStr; + options.LogHeaders = backendOptions.LogHeaders; + options.MaxQueueLength = backendOptions.MaxQueueLength; + options.OAuthAudience = backendOptions.OAuthAudience; + options.PriorityKeys = backendOptions.PriorityKeys; + options.PriorityValues = backendOptions.PriorityValues; + options.Port = backendOptions.Port; + options.PollInterval = backendOptions.PollInterval; + options.PollTimeout = backendOptions.PollTimeout; + options.SuccessRate = backendOptions.SuccessRate; + options.Timeout = backendOptions.Timeout; + options.UseOAuth = backendOptions.UseOAuth; + options.Workers = backendOptions.Workers; }); - if (aiConnectionString != "") - Console.WriteLine("AppInsights initialized"); - } - var eventHubConnectionString = Environment.GetEnvironmentVariable("EVENTHUB_CONNECTIONSTRING") ?? ""; - var eventHubName = Environment.GetEnvironmentVariable("EVENTHUB_NAME") ?? ""; - EventHubClient eventHubClient = new(eventHubConnectionString, eventHubName); - eventHubClient.StartTimer(); - - services.AddSingleton(provider => eventHubClient); - services.AddSingleton(backendOptions); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - }); + services.AddLogging(loggingBuilder => loggingBuilder.AddFilter("Category", LogLevel.Information)); + var aiConnectionString = Environment.GetEnvironmentVariable("APPINSIGHTS_CONNECTIONSTRING") ?? ""; + if (aiConnectionString != null) + { + services.AddApplicationInsightsTelemetryWorkerService((ApplicationInsightsServiceOptions options) => options.ConnectionString = aiConnectionString); + services.AddApplicationInsightsTelemetry(options => + { + options.EnableRequestTrackingTelemetryModule = true; + }); + if (aiConnectionString != "") + Console.WriteLine("AppInsights initialized"); + } + + var eventHubConnectionString = Environment.GetEnvironmentVariable("EVENTHUB_CONNECTIONSTRING") ?? ""; + var eventHubName = Environment.GetEnvironmentVariable("EVENTHUB_NAME") ?? ""; + EventHubClient eventHubClient = new(eventHubConnectionString, eventHubName); + eventHubClient.StartTimer(); + + services.AddSingleton(provider => eventHubClient); + services.AddSingleton(backendOptions); + services.AddSingleton(); + services.AddSingleton(); + }); + var frameworkHost = hostBuilder.Build(); var serviceProvider = frameworkHost.Services; var backends = serviceProvider.GetRequiredService(); @@ -113,51 +112,29 @@ public static async Task Main(string[] args) } catch (InvalidOperationException) { - //TODO: handle this. + //TODO: Handle this exception } backends.Start(cancellationToken); var server = serviceProvider.GetRequiredService(); var eventHubClient = serviceProvider.GetRequiredService(); - BlockingPriorityQueue queue; - IQueryable tasks; - try - { - await backends.waitForStartup(20); // wait for up to 20 seconds for startup - queue = server.Start(cancellationToken); - queue.StartSignaler(cancellationToken); - - var proxyStreamWriter = serviceProvider.GetRequiredService(); - // startup Worker # of tasks - tasks = Enumerable.Range(0, backendOptions.Workers) - .AsQueryable() - .Select(worker => new ProxyWorker( - worker, - proxyStreamWriter, - queue, - backendOptions, - backends, - eventHubClient, - telemetryClient, - cancellationToken)) - .Select(x => Task.Run(() => x.TaskRunner(), cancellationToken)); - } - catch (Exception e) - { - Console.WriteLine($"Exiting: {e.Message}"); ; - Environment.Exit(1); - return 1; - } + IEnumerable tasks = await QueueWork( + server, + eventHubClient, + backendOptions, + backends, + telemetryClient, + cancellationToken); try - { - var timeout = TimeSpan.FromSeconds(10); - var runTask = server.Run(); + { + await server.Run(); Console.WriteLine("Waiting for tasks to complete for maximum 10 seconds"); - await runTask; //.WaitAsync(timeout, cancellationToken); server.Queue().Stop(); - await Task.WhenAll(tasks).WaitAsync(timeout, cancellationToken); + var timeoutTask = Task.Delay(10000); // 10 seconds timeout + var allTasks = Task.WhenAll(tasks); + var completedTask = await Task.WhenAny(allTasks, timeoutTask); } catch (Exception e) { @@ -180,7 +157,6 @@ public static async Task Main(string[] args) // Handle other exceptions that might occur Console.WriteLine($"An unexpected error occurred: {e.Message}"); } - return 0; } // Rreads an environment variable and returns its value as an integer. @@ -209,15 +185,14 @@ private static string ReadEnvironmentVariableOrDefault(string variableName, stri } // Converts a comma-separated string to a list of strings. - private static List toListOfString(string s) + private static List ToListOfString(string s) { return s.Split(',').Select(p => p.Trim()).ToList(); } // Converts a comma-separated string to a list of integers. - private static List toListOfInt(string s) + private static List ToListOfInt(string s) { - // parse each value in the list List ints = new List(); foreach (var item in s.Split(',')) @@ -282,8 +257,8 @@ private static BackendOptions LoadBackendOptions() Port = ReadEnvironmentVariableOrDefault("Port", 443), PollInterval = ReadEnvironmentVariableOrDefault("PollInterval", 15000), PollTimeout = ReadEnvironmentVariableOrDefault("PollTimeout", 3000), - PriorityKeys = toListOfString(ReadEnvironmentVariableOrDefault("PriorityKeys", "12345,234")), - PriorityValues = toListOfInt(ReadEnvironmentVariableOrDefault("PriorityValues", "1,3")), + PriorityKeys = ToListOfString(ReadEnvironmentVariableOrDefault("PriorityKeys", "12345,234")), + PriorityValues = ToListOfInt(ReadEnvironmentVariableOrDefault("PriorityValues", "1,3")), SuccessRate = ReadEnvironmentVariableOrDefault("SuccessRate", 80), Timeout = ReadEnvironmentVariableOrDefault("Timeout", 3000), UseOAuth = ReadEnvironmentVariableOrDefault("UseOAuth", "false").Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true, @@ -349,4 +324,41 @@ private static BackendOptions LoadBackendOptions() return backendOptions; } + + private static async Task> QueueWork( + IServer server, + IEventHubClient eventHubClient, + BackendOptions backendOptions, + IBackendService backends, + TelemetryClient? telemetryClient, + CancellationToken cancellationToken) + { + try + { + await backends.waitForStartup(20); // wait for up to 20 seconds for startup + var queue = server.Start(cancellationToken); + queue.StartSignaler(cancellationToken); + + ProxyStreamWriter proxyStreamWriter = new(); + var queuedTasks = Enumerable.Range(0, backendOptions.Workers) + .AsQueryable() + .Select(worker => new ProxyWorker( + worker, + proxyStreamWriter, + queue, + backendOptions, + backends, + eventHubClient, + telemetryClient, + cancellationToken)) + .Select(t => Task.Run(() => t.TaskRunner(), cancellationToken)); + return [..queuedTasks]; + } + catch (Exception e) + { + Console.WriteLine($"Exiting: {e.Message}"); + Environment.Exit(1); + throw; + } + } } \ No newline at end of file From b9020649d5518e40f51b75cfa4cea86c29b96233 Mon Sep 17 00:00:00 2001 From: Pete Messina Date: Mon, 13 Jan 2025 11:19:37 -0500 Subject: [PATCH 03/10] Removing build for test project --- src/SimpleL7Proxy.sln | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/SimpleL7Proxy.sln b/src/SimpleL7Proxy.sln index 6da97ef9..d1a9ee1f 100644 --- a/src/SimpleL7Proxy.sln +++ b/src/SimpleL7Proxy.sln @@ -22,11 +22,9 @@ Global {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Debug|Any CPU.Build.0 = Debug|Any CPU {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Release|Any CPU.Build.0 = Release|Any CPU {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From aab1bfd4c1c7a5dcf87bb1856bcd67a03589fd1c Mon Sep 17 00:00:00 2001 From: Tyler Kendrick <145080887+Tyler-R-Kendrick@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:13:12 +0000 Subject: [PATCH 04/10] git move --- src/{ => SimpleL7Proxy}/AppInsightsTextWriter.cs | 0 src/{ => SimpleL7Proxy}/BackendHost.cs | 0 src/{ => SimpleL7Proxy}/BackendOptions.cs | 0 src/{ => SimpleL7Proxy}/Backends.cs | 0 src/{ => SimpleL7Proxy}/Dockerfile | 0 src/{ => SimpleL7Proxy}/EventHubClient.cs | 0 src/{ => SimpleL7Proxy}/HttpListenerResponseDecorator.cs | 0 src/{ => SimpleL7Proxy}/IHttpListenerResponse.cs | 0 src/{ => SimpleL7Proxy}/IServer.cs | 0 src/{ => SimpleL7Proxy}/Interfaces.cs | 0 src/{ => SimpleL7Proxy}/PriorityQueue.cs | 0 src/{ => SimpleL7Proxy}/Program.cs | 0 src/{ => SimpleL7Proxy}/ProxyData.cs | 0 src/{ => SimpleL7Proxy}/ProxyStreamWriter.cs | 0 src/{ => SimpleL7Proxy}/ProxyWorker.cs | 0 src/{ => SimpleL7Proxy}/RequestData.cs | 0 src/{ => SimpleL7Proxy}/S7PRequeueException.cs | 0 src/{ => SimpleL7Proxy}/SimpleL7Proxy.csproj | 0 src/{ => SimpleL7Proxy}/SimpleL7Proxy.sln | 0 src/{ => SimpleL7Proxy}/sample-deployment.yaml | 0 src/{ => SimpleL7Proxy}/server.cs | 0 21 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => SimpleL7Proxy}/AppInsightsTextWriter.cs (100%) rename src/{ => SimpleL7Proxy}/BackendHost.cs (100%) rename src/{ => SimpleL7Proxy}/BackendOptions.cs (100%) rename src/{ => SimpleL7Proxy}/Backends.cs (100%) rename src/{ => SimpleL7Proxy}/Dockerfile (100%) rename src/{ => SimpleL7Proxy}/EventHubClient.cs (100%) rename src/{ => SimpleL7Proxy}/HttpListenerResponseDecorator.cs (100%) rename src/{ => SimpleL7Proxy}/IHttpListenerResponse.cs (100%) rename src/{ => SimpleL7Proxy}/IServer.cs (100%) rename src/{ => SimpleL7Proxy}/Interfaces.cs (100%) rename src/{ => SimpleL7Proxy}/PriorityQueue.cs (100%) rename src/{ => SimpleL7Proxy}/Program.cs (100%) rename src/{ => SimpleL7Proxy}/ProxyData.cs (100%) rename src/{ => SimpleL7Proxy}/ProxyStreamWriter.cs (100%) rename src/{ => SimpleL7Proxy}/ProxyWorker.cs (100%) rename src/{ => SimpleL7Proxy}/RequestData.cs (100%) rename src/{ => SimpleL7Proxy}/S7PRequeueException.cs (100%) rename src/{ => SimpleL7Proxy}/SimpleL7Proxy.csproj (100%) rename src/{ => SimpleL7Proxy}/SimpleL7Proxy.sln (100%) rename src/{ => SimpleL7Proxy}/sample-deployment.yaml (100%) rename src/{ => SimpleL7Proxy}/server.cs (100%) diff --git a/src/AppInsightsTextWriter.cs b/src/SimpleL7Proxy/AppInsightsTextWriter.cs similarity index 100% rename from src/AppInsightsTextWriter.cs rename to src/SimpleL7Proxy/AppInsightsTextWriter.cs diff --git a/src/BackendHost.cs b/src/SimpleL7Proxy/BackendHost.cs similarity index 100% rename from src/BackendHost.cs rename to src/SimpleL7Proxy/BackendHost.cs diff --git a/src/BackendOptions.cs b/src/SimpleL7Proxy/BackendOptions.cs similarity index 100% rename from src/BackendOptions.cs rename to src/SimpleL7Proxy/BackendOptions.cs diff --git a/src/Backends.cs b/src/SimpleL7Proxy/Backends.cs similarity index 100% rename from src/Backends.cs rename to src/SimpleL7Proxy/Backends.cs diff --git a/src/Dockerfile b/src/SimpleL7Proxy/Dockerfile similarity index 100% rename from src/Dockerfile rename to src/SimpleL7Proxy/Dockerfile diff --git a/src/EventHubClient.cs b/src/SimpleL7Proxy/EventHubClient.cs similarity index 100% rename from src/EventHubClient.cs rename to src/SimpleL7Proxy/EventHubClient.cs diff --git a/src/HttpListenerResponseDecorator.cs b/src/SimpleL7Proxy/HttpListenerResponseDecorator.cs similarity index 100% rename from src/HttpListenerResponseDecorator.cs rename to src/SimpleL7Proxy/HttpListenerResponseDecorator.cs diff --git a/src/IHttpListenerResponse.cs b/src/SimpleL7Proxy/IHttpListenerResponse.cs similarity index 100% rename from src/IHttpListenerResponse.cs rename to src/SimpleL7Proxy/IHttpListenerResponse.cs diff --git a/src/IServer.cs b/src/SimpleL7Proxy/IServer.cs similarity index 100% rename from src/IServer.cs rename to src/SimpleL7Proxy/IServer.cs diff --git a/src/Interfaces.cs b/src/SimpleL7Proxy/Interfaces.cs similarity index 100% rename from src/Interfaces.cs rename to src/SimpleL7Proxy/Interfaces.cs diff --git a/src/PriorityQueue.cs b/src/SimpleL7Proxy/PriorityQueue.cs similarity index 100% rename from src/PriorityQueue.cs rename to src/SimpleL7Proxy/PriorityQueue.cs diff --git a/src/Program.cs b/src/SimpleL7Proxy/Program.cs similarity index 100% rename from src/Program.cs rename to src/SimpleL7Proxy/Program.cs diff --git a/src/ProxyData.cs b/src/SimpleL7Proxy/ProxyData.cs similarity index 100% rename from src/ProxyData.cs rename to src/SimpleL7Proxy/ProxyData.cs diff --git a/src/ProxyStreamWriter.cs b/src/SimpleL7Proxy/ProxyStreamWriter.cs similarity index 100% rename from src/ProxyStreamWriter.cs rename to src/SimpleL7Proxy/ProxyStreamWriter.cs diff --git a/src/ProxyWorker.cs b/src/SimpleL7Proxy/ProxyWorker.cs similarity index 100% rename from src/ProxyWorker.cs rename to src/SimpleL7Proxy/ProxyWorker.cs diff --git a/src/RequestData.cs b/src/SimpleL7Proxy/RequestData.cs similarity index 100% rename from src/RequestData.cs rename to src/SimpleL7Proxy/RequestData.cs diff --git a/src/S7PRequeueException.cs b/src/SimpleL7Proxy/S7PRequeueException.cs similarity index 100% rename from src/S7PRequeueException.cs rename to src/SimpleL7Proxy/S7PRequeueException.cs diff --git a/src/SimpleL7Proxy.csproj b/src/SimpleL7Proxy/SimpleL7Proxy.csproj similarity index 100% rename from src/SimpleL7Proxy.csproj rename to src/SimpleL7Proxy/SimpleL7Proxy.csproj diff --git a/src/SimpleL7Proxy.sln b/src/SimpleL7Proxy/SimpleL7Proxy.sln similarity index 100% rename from src/SimpleL7Proxy.sln rename to src/SimpleL7Proxy/SimpleL7Proxy.sln diff --git a/src/sample-deployment.yaml b/src/SimpleL7Proxy/sample-deployment.yaml similarity index 100% rename from src/sample-deployment.yaml rename to src/SimpleL7Proxy/sample-deployment.yaml diff --git a/src/server.cs b/src/SimpleL7Proxy/server.cs similarity index 100% rename from src/server.cs rename to src/SimpleL7Proxy/server.cs From f6bb8b93101e16d5f3728386f57f40eb425aa199 Mon Sep 17 00:00:00 2001 From: Tyler Kendrick <145080887+Tyler-R-Kendrick@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:17:46 +0000 Subject: [PATCH 05/10] fixed sln --- SimpleL7Proxy.sln | 81 +++++++++++++++++++ src-rx/Program.cs | 11 +++ src-rx/SimpleL7Proxy2.csproj | 28 +++++++ src/SimpleL7Proxy/ProxyWorker.cs | 1 - src/SimpleL7Proxy/SimpleL7Proxy.sln | 35 -------- test/ProxyWorkerTests/Tests.csproj | 2 +- test/generator/generator_one/appsettings.json | 2 +- 7 files changed, 122 insertions(+), 38 deletions(-) create mode 100644 SimpleL7Proxy.sln create mode 100644 src-rx/Program.cs create mode 100644 src-rx/SimpleL7Proxy2.csproj delete mode 100644 src/SimpleL7Proxy/SimpleL7Proxy.sln diff --git a/SimpleL7Proxy.sln b/SimpleL7Proxy.sln new file mode 100644 index 00000000..73b8d0b5 --- /dev/null +++ b/SimpleL7Proxy.sln @@ -0,0 +1,81 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestClient", "TestClient\TestClient.csproj", "{98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleL7Proxy2", "src-rx\SimpleL7Proxy2.csproj", "{90879664-7F50-411F-B1EA-B805918E43B8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2F43777E-BC06-4A84-9D37-76E9FE34E6BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleL7Proxy", "src\SimpleL7Proxy\SimpleL7Proxy.csproj", "{30819770-EC5D-4D6F-8C98-A6780E4633A1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A9F0DA11-B3E4-4060-9A76-10B37D3B09CA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "test\ProxyWorkerTests\Tests.csproj", "{8ED7BB66-0076-43B0-832C-600C2F62E66D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nullserver", "nullserver", "{3DE5977E-1C8E-461E-9AD2-DD9E57ABADD1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nullserver", "test\nullserver\nullserver\nullserver.csproj", "{56F72749-1C09-49EE-90E2-A11D571AE1AA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "generator", "generator", "{5C8DAEFB-1752-4182-988E-AAB97ABE1F28}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "generator_one", "test\generator\generator_one\generator_one.csproj", "{B1136BDF-743B-4581-9F6A-EDED51F4D00F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "identity-test", "identity-test", "{3460927E-7A06-4E4D-A5ED-1F6A0D4E2D52}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test-identity", "test\identity-test\dotnet\test-identity.csproj", "{0594D29A-88EE-4D53-AC9F-A2980438999F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}.Release|Any CPU.Build.0 = Release|Any CPU + {90879664-7F50-411F-B1EA-B805918E43B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90879664-7F50-411F-B1EA-B805918E43B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90879664-7F50-411F-B1EA-B805918E43B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90879664-7F50-411F-B1EA-B805918E43B8}.Release|Any CPU.Build.0 = Release|Any CPU + {30819770-EC5D-4D6F-8C98-A6780E4633A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30819770-EC5D-4D6F-8C98-A6780E4633A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30819770-EC5D-4D6F-8C98-A6780E4633A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30819770-EC5D-4D6F-8C98-A6780E4633A1}.Release|Any CPU.Build.0 = Release|Any CPU + {8ED7BB66-0076-43B0-832C-600C2F62E66D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8ED7BB66-0076-43B0-832C-600C2F62E66D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8ED7BB66-0076-43B0-832C-600C2F62E66D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8ED7BB66-0076-43B0-832C-600C2F62E66D}.Release|Any CPU.Build.0 = Release|Any CPU + {56F72749-1C09-49EE-90E2-A11D571AE1AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56F72749-1C09-49EE-90E2-A11D571AE1AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56F72749-1C09-49EE-90E2-A11D571AE1AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56F72749-1C09-49EE-90E2-A11D571AE1AA}.Release|Any CPU.Build.0 = Release|Any CPU + {B1136BDF-743B-4581-9F6A-EDED51F4D00F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1136BDF-743B-4581-9F6A-EDED51F4D00F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1136BDF-743B-4581-9F6A-EDED51F4D00F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1136BDF-743B-4581-9F6A-EDED51F4D00F}.Release|Any CPU.Build.0 = Release|Any CPU + {0594D29A-88EE-4D53-AC9F-A2980438999F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0594D29A-88EE-4D53-AC9F-A2980438999F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0594D29A-88EE-4D53-AC9F-A2980438999F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0594D29A-88EE-4D53-AC9F-A2980438999F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {30819770-EC5D-4D6F-8C98-A6780E4633A1} = {2F43777E-BC06-4A84-9D37-76E9FE34E6BE} + {8ED7BB66-0076-43B0-832C-600C2F62E66D} = {A9F0DA11-B3E4-4060-9A76-10B37D3B09CA} + {3DE5977E-1C8E-461E-9AD2-DD9E57ABADD1} = {A9F0DA11-B3E4-4060-9A76-10B37D3B09CA} + {56F72749-1C09-49EE-90E2-A11D571AE1AA} = {3DE5977E-1C8E-461E-9AD2-DD9E57ABADD1} + {5C8DAEFB-1752-4182-988E-AAB97ABE1F28} = {A9F0DA11-B3E4-4060-9A76-10B37D3B09CA} + {B1136BDF-743B-4581-9F6A-EDED51F4D00F} = {5C8DAEFB-1752-4182-988E-AAB97ABE1F28} + {3460927E-7A06-4E4D-A5ED-1F6A0D4E2D52} = {A9F0DA11-B3E4-4060-9A76-10B37D3B09CA} + {0594D29A-88EE-4D53-AC9F-A2980438999F} = {3460927E-7A06-4E4D-A5ED-1F6A0D4E2D52} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4FFFA080-AA7B-44E5-BF2D-BA66D6396B12} + EndGlobalSection +EndGlobal diff --git a/src-rx/Program.cs b/src-rx/Program.cs new file mode 100644 index 00000000..559118ca --- /dev/null +++ b/src-rx/Program.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddSingleton(); +var host = builder.Build(); +await host.RunAsync(); + +public class Worker +{ +} \ No newline at end of file diff --git a/src-rx/SimpleL7Proxy2.csproj b/src-rx/SimpleL7Proxy2.csproj new file mode 100644 index 00000000..06021b51 --- /dev/null +++ b/src-rx/SimpleL7Proxy2.csproj @@ -0,0 +1,28 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SimpleL7Proxy/ProxyWorker.cs b/src/SimpleL7Proxy/ProxyWorker.cs index 3e12a165..9a47e47e 100644 --- a/src/SimpleL7Proxy/ProxyWorker.cs +++ b/src/SimpleL7Proxy/ProxyWorker.cs @@ -3,7 +3,6 @@ using System.Net.Http.Headers; using System.Text; using System.Text.Json; -using CommunityToolkit.Diagnostics; using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.DataContracts; diff --git a/src/SimpleL7Proxy/SimpleL7Proxy.sln b/src/SimpleL7Proxy/SimpleL7Proxy.sln deleted file mode 100644 index d1a9ee1f..00000000 --- a/src/SimpleL7Proxy/SimpleL7Proxy.sln +++ /dev/null @@ -1,35 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.002.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleL7Proxy", "SimpleL7Proxy.csproj", "{63C0EB3E-5382-432A-8CD1-06453DA72041}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "..\TestClient\TestClient.csproj", "{DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "..\test\ProxyWorkerTests\Tests.csproj", "{D510F7CC-3744-435D-B24B-6F04545E1D9D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {63C0EB3E-5382-432A-8CD1-06453DA72041}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63C0EB3E-5382-432A-8CD1-06453DA72041}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63C0EB3E-5382-432A-8CD1-06453DA72041}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63C0EB3E-5382-432A-8CD1-06453DA72041}.Release|Any CPU.Build.0 = Release|Any CPU - {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE017BF2-A6C4-4492-9E61-B4CDAB685DC8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D510F7CC-3744-435D-B24B-6F04545E1D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {EE590C69-9433-4492-B8B0-5EE6D5D0CBDB} - EndGlobalSection -EndGlobal diff --git a/test/ProxyWorkerTests/Tests.csproj b/test/ProxyWorkerTests/Tests.csproj index f66af1ca..2f0215f9 100644 --- a/test/ProxyWorkerTests/Tests.csproj +++ b/test/ProxyWorkerTests/Tests.csproj @@ -6,7 +6,7 @@ - + \ No newline at end of file diff --git a/test/generator/generator_one/appsettings.json b/test/generator/generator_one/appsettings.json index 7d2d2299..02126f06 100644 --- a/test/generator/generator_one/appsettings.json +++ b/test/generator/generator_one/appsettings.json @@ -1,5 +1,5 @@ { - "test_endpoint": "http://localhost:8000", + "test_endpoint": "http://localhost:7002", "duration_seconds": "5s", "concurrency": 100, "interrun_delay": "50ms", From f549ac00047d52f6b27ad0900f7b264bd8791a01 Mon Sep 17 00:00:00 2001 From: Tyler Kendrick <145080887+Tyler-R-Kendrick@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:19:56 +0000 Subject: [PATCH 06/10] boyscouting/ --- src/SimpleL7Proxy/IServer.cs | 1 - src/SimpleL7Proxy/Program.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/SimpleL7Proxy/IServer.cs b/src/SimpleL7Proxy/IServer.cs index 24f3bcf2..db7831fd 100644 --- a/src/SimpleL7Proxy/IServer.cs +++ b/src/SimpleL7Proxy/IServer.cs @@ -4,7 +4,6 @@ public interface IServer { Task Run(); - //BlockingCollection Start(CancellationToken cancellationToken); BlockingPriorityQueue Start(CancellationToken cancellationToken); BlockingPriorityQueue Queue(); } \ No newline at end of file diff --git a/src/SimpleL7Proxy/Program.cs b/src/SimpleL7Proxy/Program.cs index 2e3e5b72..5f6a91c8 100644 --- a/src/SimpleL7Proxy/Program.cs +++ b/src/SimpleL7Proxy/Program.cs @@ -213,7 +213,7 @@ private static List ToListOfInt(string s) // It also configures the DNS refresh timeout and sets up an HttpClient instance. // If the IgnoreSSLCert environment variable is set to true, it configures the HttpClient to ignore SSL certificate errors. // If the AppendHostsFile environment variable is set to true, it appends the IP addresses and hostnames to the /etc/hosts file. - private static BackendOptions LoadBackendOptions() + private static BackendOptions LoadBackendOptions() { // Read and set the DNS refresh timeout from environment variables or use the default value var DNSTimeout= ReadEnvironmentVariableOrDefault("DnsRefreshTimeout", 120000); From 5f065b495260f78aec1a3e2bdfcbd0eb7a7cbde6 Mon Sep 17 00:00:00 2001 From: Tyler Kendrick <145080887+Tyler-R-Kendrick@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:10:44 +0000 Subject: [PATCH 07/10] Build fix --- src/SimpleL7Proxy/BackendOptions.cs | 6 ++-- src/SimpleL7Proxy/Backends.cs | 6 ++-- src/SimpleL7Proxy/Events/Events/ProxyEvent.cs | 2 +- src/SimpleL7Proxy/Program.cs | 4 +-- .../HttpListenerResponseWrapper.cs} | 10 ++++-- .../{ => Proxy}/IHttpListenerResponse.cs | 2 +- src/SimpleL7Proxy/Proxy/ProxyData.cs | 22 ++++--------- .../{ => Proxy}/ProxyStreamWriter.cs | 3 +- src/SimpleL7Proxy/Proxy/ProxyWorker.cs | 32 +++---------------- .../Proxy/ProxyWorkerCollection.cs | 4 +-- src/SimpleL7Proxy/Queue/PriorityQueue.cs | 2 +- .../FakeHttpListenerResponse.cs | 1 + .../ProxyStreamWriterTestFixture.cs | 1 + 13 files changed, 35 insertions(+), 60 deletions(-) rename src/SimpleL7Proxy/{HttpListenerResponseDecorator.cs => Proxy/HttpListenerResponseWrapper.cs} (69%) rename src/SimpleL7Proxy/{ => Proxy}/IHttpListenerResponse.cs (88%) rename src/SimpleL7Proxy/{ => Proxy}/ProxyStreamWriter.cs (97%) diff --git a/src/SimpleL7Proxy/BackendOptions.cs b/src/SimpleL7Proxy/BackendOptions.cs index bf9796a4..d3b6ad6e 100644 --- a/src/SimpleL7Proxy/BackendOptions.cs +++ b/src/SimpleL7Proxy/BackendOptions.cs @@ -6,14 +6,14 @@ public class BackendOptions public string HostName { get; set; } = ""; public List? Hosts { get; set; } public string IDStr { get; set; } = "S7P"; - public List LogHeaders { get; set; } = new List(); + public List LogHeaders { get; set; } = []; public int MaxQueueLength { get; set; } public string OAuthAudience { get; set; } = ""; public int Port { get; set; } public int PollInterval { get; set; } public int PollTimeout { get; set; } - public List PriorityKeys { get; set; } = new List(); - public List PriorityValues { get; set; } = new List(); + public List PriorityKeys { get; set; } = []; + public List PriorityValues { get; set; } = []; public int SuccessRate { get; set; } public int Timeout { get; set; } public bool UseOAuth { get; set; } diff --git a/src/SimpleL7Proxy/Backends.cs b/src/SimpleL7Proxy/Backends.cs index f8f90b58..744e67ee 100644 --- a/src/SimpleL7Proxy/Backends.cs +++ b/src/SimpleL7Proxy/Backends.cs @@ -47,7 +47,7 @@ public Backends(IOptions options, IEventClient eventClient, Tele _hosts = bo.Hosts; _options = bo; - _activeHosts = new List(); + _activeHosts = []; _successRate = bo.SuccessRate / 100.0; } @@ -63,7 +63,7 @@ public void Start(CancellationToken cancellationToken) } - List hostFailureTimes = new List(); + List hostFailureTimes = []; private const int FailureThreshold = 5; private const int FailureTimeFrame = 10; // seconds @@ -143,7 +143,7 @@ public async Task waitForStartup(int timeout) throw new Exception("Backend Poller did not start in time."); } - Dictionary currentHostStatus = new Dictionary(); + Dictionary currentHostStatus = []; private async Task Run() { using (HttpClient _client = CreateHttpClient()) { diff --git a/src/SimpleL7Proxy/Events/Events/ProxyEvent.cs b/src/SimpleL7Proxy/Events/Events/ProxyEvent.cs index 158a2feb..e69235ce 100644 --- a/src/SimpleL7Proxy/Events/Events/ProxyEvent.cs +++ b/src/SimpleL7Proxy/Events/Events/ProxyEvent.cs @@ -2,6 +2,6 @@ public class ProxyEvent { - public Dictionary EventData { get; private set; } = new Dictionary(); + public Dictionary EventData { get; private set; } = []; public string Name { get; set; } = string.Empty; } diff --git a/src/SimpleL7Proxy/Program.cs b/src/SimpleL7Proxy/Program.cs index 6995cbf7..cb784a90 100644 --- a/src/SimpleL7Proxy/Program.cs +++ b/src/SimpleL7Proxy/Program.cs @@ -205,7 +205,7 @@ private static List toListOfInt(string s) { // parse each value in the list - List ints = new List(); + List ints = []; foreach (var item in s.Split(',')) { if (int.TryParse(item.Trim(), out int value)) @@ -263,7 +263,7 @@ private static BackendOptions LoadBackendOptions() DefaultPriority = ReadEnvironmentVariableOrDefault("DefaultPriority", 2), DefaultTTLSecs = ReadEnvironmentVariableOrDefault("DefaultTTLSecs", 300), HostName = ReadEnvironmentVariableOrDefault("Hostname", "Default"), - Hosts = new List(), + Hosts = [], IDStr = ReadEnvironmentVariableOrDefault("RequestIDPrefix", "S7P") + "-" + replicaID + "-", LogHeaders = ReadEnvironmentVariableOrDefault("LogHeaders", "").Split(',').Select(x => x.Trim()).ToList(), MaxQueueLength = ReadEnvironmentVariableOrDefault("MaxQueueLength", 10), diff --git a/src/SimpleL7Proxy/HttpListenerResponseDecorator.cs b/src/SimpleL7Proxy/Proxy/HttpListenerResponseWrapper.cs similarity index 69% rename from src/SimpleL7Proxy/HttpListenerResponseDecorator.cs rename to src/SimpleL7Proxy/Proxy/HttpListenerResponseWrapper.cs index 88d4995e..05d17ffe 100644 --- a/src/SimpleL7Proxy/HttpListenerResponseDecorator.cs +++ b/src/SimpleL7Proxy/Proxy/HttpListenerResponseWrapper.cs @@ -1,8 +1,8 @@ using System.Net; -namespace SimpleL7Proxy; +namespace SimpleL7Proxy.Proxy; -public class HttpListenerResponseDecorator(HttpListenerResponse response) +internal class HttpListenerResponseWrapper(HttpListenerResponse response) : IHttpListenerResponse { public int StatusCode @@ -23,7 +23,11 @@ public long ContentLength64 set => response.ContentLength64 = value; } - public WebHeaderCollection Headers => response.Headers; + public WebHeaderCollection Headers + { + get => response.Headers; + set => response.Headers = value; + } public Stream OutputStream => response.OutputStream; } diff --git a/src/SimpleL7Proxy/IHttpListenerResponse.cs b/src/SimpleL7Proxy/Proxy/IHttpListenerResponse.cs similarity index 88% rename from src/SimpleL7Proxy/IHttpListenerResponse.cs rename to src/SimpleL7Proxy/Proxy/IHttpListenerResponse.cs index 1594dc8b..4c40a3a0 100644 --- a/src/SimpleL7Proxy/IHttpListenerResponse.cs +++ b/src/SimpleL7Proxy/Proxy/IHttpListenerResponse.cs @@ -1,6 +1,6 @@ using System.Net; -namespace SimpleL7Proxy; +namespace SimpleL7Proxy.Proxy; public interface IHttpListenerResponse { diff --git a/src/SimpleL7Proxy/Proxy/ProxyData.cs b/src/SimpleL7Proxy/Proxy/ProxyData.cs index 9badef38..0ca31011 100644 --- a/src/SimpleL7Proxy/Proxy/ProxyData.cs +++ b/src/SimpleL7Proxy/Proxy/ProxyData.cs @@ -4,30 +4,22 @@ namespace SimpleL7Proxy.Proxy; // This class represents the data returned from the downstream host. public class ProxyData { - public HttpStatusCode StatusCode { get; set; } + public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK; - public WebHeaderCollection Headers { get; set; } + public WebHeaderCollection Headers { get; set; } = []; - public WebHeaderCollection ContentHeaders { get; set; } + public WebHeaderCollection ContentHeaders { get; set; } = []; - public HttpResponseMessage ResponseMessage { get; set; } + public HttpResponseMessage ResponseMessage { get; set; } = new(); public byte[]? Body { get; set; } - public string FullURL { get; set; } + public string FullURL { get; set; } = string.Empty; // this is a copy of the calculated average latency public double CalculatedHostLatency { get; set; } - public string BackendHostname { get; set; } + public string BackendHostname { get; set; } = string.Empty; - public DateTime ResponseDate { get; set; } - - public ProxyData() - { - Headers = new WebHeaderCollection(); - ContentHeaders = new WebHeaderCollection(); - FullURL=""; - BackendHostname=""; - } + public DateTime ResponseDate { get; set; } = DateTime.UtcNow; } \ No newline at end of file diff --git a/src/SimpleL7Proxy/ProxyStreamWriter.cs b/src/SimpleL7Proxy/Proxy/ProxyStreamWriter.cs similarity index 97% rename from src/SimpleL7Proxy/ProxyStreamWriter.cs rename to src/SimpleL7Proxy/Proxy/ProxyStreamWriter.cs index 9196af6d..765c75ae 100644 --- a/src/SimpleL7Proxy/ProxyStreamWriter.cs +++ b/src/SimpleL7Proxy/Proxy/ProxyStreamWriter.cs @@ -1,7 +1,6 @@ using CommunityToolkit.Diagnostics; -using SimpleL7Proxy.Proxy; -namespace SimpleL7Proxy; +namespace SimpleL7Proxy.Proxy; public class ProxyStreamWriter { diff --git a/src/SimpleL7Proxy/Proxy/ProxyWorker.cs b/src/SimpleL7Proxy/Proxy/ProxyWorker.cs index f55e870f..a28ba5d9 100644 --- a/src/SimpleL7Proxy/Proxy/ProxyWorker.cs +++ b/src/SimpleL7Proxy/Proxy/ProxyWorker.cs @@ -27,7 +27,7 @@ public class ProxyWorker private readonly TelemetryClient? _telemetryClient; private readonly IEventClient _eventClient; private readonly ILogger _logger; - private readonly ProxyStreamWriter proxyStreamWriter; + private readonly ProxyStreamWriter _proxyStreamWriter; private readonly string IDstr = ""; public ProxyWorker( @@ -45,7 +45,7 @@ public ProxyWorker( _backends = backends ?? throw new ArgumentNullException(nameof(backends)); _eventClient = eventClient; _logger = logger; - this.proxyStreamWriter = proxyStreamWriter; + _proxyStreamWriter = proxyStreamWriter; _telemetryClient = telemetryClient; _options = backendOptions ?? throw new ArgumentNullException(nameof(backendOptions)); if (_options.Client == null) throw new ArgumentNullException(nameof(_options.Client)); @@ -263,33 +263,11 @@ public async Task TaskRunner() _logger.LogInformation($"Worker {IDstr} stopped."); } - private static async Task WriteResponseDataAsync(HttpListenerContext context, + private Task WriteResponseDataAsync(HttpListenerContext context, ProxyData pr, CancellationToken token) { - // Set the response status code - context.Response.StatusCode = (int)pr.ResponseMessage.StatusCode; - - if (pr.ResponseMessage.Content.Headers != null) - { - foreach (var header in pr.ResponseMessage.Content.Headers) - { - context.Response.Headers[header.Key] = string.Join(", ", header.Value); - - if (header.Key.ToLower().Equals("content-length")) - { - context.Response.ContentLength64 = pr.ResponseMessage.Content.Headers.ContentLength ?? 0; - } - } - } - - context.Response.KeepAlive = false; - // Stream the response body to the client - if (pr.ResponseMessage.Content != null) - { - await using var responseStream = await pr.ResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); - await responseStream.CopyToAsync(context.Response.OutputStream).ConfigureAwait(false); - await context.Response.OutputStream.FlushAsync().ConfigureAwait(false); - } + HttpListenerResponseWrapper listener = new(context.Response); + return _proxyStreamWriter.WriteResponseDataAsync(listener, pr, token); } public async Task ReadProxyAsync(RequestData request, CancellationToken cancellationToken) //DateTime requestDate, string method, string path, WebHeaderCollection headers, Stream body)//HttpListenerResponse downStreamResponse) diff --git a/src/SimpleL7Proxy/Proxy/ProxyWorkerCollection.cs b/src/SimpleL7Proxy/Proxy/ProxyWorkerCollection.cs index f7aa873d..0d74a2e1 100644 --- a/src/SimpleL7Proxy/Proxy/ProxyWorkerCollection.cs +++ b/src/SimpleL7Proxy/Proxy/ProxyWorkerCollection.cs @@ -21,8 +21,8 @@ public ProxyWorkerCollection( CancellationToken cancellationToken, ProxyStreamWriter proxyStreamWriter) { - _workers = new List(); - _tasks = new List(); + _workers = []; + _tasks = []; for (int i = 0; i < backendOptions.Workers; i++) { var pw = new ProxyWorker(cancellationToken, i, queue, backendOptions, backends, eventClient, telemetryClient, logger, proxyStreamWriter); diff --git a/src/SimpleL7Proxy/Queue/PriorityQueue.cs b/src/SimpleL7Proxy/Queue/PriorityQueue.cs index 67cf4494..1b7244c8 100644 --- a/src/SimpleL7Proxy/Queue/PriorityQueue.cs +++ b/src/SimpleL7Proxy/Queue/PriorityQueue.cs @@ -2,7 +2,7 @@ namespace SimpleL7Proxy.Queue; public class PriorityQueue { - private readonly List> _items = new List>(); + private readonly List> _items = []; private static readonly PriorityQueueItemComparer Comparer = new PriorityQueueItemComparer(); public int Count => _items.Count; diff --git a/test/ProxyWorkerTests/FakeHttpListenerResponse.cs b/test/ProxyWorkerTests/FakeHttpListenerResponse.cs index bcd5317c..3f9e69ad 100644 --- a/test/ProxyWorkerTests/FakeHttpListenerResponse.cs +++ b/test/ProxyWorkerTests/FakeHttpListenerResponse.cs @@ -1,4 +1,5 @@ using System.Net; +using SimpleL7Proxy.Proxy; namespace Tests; diff --git a/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs b/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs index db71561e..51e94070 100644 --- a/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs +++ b/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs @@ -1,5 +1,6 @@ using System.Net; using System.Text; +using SimpleL7Proxy.Proxy; namespace Tests; From d9112e76c8d7a33907e861dd68aaa5160dea246a Mon Sep 17 00:00:00 2001 From: Tyler Kendrick <145080887+Tyler-R-Kendrick@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:34:19 +0000 Subject: [PATCH 08/10] post merge cleanup --- src/SimpleL7Proxy/Events/{Events => }/AppInsightsEventClient.cs | 0 src/SimpleL7Proxy/Events/{Events => }/CompositeEventClient.cs | 0 src/SimpleL7Proxy/Events/{Events => }/EventHubClient.cs | 0 src/SimpleL7Proxy/Events/{Events => }/IEventClient.cs | 0 src/SimpleL7Proxy/Events/{Events => }/ProxyEvent.cs | 0 .../Events/{Events => }/ProxyEventServiceCollectionExtensions.cs | 0 src/{ => SimpleL7Proxy}/IBackendService.cs | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename src/SimpleL7Proxy/Events/{Events => }/AppInsightsEventClient.cs (100%) rename src/SimpleL7Proxy/Events/{Events => }/CompositeEventClient.cs (100%) rename src/SimpleL7Proxy/Events/{Events => }/EventHubClient.cs (100%) rename src/SimpleL7Proxy/Events/{Events => }/IEventClient.cs (100%) rename src/SimpleL7Proxy/Events/{Events => }/ProxyEvent.cs (100%) rename src/SimpleL7Proxy/Events/{Events => }/ProxyEventServiceCollectionExtensions.cs (100%) rename src/{ => SimpleL7Proxy}/IBackendService.cs (100%) diff --git a/src/SimpleL7Proxy/Events/Events/AppInsightsEventClient.cs b/src/SimpleL7Proxy/Events/AppInsightsEventClient.cs similarity index 100% rename from src/SimpleL7Proxy/Events/Events/AppInsightsEventClient.cs rename to src/SimpleL7Proxy/Events/AppInsightsEventClient.cs diff --git a/src/SimpleL7Proxy/Events/Events/CompositeEventClient.cs b/src/SimpleL7Proxy/Events/CompositeEventClient.cs similarity index 100% rename from src/SimpleL7Proxy/Events/Events/CompositeEventClient.cs rename to src/SimpleL7Proxy/Events/CompositeEventClient.cs diff --git a/src/SimpleL7Proxy/Events/Events/EventHubClient.cs b/src/SimpleL7Proxy/Events/EventHubClient.cs similarity index 100% rename from src/SimpleL7Proxy/Events/Events/EventHubClient.cs rename to src/SimpleL7Proxy/Events/EventHubClient.cs diff --git a/src/SimpleL7Proxy/Events/Events/IEventClient.cs b/src/SimpleL7Proxy/Events/IEventClient.cs similarity index 100% rename from src/SimpleL7Proxy/Events/Events/IEventClient.cs rename to src/SimpleL7Proxy/Events/IEventClient.cs diff --git a/src/SimpleL7Proxy/Events/Events/ProxyEvent.cs b/src/SimpleL7Proxy/Events/ProxyEvent.cs similarity index 100% rename from src/SimpleL7Proxy/Events/Events/ProxyEvent.cs rename to src/SimpleL7Proxy/Events/ProxyEvent.cs diff --git a/src/SimpleL7Proxy/Events/Events/ProxyEventServiceCollectionExtensions.cs b/src/SimpleL7Proxy/Events/ProxyEventServiceCollectionExtensions.cs similarity index 100% rename from src/SimpleL7Proxy/Events/Events/ProxyEventServiceCollectionExtensions.cs rename to src/SimpleL7Proxy/Events/ProxyEventServiceCollectionExtensions.cs diff --git a/src/IBackendService.cs b/src/SimpleL7Proxy/IBackendService.cs similarity index 100% rename from src/IBackendService.cs rename to src/SimpleL7Proxy/IBackendService.cs From aea360cde3113927fd20976544c35ca72266531d Mon Sep 17 00:00:00 2001 From: Tyler Kendrick <145080887+Tyler-R-Kendrick@users.noreply.github.com> Date: Mon, 13 Jan 2025 22:34:33 +0000 Subject: [PATCH 09/10] post merge cleanup --- src/SimpleL7Proxy/BackendHost.cs | 83 ++-- src/SimpleL7Proxy/BackendOptions.cs | 2 + src/SimpleL7Proxy/Backends.cs | 244 +++++----- src/SimpleL7Proxy/EventHubClient.cs | 0 .../Events/AppInsightsEventClient.cs | 25 +- .../Events/CompositeEventClient.cs | 14 +- src/SimpleL7Proxy/Events/EventHubClient.cs | 43 +- src/SimpleL7Proxy/Events/IEventClient.cs | 11 +- .../ProxyEventServiceCollectionExtensions.cs | 4 +- src/SimpleL7Proxy/IBackendService.cs | 6 +- src/SimpleL7Proxy/IServer.cs | 2 + src/SimpleL7Proxy/Program.cs | 73 ++- src/SimpleL7Proxy/Proxy/ProxyData.cs | 2 +- src/SimpleL7Proxy/Proxy/ProxyWorker.cs | 440 +++++++++--------- .../Proxy/ProxyWorkerCollection.cs | 15 +- src/SimpleL7Proxy/ProxyData.cs | 0 .../Queue/BlockingPriorityQueue.cs | 55 +-- .../Queue/IBlockingPriorityQueue.cs | 2 +- src/SimpleL7Proxy/Queue/PriorityQueue.cs | 32 +- src/SimpleL7Proxy/Queue/PriorityQueueItem.cs | 15 +- .../Queue/PriorityQueueItemComparer.cs | 23 + .../Queue/QueueServiceCollectionExtensions.cs | 6 - src/SimpleL7Proxy/Queue/TaskSignaler.cs | 14 +- src/SimpleL7Proxy/RequestData.cs | 6 +- src/SimpleL7Proxy/S7PRequeueException.cs | 19 +- src/SimpleL7Proxy/server.cs | 162 +++---- .../ProxyStreamWriterTestFixture.cs | 2 +- 27 files changed, 625 insertions(+), 675 deletions(-) delete mode 100644 src/SimpleL7Proxy/EventHubClient.cs delete mode 100644 src/SimpleL7Proxy/ProxyData.cs create mode 100644 src/SimpleL7Proxy/Queue/PriorityQueueItemComparer.cs delete mode 100644 src/SimpleL7Proxy/Queue/QueueServiceCollectionExtensions.cs diff --git a/src/SimpleL7Proxy/BackendHost.cs b/src/SimpleL7Proxy/BackendHost.cs index 8aae09ca..80619fa6 100644 --- a/src/SimpleL7Proxy/BackendHost.cs +++ b/src/SimpleL7Proxy/BackendHost.cs @@ -1,29 +1,32 @@ + +using Microsoft.Extensions.Logging; + public class BackendHost { - public string host; - public string? ipaddr; - public int port; - public string protocol; - public string probe_path; + public string Host { get; set; } + public string? IpAddr { get; set; } + public int Port { get; set; } + public string Protocol { get; set; } + public string ProbePath { get; set; } - string? _url = null; - string? _probeurl = null; - public string url => _url ??= new UriBuilder(protocol, ipaddr ?? host, port).Uri.AbsoluteUri; + private string? _url = null; + private string? _probeurl = null; + public string Url => _url ??= new UriBuilder(Protocol, IpAddr ?? Host, Port).Uri.AbsoluteUri; - public string probeurl => _probeurl ??= System.Net.WebUtility.UrlDecode( new UriBuilder(protocol, ipaddr ?? host, port, probe_path).Uri.AbsoluteUri ); + public string ProbeUrl => _probeurl ??= System.Net.WebUtility.UrlDecode($"{Url}/{ProbePath}"); private const int MaxData = 50; - private readonly Queue latencies = new Queue(); - private readonly Queue callSuccess = new Queue(); - public double calculatedAverageLatency { get; set; } + private readonly Queue latencies = new(); + private readonly Queue callSuccess = new(); + public double CalculatedAverageLatency { get; set; } - private Queue PxLatency = new Queue(); - private int errors=0; - private object lockObj = new object(); + private Queue PxLatency = new(); + private int errors = 0; + private readonly Lock lockObj = new(); + private readonly ILogger _logger; - public BackendHost(string hostname, string? probepath, string? ipaddress) + public BackendHost(string hostname, string? probepath, string? ipaddress, ILogger logger) { - // If host does not have a protocol, add one if (!hostname.StartsWith("http://") && !hostname.StartsWith("https://")) { @@ -31,40 +34,40 @@ public BackendHost(string hostname, string? probepath, string? ipaddress) } // if host ends with a slash, remove it - if (hostname.EndsWith("/")) + if (hostname.EndsWith('/')) { - hostname = hostname.Substring(0, hostname.Length - 1); + hostname = hostname[..^1]; } // parse the host, prototol and port - Uri uri = new Uri(hostname); - protocol = uri.Scheme; - port = uri.Port; - host = uri.Host; - - probe_path = probepath ?? "echo/resource?param1=sample"; - if (probe_path.StartsWith("/")) + Uri uri = new(hostname); + Protocol = uri.Scheme; + Port = uri.Port; + Host = uri.Host; + + ProbePath = probepath ?? "echo/resource?param1=sample"; + _logger = logger; + if (ProbePath.StartsWith('/')) { - probe_path = probe_path.Substring(1); + ProbePath = ProbePath[1..]; } - Console.WriteLine($"Adding backend host: {this.host} probe path: {this.probe_path}"); - //_logger.LogInformation($"Adding backend host: {this.host} probe path: {this.probe_path}"); + _logger.LogInformation($"Adding backend host: {Host} probe path: {ProbePath}"); } - public override string ToString() - { - return $"{protocol}://{host}:{port}"; - } + public override string ToString() => $"{Protocol}://{Host}:{Port}"; public void AddPxLatency(double latency) { - lock(lockObj) { + lock(lockObj) + { PxLatency.Enqueue(latency); } } - public void AddError() { - lock(lockObj) { + public void AddError() + { + lock(lockObj) + { errors++; } } @@ -83,12 +86,12 @@ public string GetStatus(out int calls, out int errorCalls, out double average) return " - "; } - var status=PxLatency; - errorCalls=errors; + var status = PxLatency; + errorCalls = errors; lock (lockObj) { // Reset the counts - PxLatency = new Queue(); + PxLatency = new(); errors = 0; } @@ -140,4 +143,4 @@ public double SuccessRate() // Otherwise, return the success rate return (double)callSuccess.Count(x => x) / callSuccess.Count; } -} \ No newline at end of file +} diff --git a/src/SimpleL7Proxy/BackendOptions.cs b/src/SimpleL7Proxy/BackendOptions.cs index d3b6ad6e..db5f416e 100644 --- a/src/SimpleL7Proxy/BackendOptions.cs +++ b/src/SimpleL7Proxy/BackendOptions.cs @@ -1,3 +1,5 @@ +namespace SimpleL7Proxy; + public class BackendOptions { public HttpClient? Client { get; set; } diff --git a/src/SimpleL7Proxy/Backends.cs b/src/SimpleL7Proxy/Backends.cs index 744e67ee..076d6e96 100644 --- a/src/SimpleL7Proxy/Backends.cs +++ b/src/SimpleL7Proxy/Backends.cs @@ -7,18 +7,19 @@ using Microsoft.ApplicationInsights; using Microsoft.Extensions.Logging; +namespace SimpleL7Proxy; // This code has 3 objectives: // * Check the status of each backend host and measure its latency // * Filter the active hosts based on the success rate // * Fetch the OAuth2 token and refresh it 100ms minutes before it expires -public class Backends +public class Backends : IBackendService { - private List _hosts; + private readonly List _hosts; private List _activeHosts; - private BackendOptions _options; - private static bool _debug=false; + private readonly BackendOptions _options; + private static readonly bool _debug = false; private static double _successRate; private static DateTime _lastStatusDisplay = DateTime.Now; @@ -26,13 +27,17 @@ public class Backends private static bool _isRunning = false; private CancellationToken _cancellationToken; - private Azure.Core.AccessToken? AuthToken { get; set; } - private object lockObj = new object(); + private AccessToken? AuthToken { get; set; } + private readonly Lock lockObj = new(); private readonly IEventClient _eventClient; private readonly TelemetryClient _telemetryClient; private readonly ILogger _logger; - public Backends(IOptions options, IEventClient eventClient, TelemetryClient telemetryClient, ILogger logger) + public Backends( + IOptions options, + IEventClient eventClient, + TelemetryClient telemetryClient, + ILogger logger) { if (options == null) throw new ArgumentNullException(nameof(options)); if (options.Value == null) throw new ArgumentNullException(nameof(options.Value)); @@ -54,27 +59,20 @@ public Backends(IOptions options, IEventClient eventClient, Tele public void Start(CancellationToken cancellationToken) { _cancellationToken = cancellationToken; - Task.Run(() => Run()); + Task.Run(() => Run(), cancellationToken); if (_options.UseOAuth) { GetToken(); } - } - + } - List hostFailureTimes = []; + private readonly List hostFailureTimes = []; private const int FailureThreshold = 5; private const int FailureTimeFrame = 10; // seconds - public List GetActiveHosts() - { - return _activeHosts; - } - public int ActiveHostCount() { - return _activeHosts.Count; - } - + public List GetActiveHosts() => _activeHosts; + public int ActiveHostCount() => _activeHosts.Count; public void TrackStatus(int code) { @@ -86,7 +84,7 @@ public void TrackStatus(int code) } else { - DateTime now = DateTime.UtcNow; + var now = DateTime.UtcNow; hostFailureTimes.Add(now); // truncate older entries @@ -96,16 +94,17 @@ public void TrackStatus(int code) } // returns true if the service is in failure state - public bool CheckFailedStatus() { + public bool CheckFailedStatus() + { lock(lockObj) { hostFailureTimes.RemoveAll(t => (DateTime.UtcNow - t).TotalSeconds >= FailureTimeFrame); return hostFailureTimes.Count >= FailureThreshold; } - } - public string OAuth2Token() { + public string OAuth2Token() + { while (AuthToken?.ExpiresOn < DateTime.UtcNow) { Task.Delay(100).Wait(); @@ -113,10 +112,10 @@ public string OAuth2Token() { return AuthToken?.Token ?? ""; } - public async Task waitForStartup(int timeout) + public async Task WaitForStartup(int timeout) { var start = DateTime.Now; - for (int i=0; i < 10; i++ ) + for (int i = 0; i < 10; i++) { var startTimer = DateTime.Now; // Wait for the backend poller to start or until the timeout is reached. Make sure that if a token is required, it is available. @@ -142,54 +141,58 @@ public async Task waitForStartup(int timeout) } throw new Exception("Backend Poller did not start in time."); } - - Dictionary currentHostStatus = []; + + private readonly Dictionary currentHostStatus = []; private async Task Run() { - using (HttpClient _client = CreateHttpClient()) { - var intervalTime = TimeSpan.FromMilliseconds(_options.PollInterval).ToString(@"hh\:mm\:ss"); - var timeoutTime = TimeSpan.FromMilliseconds(_options.PollTimeout).ToString(@"hh\:mm\:ss\.fff"); - _logger.LogInformation($"Starting Backend Poller: Interval: {intervalTime}, SuccessRate: {_successRate}, Timeout: {timeoutTime}"); + using HttpClient _client = CreateHttpClient(); + var intervalTime = TimeSpan.FromMilliseconds(_options.PollInterval).ToString(@"hh\:mm\:ss"); + var timeoutTime = TimeSpan.FromMilliseconds(_options.PollTimeout).ToString(@"hh\:mm\:ss\.fff"); + _logger.LogInformation($"Starting Backend Poller: Interval: {intervalTime}, SuccessRate: {_successRate}, Timeout: {timeoutTime}"); - _client.Timeout = TimeSpan.FromMilliseconds(_options.PollTimeout); + _client.Timeout = TimeSpan.FromMilliseconds(_options.PollTimeout); - using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken)) { - while (!linkedCts.Token.IsCancellationRequested && _cancellationToken.IsCancellationRequested == false) { - try { - await UpdateHostStatus(_client); - FilterActiveHosts(); + using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken)) + { + while (!linkedCts.Token.IsCancellationRequested && _cancellationToken.IsCancellationRequested == false) + { + try + { + await UpdateHostStatus(_client); + FilterActiveHosts(); - if ((DateTime.Now - _lastStatusDisplay).TotalSeconds > 60) { - DisplayHostStatus(); - } + if ((DateTime.Now - _lastStatusDisplay).TotalSeconds > 60) + { + DisplayHostStatus(); + } - await Task.Delay(_options.PollInterval, linkedCts.Token); + await Task.Delay(_options.PollInterval, linkedCts.Token); - } catch (OperationCanceledException) { - _logger.LogInformation("Operation was canceled. Stopping the backend poller task."); - break;; - } catch (Exception e) { - _logger.LogError($"An unexpected error occurred: {e.Message}"); - } + } + catch (OperationCanceledException) + { + _logger.LogInformation("Operation was canceled. Stopping the backend poller task."); + break; ; + } + catch (Exception e) + { + _logger.LogError($"An unexpected error occurred: {e.Message}"); } } - - _logger.LogInformation("Backend Poller stopped."); } + + _logger.LogInformation("Backend Poller stopped."); } - private HttpClient CreateHttpClient() { - if (Environment.GetEnvironmentVariable("IgnoreSSLCert")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true) { - var handler = new HttpClientHandler { + private static HttpClient CreateHttpClient() + => Environment.GetEnvironmentVariable("IgnoreSSLCert")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true + ? new HttpClient(new HttpClientHandler + { ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true - }; - return new HttpClient(handler); - } else { - return new HttpClient(); - } - } - - private async Task UpdateHostStatus(HttpClient _client) + }) + : new HttpClient(); + + private async Task UpdateHostStatus(HttpClient client) { var _statusChanged = false; @@ -198,12 +201,12 @@ private async Task UpdateHostStatus(HttpClient _client) return _statusChanged; } - foreach (var host in _hosts ) + foreach (var host in _hosts) { - var currentStatus = await GetHostStatus(host, _client); - bool statusChanged = !currentHostStatus.ContainsKey(host.host) || currentHostStatus[host.host] != currentStatus; + var currentStatus = await GetHostStatus(host, client); + bool statusChanged = !currentHostStatus.ContainsKey(host.Host) || currentHostStatus[host.Host] != currentStatus; - currentHostStatus[host.host] = currentStatus; + currentHostStatus[host.Host] = currentStatus; host.AddCallSuccess(currentStatus); if (statusChanged) @@ -220,24 +223,28 @@ private async Task UpdateHostStatus(HttpClient _client) private async Task GetHostStatus(BackendHost host, HttpClient client) { - ProxyEvent probeData = new(); - probeData.EventData["ProxyHost"] = _options.HostName; - probeData.EventData["Host"] = host.host; - probeData.EventData["Port"] = host.port.ToString(); - probeData.EventData["Path"] = host.probe_path; + ProxyEvent probeData = new() + { + EventData = + { + ["ProxyHost"] = _options.HostName, + ["Host"] = host.Host, + ["Port"] = host.Port.ToString(), + ["Path"] = host.ProbePath + } + }; if (_debug) - _logger.LogDebug($"Checking host {host.url + host.probe_path}"); + _logger.LogDebug($"Checking host {host.Url + host.ProbePath}"); - - var request = new HttpRequestMessage(HttpMethod.Get, host.probeurl); + HttpRequestMessage request = new(HttpMethod.Get, host.ProbePath); if (_options.UseOAuth) { - request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", OAuth2Token()); + request.Headers.Authorization = new("Bearer", OAuth2Token()); } var stopwatch = Stopwatch.StartNew(); - + var probeEventData = probeData.EventData; try { @@ -248,9 +255,9 @@ private async Task GetHostStatus(BackendHost host, HttpClient client) // Update the host with the new latency host.AddLatency(latency); - probeData.EventData["Latency"] = latency.ToString(); - probeData.EventData["Code"] = response.StatusCode.ToString(); - probeData.EventData["Type"] = "Poller"; + probeEventData["Latency"] = latency.ToString(); + probeEventData["Code"] = response.StatusCode.ToString(); + probeEventData["Type"] = "Poller"; response.EnsureSuccessStatusCode(); @@ -262,35 +269,35 @@ private async Task GetHostStatus(BackendHost host, HttpClient client) catch (UriFormatException e) { _telemetryClient?.TrackException(e); _logger.LogError($"Poller: Could not check probe: {e.Message}"); - probeData.EventData["Type"] = "Uri Format Exception"; - probeData.EventData["Code"] = "-"; + probeEventData["Type"] = "Uri Format Exception"; + probeEventData["Code"] = "-"; } - catch (System.Threading.Tasks.TaskCanceledException) { - _logger.LogError($"Poller: Host Timeout: {host.host}"); - probeData.EventData["Type"] = "TaskCanceledException"; - probeData.EventData["Code"] = "-"; + catch (TaskCanceledException) { + _logger.LogError($"Poller: Host Timeout: {host.Host}"); + probeEventData["Type"] = "TaskCanceledException"; + probeEventData["Code"] = "-"; } catch (HttpRequestException e) { _telemetryClient?.TrackException(e); - _logger.LogError($"Poller: Host {host.host} is down with exception: {e.Message}"); - probeData.EventData["Type"] = "HttpRequestException"; - probeData.EventData["Code"] = "-"; - } + _logger.LogError($"Poller: Host {host.Host} is down with exception: {e.Message}"); + probeEventData["Type"] = "HttpRequestException"; + probeEventData["Code"] = "-"; + } catch (OperationCanceledException) { // Handle the cancellation request (e.g., break the loop, log the cancellation, etc.) _logger.LogInformation("Poller: Operation was canceled. Stopping the server."); throw; // Exit the loop } catch (System.Net.Sockets.SocketException e) { - _logger.LogError($"Poller: Host {host.host} is down: {e.Message}"); - probeData.EventData["Type"] = "SocketException"; - probeData.EventData["Code"] = "-"; + _logger.LogError($"Poller: Host {host.Host} is down: {e.Message}"); + probeEventData["Type"] = "SocketException"; + probeEventData["Code"] = "-"; } catch (Exception e) { _telemetryClient?.TrackException(e); _logger.LogError($"Poller: Error: {e.Message}"); - probeData.EventData["Type"] = "Exception " + e.Message; - probeData.EventData["Code"] = "-"; + probeEventData["Type"] = "Exception " + e.Message; + probeEventData["Code"] = "-"; } finally { _eventClient.SendData(probeData); @@ -302,57 +309,50 @@ private async Task GetHostStatus(BackendHost host, HttpClient client) // Filter the active hosts based on the success rate private void FilterActiveHosts() { - _activeHosts = _hosts + _activeHosts = [.. _hosts .Where(h => h.SuccessRate() > _successRate) .Select(h => { - h.calculatedAverageLatency = h.AverageLatency(); + h.CalculatedAverageLatency = h.AverageLatency(); return h; }) - .OrderBy(h => h.calculatedAverageLatency) - .ToList(); + .OrderBy(h => h.CalculatedAverageLatency)]; } - public string _hostStatus { get; set; } = "-"; + public string HostStatus { get; set; } = "-"; - public string HostStatus() - { - // Implementation here - return _hostStatus; - } // Display the status of the hosts private void DisplayHostStatus() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); sb.Append("\n\n============ Host Status =========\n"); - int txActivity=0; + int txActivity = 0; - string statusIndicator = string.Empty; - if (_hosts != null ) - foreach (var host in _hosts ) + var statusIndicator = string.Empty; + if (_hosts != null) + foreach (var host in _hosts) { statusIndicator = host.SuccessRate() > _successRate ? "Good " : "Errors"; - double roundedLatency = Math.Round(host.AverageLatency(), 3); - double successRatePercentage = Math.Round(host.SuccessRate() * 100, 2); - - string hoststatus=host.GetStatus(out int calls, out int errors, out double average); + var roundedLatency = Math.Round(host.AverageLatency(), 3); + var successRatePercentage = Math.Round(host.SuccessRate() * 100, 2); + var hoststatus = host.GetStatus(out int calls, out int errors, out double average); txActivity += calls; txActivity += errors; - sb.Append($"{statusIndicator} Host: {host.url} Lat: {roundedLatency}ms Succ: {successRatePercentage}% {hoststatus}\n"); + sb.Append($"{statusIndicator} Host: {host.Url} Lat: {roundedLatency}ms Succ: {successRatePercentage}% {hoststatus}\n"); } - _lastStatusDisplay = DateTime.Now; - _hostStatus = sb.ToString(); + HostStatus = sb.ToString(); if (statusIndicator.StartsWith("Good")) { - _logger.LogInformation(_hostStatus); - } else + _logger.LogInformation(HostStatus); + } + else { - _logger.LogError(_hostStatus); + _logger.LogError(HostStatus); } if (txActivity == 0 && (DateTime.Now - _lastGCTime).TotalSeconds > (60*15) ) @@ -395,7 +395,6 @@ public void GetToken() { _logger.LogDebug("Auth Token is null. Retrying in 10 seconds."); await Task.Delay(TimeSpan.FromMilliseconds(10000), _cancellationToken); } - } } catch (OperationCanceledException) { @@ -423,11 +422,9 @@ public async Task GetTokenAsync() { try { - var credential = new DefaultAzureCredential(); - var context = new TokenRequestContext(new[] { _options.OAuthAudience }); - var token = await credential.GetTokenAsync(context); - - return token; + DefaultAzureCredential credential = new(); + TokenRequestContext context = new([_options.OAuthAudience]); + return await credential.GetTokenAsync(context); } catch (AuthenticationFailedException ex) { @@ -442,5 +439,4 @@ public async Task GetTokenAsync() throw; } } - } diff --git a/src/SimpleL7Proxy/EventHubClient.cs b/src/SimpleL7Proxy/EventHubClient.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/SimpleL7Proxy/Events/AppInsightsEventClient.cs b/src/SimpleL7Proxy/Events/AppInsightsEventClient.cs index bf008ab2..7b5f3f5a 100644 --- a/src/SimpleL7Proxy/Events/AppInsightsEventClient.cs +++ b/src/SimpleL7Proxy/Events/AppInsightsEventClient.cs @@ -2,31 +2,18 @@ namespace SimpleL7Proxy.Events; -public class AppInsightsEventClient : IEventClient +public class AppInsightsEventClient(TelemetryClient telemetryClient) + : IEventClient { - private readonly TelemetryClient _telemetryClient; - public AppInsightsEventClient(TelemetryClient telemetryClient) - { - _telemetryClient = telemetryClient; - } - public void SendData(string? value) - { - _telemetryClient.TrackEvent(value); - } + public void SendData(string? value) => telemetryClient.TrackEvent(value); public void SendData(ProxyEvent proxyEvent) { if (string.IsNullOrEmpty(proxyEvent.Name)) { - if (proxyEvent.EventData.TryGetValue("Type", out var type)) - { - proxyEvent.Name = type; - } - else - { - proxyEvent.Name = "ProxyEvent"; - } + proxyEvent.Name = proxyEvent.EventData.TryGetValue("Type", out var type) + ? type : "ProxyEvent"; } - _telemetryClient.TrackEvent(proxyEvent.Name, proxyEvent.EventData); + telemetryClient.TrackEvent(proxyEvent.Name, proxyEvent.EventData); } } diff --git a/src/SimpleL7Proxy/Events/CompositeEventClient.cs b/src/SimpleL7Proxy/Events/CompositeEventClient.cs index 59dabee0..35ce5413 100644 --- a/src/SimpleL7Proxy/Events/CompositeEventClient.cs +++ b/src/SimpleL7Proxy/Events/CompositeEventClient.cs @@ -1,17 +1,11 @@ namespace SimpleL7Proxy.Events; -public class CompositeEventClient : IEventClient +public class CompositeEventClient(IEnumerable eventClients) + : IEventClient { - private readonly IEnumerable _eventClients; - - public CompositeEventClient(IEnumerable eventClients) - { - _eventClients = eventClients; - } - public void SendData(string? value) { - foreach (var client in _eventClients) + foreach (var client in eventClients) { client.SendData(value); } @@ -19,7 +13,7 @@ public void SendData(string? value) public void SendData(ProxyEvent proxyEvent) { - foreach (var client in _eventClients) + foreach (var client in eventClients) { client.SendData(proxyEvent); } diff --git a/src/SimpleL7Proxy/Events/EventHubClient.cs b/src/SimpleL7Proxy/Events/EventHubClient.cs index 8f4e3797..9c531086 100644 --- a/src/SimpleL7Proxy/Events/EventHubClient.cs +++ b/src/SimpleL7Proxy/Events/EventHubClient.cs @@ -8,41 +8,41 @@ namespace SimpleL7Proxy.Events; public class EventHubClient : IEventClient { - private EventHubProducerClient producerClient; - private EventDataBatch batchData; - private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + private readonly EventHubProducerClient _producerClient; + private EventDataBatch _batchData; + private readonly CancellationTokenSource _cancellationTokenSource = new(); private bool isRunning = false; - private ConcurrentQueue _logBuffer = new ConcurrentQueue(); + private readonly ConcurrentQueue _logBuffer = new(); public EventHubClient(string connectionString, string eventHubName) { - producerClient = new EventHubProducerClient(connectionString, eventHubName); - batchData = producerClient.CreateBatchAsync().Result; + _producerClient = new EventHubProducerClient(connectionString, eventHubName); + _batchData = _producerClient.CreateBatchAsync().Result; //TODO: Don't await in constructors. isRunning = true; } public void StartTimer() { - if (isRunning && producerClient is not null && batchData is not null) + if (isRunning && _producerClient is not null && _batchData is not null) Task.Run(() => WriterTask()); } public async Task WriterTask() { - if (batchData is null || producerClient is null) + if (_batchData is null || _producerClient is null) return; try { - while (!cancellationTokenSource.Token.IsCancellationRequested) + while (!_cancellationTokenSource.Token.IsCancellationRequested) { if (GetNextBatch(100) > 0) { - await producerClient.SendAsync(batchData); - batchData = await producerClient.CreateBatchAsync(); + await _producerClient.SendAsync(_batchData); + _batchData = await _producerClient.CreateBatchAsync(); } - await Task.Delay(1000, cancellationTokenSource.Token); // Wait for 1 second + await Task.Delay(1000, _cancellationTokenSource.Token); // Wait for 1 second } } catch (TaskCanceledException) { @@ -52,7 +52,7 @@ public async Task WriterTask() while (true) { if (GetNextBatch(100) > 0) { - await producerClient.SendAsync(batchData); + await _producerClient.SendAsync(_batchData); } else { break; } @@ -63,32 +63,30 @@ public async Task WriterTask() // Add the log to the batch up to count number at a time private int GetNextBatch(int count) { - if (batchData is null) + if (_batchData is null) return 0; while (_logBuffer.TryDequeue(out string? log) && count-- > 0) { - batchData.TryAdd(new EventData(Encoding.UTF8.GetBytes(log))); + _batchData.TryAdd(new EventData(Encoding.UTF8.GetBytes(log))); } - return batchData.Count; + return _batchData.Count; } public void StopTimer() { if (isRunning) - cancellationTokenSource.Cancel(); + _cancellationTokenSource.Cancel(); isRunning = false; } public void SendData(string? value) { - if (!isRunning) return; - - if (value == null) return; + if (!isRunning || value == null) return; if (value.StartsWith("\n\n")) - value = value.Substring(2); + value = value[2..]; _logBuffer.Enqueue(value); } @@ -99,5 +97,4 @@ public void SendData(ProxyEvent proxyEvent) { string jsonData = JsonSerializer.Serialize(proxyEvent.EventData); SendData(jsonData); } - -} \ No newline at end of file +} diff --git a/src/SimpleL7Proxy/Events/IEventClient.cs b/src/SimpleL7Proxy/Events/IEventClient.cs index b178bad4..ba7ba1d6 100644 --- a/src/SimpleL7Proxy/Events/IEventClient.cs +++ b/src/SimpleL7Proxy/Events/IEventClient.cs @@ -1,8 +1,7 @@ -namespace SimpleL7Proxy.Events +namespace SimpleL7Proxy.Events; + +public interface IEventClient { - public interface IEventClient - { - void SendData(string? value); - void SendData(ProxyEvent eventData); - } + void SendData(string? value); + void SendData(ProxyEvent eventData); } diff --git a/src/SimpleL7Proxy/Events/ProxyEventServiceCollectionExtensions.cs b/src/SimpleL7Proxy/Events/ProxyEventServiceCollectionExtensions.cs index 33ba089c..b1f1893c 100644 --- a/src/SimpleL7Proxy/Events/ProxyEventServiceCollectionExtensions.cs +++ b/src/SimpleL7Proxy/Events/ProxyEventServiceCollectionExtensions.cs @@ -24,7 +24,7 @@ public static IServiceCollection AddProxyEventClient( services.AddSingleton(svc => { - var clients = new List(); + List clients = []; var eventHubClient = svc.GetService(); var appInsightsClient = svc.GetService(); if (eventHubClient != null) @@ -35,7 +35,7 @@ public static IServiceCollection AddProxyEventClient( { clients.Add(appInsightsClient); } - return new CompositeEventClient(clients); + return new(clients); }); return services; diff --git a/src/SimpleL7Proxy/IBackendService.cs b/src/SimpleL7Proxy/IBackendService.cs index fc6cac92..7339d605 100644 --- a/src/SimpleL7Proxy/IBackendService.cs +++ b/src/SimpleL7Proxy/IBackendService.cs @@ -1,3 +1,5 @@ +namespace SimpleL7Proxy; + public interface IBackendService { void Start(CancellationToken cancellationToken); @@ -5,8 +7,8 @@ public interface IBackendService public int ActiveHostCount(); - public Task waitForStartup(int timeout); - public string HostStatus(); + public Task WaitForStartup(int timeout); + public string HostStatus { get; } public void TrackStatus(int code); public bool CheckFailedStatus(); public string OAuth2Token(); diff --git a/src/SimpleL7Proxy/IServer.cs b/src/SimpleL7Proxy/IServer.cs index 53fe3511..9dc020cc 100644 --- a/src/SimpleL7Proxy/IServer.cs +++ b/src/SimpleL7Proxy/IServer.cs @@ -1,3 +1,5 @@ +namespace SimpleL7Proxy; + public interface IServer { Task Run(); diff --git a/src/SimpleL7Proxy/Program.cs b/src/SimpleL7Proxy/Program.cs index cb784a90..6964ad84 100644 --- a/src/SimpleL7Proxy/Program.cs +++ b/src/SimpleL7Proxy/Program.cs @@ -4,12 +4,12 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using SimpleL7Proxy; using SimpleL7Proxy.Events; using SimpleL7Proxy.Proxy; using SimpleL7Proxy.Queue; using System.Net; using System.Text; -using OS = System; // This code serves as the entry point for the .NET application. // It sets up the necessary configurations, including logging and telemetry. @@ -24,16 +24,17 @@ public class Program { - private static TelemetryClient? _telemetryClient; + private static readonly TelemetryClient? _telemetryClient; private static ILogger? _logger; - static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + static readonly CancellationTokenSource cancellationTokenSource = new(); public string OAuthAudience { get; set; } = ""; public static async Task Main(string[] args) { var cancellationToken = cancellationTokenSource.Token; - var backendOptions = LoadBackendOptions(); + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var backendOptions = LoadBackendOptions(loggerFactory); Console.CancelKeyPress += (sender, e) => { @@ -67,7 +68,7 @@ public static async Task Main(string[] args) options.Workers = backendOptions.Workers; }); - var aiConnectionString = OS.Environment.GetEnvironmentVariable("APPINSIGHTS_CONNECTIONSTRING") ?? ""; + var aiConnectionString = Environment.GetEnvironmentVariable("APPINSIGHTS_CONNECTIONSTRING") ?? ""; services.AddLogging( loggingBuilder => { @@ -92,8 +93,8 @@ public static async Task Main(string[] args) } // Add the proxy event client - var eventHubConnectionString = OS.Environment.GetEnvironmentVariable("EVENTHUB_CONNECTIONSTRING"); - var eventHubName = OS.Environment.GetEnvironmentVariable("EVENTHUB_NAME"); + var eventHubConnectionString = Environment.GetEnvironmentVariable("EVENTHUB_CONNECTIONSTRING"); + var eventHubName = Environment.GetEnvironmentVariable("EVENTHUB_NAME"); services.AddProxyEventClient(eventHubConnectionString, eventHubName, aiConnectionString); //services.AddHttpLogging(o => { }); @@ -126,7 +127,7 @@ public static async Task Main(string[] args) var pwCollection = serviceProvider.GetRequiredService(); try { - await backends.waitForStartup(20); // wait for up to 20 seconds for startup + await backends.WaitForStartup(20); // wait for up to 20 seconds for startup server.Start(cancellationToken); pwCollection.StartWorkers(); @@ -134,7 +135,7 @@ public static async Task Main(string[] args) catch (Exception e) { _logger.LogError(e, "Exiting: {Message}", e.Message); - System.Environment.Exit(1); + Environment.Exit(1); } try @@ -173,7 +174,7 @@ public static async Task Main(string[] args) // If the environment variable is not set, it returns the provided default value. private static int ReadEnvironmentVariableOrDefault(string variableName, int defaultValue) { - if (!int.TryParse(OS.Environment.GetEnvironmentVariable(variableName), out var value)) + if (!int.TryParse(Environment.GetEnvironmentVariable(variableName), out var value)) { Console.WriteLine($"Using default: {variableName}: {defaultValue}"); return defaultValue; @@ -194,16 +195,12 @@ private static string ReadEnvironmentVariableOrDefault(string variableName, stri return envValue.Trim(); } - // Converts a comma-separated string to a list of strings. - private static List toListOfString(string s) - { - return s.Split(',').Select(p => p.Trim()).ToList(); - } + // Converts a comma-separated string to a list of strings. + private static List ToListOfString(string s) => s.Split(',').Select(p => p.Trim()).ToList(); - // Converts a comma-separated string to a list of integers. - private static List toListOfInt(string s) + // Converts a comma-separated string to a list of integers. + private static List ToListOfInt(string s) { - // parse each value in the list List ints = []; foreach (var item in s.Split(',')) @@ -226,29 +223,31 @@ private static List toListOfInt(string s) // It also configures the DNS refresh timeout and sets up an HttpClient instance. // If the IgnoreSSLCert environment variable is set to true, it configures the HttpClient to ignore SSL certificate errors. // If the AppendHostsFile environment variable is set to true, it appends the IP addresses and hostnames to the /etc/hosts file. - private static BackendOptions LoadBackendOptions() + private static BackendOptions LoadBackendOptions(ILoggerFactory loggerFactory) { + var logger = loggerFactory.CreateLogger(); // Read and set the DNS refresh timeout from environment variables or use the default value var DNSTimeout = ReadEnvironmentVariableOrDefault("DnsRefreshTimeout", 120000); ServicePointManager.DnsRefreshTimeout = DNSTimeout; // Initialize HttpClient and configure it to ignore SSL certificate errors if specified in environment variables. - HttpClient _client = new HttpClient(); + HttpClient _client = new(); if (Environment.GetEnvironmentVariable("IgnoreSSLCert")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true) { - var handler = new HttpClientHandler(); - handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true; - _client = new HttpClient(handler); + _client = new(new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true + }); } string replicaID = ReadEnvironmentVariableOrDefault("CONTAINER_APP_REPLICA_NAME", "01"); #if DEBUG - // Load appsettings.json only in Debug mode - var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json", optional: true) - .AddEnvironmentVariables() - .Build(); + // Load appsettings.json only in Debug mode + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: true) + .AddEnvironmentVariables() + .Build(); foreach (var setting in configuration.GetSection("Settings").GetChildren()) { @@ -271,8 +270,8 @@ private static BackendOptions LoadBackendOptions() Port = ReadEnvironmentVariableOrDefault("Port", 443), PollInterval = ReadEnvironmentVariableOrDefault("PollInterval", 15000), PollTimeout = ReadEnvironmentVariableOrDefault("PollTimeout", 3000), - PriorityKeys = toListOfString(ReadEnvironmentVariableOrDefault("PriorityKeys", "12345,234")), - PriorityValues = toListOfInt(ReadEnvironmentVariableOrDefault("PriorityValues", "1,3")), + PriorityKeys = ToListOfString(ReadEnvironmentVariableOrDefault("PriorityKeys", "12345,234")), + PriorityValues = ToListOfInt(ReadEnvironmentVariableOrDefault("PriorityValues", "1,3")), SuccessRate = ReadEnvironmentVariableOrDefault("SuccessRate", 80), Timeout = ReadEnvironmentVariableOrDefault("Timeout", 3000), UseOAuth = ReadEnvironmentVariableOrDefault("UseOAuth", "false").Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true, @@ -282,7 +281,7 @@ private static BackendOptions LoadBackendOptions() backendOptions.Client.Timeout = TimeSpan.FromMilliseconds(backendOptions.Timeout); int i = 1; - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); while (true) { @@ -296,10 +295,10 @@ private static BackendOptions LoadBackendOptions() { _logger?.LogInformation($"Found host {hostname} with probe path {probePath} and IP {ip}"); - var bh = new BackendHost(hostname, probePath, ip); + BackendHost bh = new(hostname, probePath, ip, logger); backendOptions.Hosts.Add(bh); - sb.AppendLine($"{ip} {bh.host}"); + sb.AppendLine($"{ip} {bh.Host}"); } catch (UriFormatException e) @@ -314,11 +313,9 @@ private static BackendOptions LoadBackendOptions() Environment.GetEnvironmentVariable("AppendHostsFile")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true) { _logger?.LogInformation($"Appending {sb.ToString()} to /etc/hosts"); - using (StreamWriter sw = File.AppendText("/etc/hosts")) - { - sw.WriteLine(sb.ToString()); - } - } + using StreamWriter sw = File.AppendText("/etc/hosts"); + sw.WriteLine(sb.ToString()); + } // confirm the number of priority keys and values match if (backendOptions.PriorityKeys.Count != backendOptions.PriorityValues.Count) diff --git a/src/SimpleL7Proxy/Proxy/ProxyData.cs b/src/SimpleL7Proxy/Proxy/ProxyData.cs index 0ca31011..edd7c8b6 100644 --- a/src/SimpleL7Proxy/Proxy/ProxyData.cs +++ b/src/SimpleL7Proxy/Proxy/ProxyData.cs @@ -22,4 +22,4 @@ public class ProxyData public string BackendHostname { get; set; } = string.Empty; public DateTime ResponseDate { get; set; } = DateTime.UtcNow; -} \ No newline at end of file +} diff --git a/src/SimpleL7Proxy/Proxy/ProxyWorker.cs b/src/SimpleL7Proxy/Proxy/ProxyWorker.cs index a28ba5d9..9ffd7ec4 100644 --- a/src/SimpleL7Proxy/Proxy/ProxyWorker.cs +++ b/src/SimpleL7Proxy/Proxy/ProxyWorker.cs @@ -3,7 +3,6 @@ using System.Net.Http.Headers; using System.Text; using Microsoft.ApplicationInsights; -using Microsoft.ApplicationInsights.DataContracts; using Microsoft.Extensions.Logging; using SimpleL7Proxy.Events; using SimpleL7Proxy.Queue; @@ -19,9 +18,9 @@ namespace SimpleL7Proxy.Proxy; // 6. Log telemetry data for each request. public class ProxyWorker { - private static bool _debug = false; - private CancellationToken _cancellationToken; - private IBlockingPriorityQueue? _requestsQueue; + private static readonly bool _debug = false; + private readonly CancellationToken _cancellationToken; + private readonly IBlockingPriorityQueue _requestsQueue; private readonly Backends _backends; private readonly BackendOptions _options; private readonly TelemetryClient? _telemetryClient; @@ -31,14 +30,14 @@ public class ProxyWorker private readonly string IDstr = ""; public ProxyWorker( - CancellationToken cancellationToken, int ID, IBlockingPriorityQueue requestsQueue, BackendOptions backendOptions, Backends? backends, IEventClient eventClient, TelemetryClient? telemetryClient, ILogger logger, - ProxyStreamWriter proxyStreamWriter) + ProxyStreamWriter proxyStreamWriter, + CancellationToken cancellationToken) { _cancellationToken = cancellationToken; _requestsQueue = requestsQueue ?? throw new ArgumentNullException(nameof(requestsQueue)); @@ -48,20 +47,18 @@ public ProxyWorker( _proxyStreamWriter = proxyStreamWriter; _telemetryClient = telemetryClient; _options = backendOptions ?? throw new ArgumentNullException(nameof(backendOptions)); - if (_options.Client == null) throw new ArgumentNullException(nameof(_options.Client)); + if (_options.Client == null) throw new NullReferenceException(nameof(_options.Client)); IDstr = ID.ToString(); } public async Task TaskRunner() { - if (_requestsQueue == null) throw new ArgumentNullException(nameof(_requestsQueue)); - while (!_cancellationToken.IsCancellationRequested) { RequestData incomingRequest; try { - incomingRequest = await _requestsQueue.Dequeue(_cancellationToken, IDstr); // This will block until an item is available or the token is cancelled + incomingRequest = await _requestsQueue.Dequeue(IDstr, _cancellationToken); // This will block until an item is available or the token is cancelled incomingRequest.DequeueTime = DateTime.UtcNow; } catch (OperationCanceledException) @@ -75,62 +72,69 @@ public async Task TaskRunner() var lcontext = incomingRequest?.Context; bool isExpired = false; - ProxyEvent proxyEvent = new(); - proxyEvent.EventData["ProxyHost"] = _options.HostName; - proxyEvent.EventData["Date"] = incomingRequest?.DequeueTime.ToString("o") ?? DateTime.UtcNow.ToString("o"); - proxyEvent.EventData["Path"] = incomingRequest?.Path ?? "N/A"; - proxyEvent.EventData["x-RequestPriority"] = incomingRequest?.Priority.ToString() ?? "N/A"; - proxyEvent.EventData["x-RequestMethod"] = incomingRequest?.Method ?? "N/A"; - proxyEvent.EventData["x-RequestPath"] = incomingRequest?.Path ?? "N/A"; - proxyEvent.EventData["x-RequestHost"] = incomingRequest?.Headers["Host"] ?? "N/A"; - proxyEvent.EventData["x-RequestUserAgent"] = incomingRequest?.Headers["User-Agent"] ?? "N/A"; - proxyEvent.EventData["x-RequestContentType"] = incomingRequest?.Headers["Content-Type"] ?? "N/A"; - proxyEvent.EventData["x-RequestContentLength"] = incomingRequest?.Headers["Content-Length"] ?? "N/A"; - proxyEvent.EventData["x-RequestWorker"] = IDstr; - + ProxyEvent proxyEvent = new() + { + EventData = { + ["PoxyHost"] = _options.HostName, + ["Date"] = incomingRequest?.DequeueTime.ToString("o") ?? DateTime.UtcNow.ToString("o"), + ["Path"] = incomingRequest?.Path ?? "N/A", + ["x-RequestPriority"] = incomingRequest?.Priority.ToString() ?? "N/A", + ["x-RequestMethod"] = incomingRequest?.Method ?? "N/A", + ["x-RequestPath"] = incomingRequest?.Path ?? "N/A", + ["x-RequestHost"] = incomingRequest?.Headers["Host"] ?? "N/A", + ["x-RequestUserAgent"] = incomingRequest?.Headers["User-Agent"] ?? "N/A", + ["x-RequestContentType"] = incomingRequest?.Headers["Content-Type"] ?? "N/A", + ["x-RequestContentLength"] = incomingRequest?.Headers["Content-Length"] ?? "N/A", + ["x-RequestWorker"] = IDstr + } + }; + var proxyEventData = proxyEvent.EventData; if (lcontext == null || incomingRequest == null) { - // Task.Yield(); // Yield to the scheduler to allow other tasks to run continue; } - + var lcontextResponse = lcontext.Response; + var lcontextResponseHeaders = lcontextResponse.Headers; + var outputStream = lcontextResponse.OutputStream; try { if (incomingRequest.Path == "/health") { - lcontext.Response.StatusCode = 200; - lcontext.Response.ContentType = "text/plain"; - lcontext.Response.Headers.Add("Cache-Control", "no-cache"); - lcontext.Response.KeepAlive = false; - - Byte[]? healthMessage = Encoding.UTF8.GetBytes(_backends?.HostStatus() ?? "OK"); - lcontext.Response.ContentLength64 = healthMessage.Length; - - await lcontext.Response.OutputStream.WriteAsync(healthMessage, 0, healthMessage.Length).ConfigureAwait(false); + lcontextResponse.StatusCode = 200; + lcontextResponse.ContentType = "text/plain"; + lcontextResponse.Headers.Add("Cache-Control", "no-cache"); + lcontextResponse.KeepAlive = false; + + var healthMessage = Encoding.UTF8.GetBytes(_backends?.HostStatus ?? "OK"); + lcontextResponse.ContentLength64 = healthMessage.Length; + + await outputStream.WriteAsync( + healthMessage, + 0, + healthMessage.Length, + _cancellationToken).ConfigureAwait(false); continue; } + var incomingHeaders = incomingRequest.Headers; + incomingHeaders["x-Request-Queue-Duration"] = (incomingRequest.DequeueTime - incomingRequest.EnqueueTime).TotalMilliseconds.ToString(); + incomingHeaders["x-Request-Process-Duration"] = (DateTime.UtcNow - incomingRequest.DequeueTime).TotalMilliseconds.ToString(); + incomingHeaders["x-Request-Worker"] = IDstr; + incomingHeaders["x-S7PID"] = incomingRequest.MID ?? "N/A"; + incomingHeaders["x-S7PPriority"] = incomingRequest.Priority.ToString() ?? "N/A"; - incomingRequest.Headers["x-Request-Queue-Duration"] = (incomingRequest.DequeueTime - incomingRequest.EnqueueTime).TotalMilliseconds.ToString(); - incomingRequest.Headers["x-Request-Process-Duration"] = (DateTime.UtcNow - incomingRequest.DequeueTime).TotalMilliseconds.ToString(); - incomingRequest.Headers["x-Request-Worker"] = IDstr; - incomingRequest.Headers["x-S7PID"] = incomingRequest.MID ?? "N/A"; - incomingRequest.Headers["x-S7PPriority"] = incomingRequest.Priority.ToString() ?? "N/A"; - - proxyEvent.EventData["x-Request-Queue-Duration"] = incomingRequest.Headers["x-Request-Queue-Duration"] ?? "N/A"; - proxyEvent.EventData["x-Request-Process-Duration"] = incomingRequest.Headers["x-Request-Process-Duration"] ?? "N/A"; - proxyEvent.EventData["x-S7PID"] = incomingRequest.MID ?? "N/A"; - - var pr = await ReadProxyAsync(incomingRequest, _cancellationToken).ConfigureAwait(false); + proxyEventData["x-Request-Queue-Duration"] = incomingHeaders["x-Request-Queue-Duration"] ?? "N/A"; + proxyEventData["x-Request-Process-Duration"] = incomingHeaders["x-Request-Process-Duration"] ?? "N/A"; + proxyEventData["x-S7PID"] = incomingRequest.MID ?? "N/A"; - // Task.Yield(); // Yield to the scheduler to allow other tasks to run + var pr = await ReadProxyAsync(incomingRequest, _cancellationToken).ConfigureAwait(false); - proxyEvent.EventData["x-Status"] = ((int)pr.StatusCode).ToString(); + proxyEventData["x-Status"] = ((int)pr.StatusCode).ToString(); if (_options.LogHeaders != null && _options.LogHeaders.Count > 0) { foreach (var header in _options.LogHeaders) { - proxyEvent.EventData[header] = lcontext.Response?.Headers[header] ?? "N/A"; + proxyEventData[header] = lcontextResponseHeaders[header] ?? "N/A"; } } @@ -142,17 +146,17 @@ public async Task TaskRunner() _logger.LogInformation($"Pri: {incomingRequest.Priority} Stat: {(int)pr.StatusCode} Len: {pr.ContentHeaders["Content-Length"]} {pr.FullURL}"); - proxyEvent.EventData["Url"] = pr.FullURL; - proxyEvent.EventData["x-Response-Latency"] = (pr.ResponseDate - incomingRequest.DequeueTime).TotalMilliseconds.ToString("F3"); - proxyEvent.EventData["x-Total-Latency"] = (DateTime.UtcNow - incomingRequest.EnqueueTime).TotalMilliseconds.ToString("F3"); - proxyEvent.EventData["x-Backend-Host"] = pr?.BackendHostname ?? "N/A"; - proxyEvent.EventData["x-Backend-Host-Latency"] = pr?.CalculatedHostLatency.ToString("F3") ?? "N/A"; - proxyEvent.EventData["Content-Length"] = lcontext.Response?.ContentLength64.ToString() ?? "N/A"; - proxyEvent.EventData["Content-Type"] = lcontext?.Response?.ContentType ?? "N/A"; + proxyEventData["Url"] = pr.FullURL; + proxyEventData["x-Response-Latency"] = (pr.ResponseDate - incomingRequest.DequeueTime).TotalMilliseconds.ToString("F3"); + proxyEventData["x-Total-Latency"] = (DateTime.UtcNow - incomingRequest.EnqueueTime).TotalMilliseconds.ToString("F3"); + proxyEventData["x-Backend-Host"] = pr?.BackendHostname ?? "N/A"; + proxyEventData["x-Backend-Host-Latency"] = pr?.CalculatedHostLatency.ToString("F3") ?? "N/A"; + proxyEventData["Content-Length"] = lcontextResponse?.ContentLength64.ToString() ?? "N/A"; + proxyEventData["Content-Type"] = lcontextResponse?.ContentType ?? "N/A"; if (_eventClient != null) { - proxyEvent.EventData["Type"] = isExpired ? "S7P-Expired-Request" : "S7P-ProxyRequest"; + proxyEventData["Type"] = isExpired ? "S7P-Expired-Request" : "S7P-ProxyRequest"; _eventClient.SendData(proxyEvent); } } @@ -163,14 +167,13 @@ public async Task TaskRunner() _logger.LogInformation($"Requeued request. Pri: {incomingRequest.Priority} Queue Length: {_requestsQueue.Count} Status: {_backends.CheckFailedStatus()} Active Hosts: {_backends.ActiveHostCount()}"); requestWasRetried = true; incomingRequest.SkipDispose = true; - proxyEvent.EventData["Url"] = e.pr.FullURL; - proxyEvent.EventData["x-Status"] = ((int)503).ToString(); - proxyEvent.EventData["x-Response-Latency"] = (e.pr.ResponseDate - incomingRequest.DequeueTime).TotalMilliseconds.ToString("F3"); - proxyEvent.EventData["x-Total-Latency"] = (DateTime.UtcNow - incomingRequest.EnqueueTime).TotalMilliseconds.ToString("F3"); - proxyEvent.EventData["x-Backend-Host"] = e.pr?.BackendHostname ?? "N/A"; - proxyEvent.EventData["x-Backend-Host-Latency"] = e.pr?.CalculatedHostLatency.ToString("F3") ?? "N/A"; - proxyEvent.EventData["Type"] = "S7P-Requeue-Request"; - + proxyEventData["Url"] = e.Pr.FullURL; + proxyEventData["x-Status"] = ((int)503).ToString(); + proxyEventData["x-Response-Latency"] = (e.Pr.ResponseDate - incomingRequest.DequeueTime).TotalMilliseconds.ToString("F3"); + proxyEventData["x-Total-Latency"] = (DateTime.UtcNow - incomingRequest.EnqueueTime).TotalMilliseconds.ToString("F3"); + proxyEventData["x-Backend-Host"] = e.Pr?.BackendHostname ?? "N/A"; + proxyEventData["x-Backend-Host-Latency"] = e.Pr?.CalculatedHostLatency.ToString("F3") ?? "N/A"; + proxyEventData["Type"] = "S7P-Requeue-Request"; _eventClient.SendData(proxyEvent); } @@ -181,27 +184,28 @@ public async Task TaskRunner() _logger.LogError("IoException on an exipred request"); continue; } - proxyEvent.EventData["x-Status"] = "502"; - proxyEvent.EventData["Type"] = "S7P-IOException"; - proxyEvent.EventData["x-Message"] = ioEx.Message; + proxyEventData["x-Status"] = "502"; + proxyEventData["Type"] = "S7P-IOException"; + proxyEventData["x-Message"] = ioEx.Message; _logger.LogError($"An IO exception occurred: {ioEx.Message}"); - lcontext.Response.StatusCode = 502; + lcontextResponse.StatusCode = 502; var errorMessage = Encoding.UTF8.GetBytes($"Broken Pipe: {ioEx.Message}"); try { - await lcontext.Response.OutputStream.WriteAsync(errorMessage, 0, errorMessage.Length).ConfigureAwait(false); + await outputStream.WriteAsync( + errorMessage, + 0, + errorMessage.Length, + _cancellationToken).ConfigureAwait(false); } catch (Exception writeEx) { _logger.LogError($"Failed to write error message: {writeEx.Message}"); - proxyEvent.EventData["x-Status"] = "Network Error"; + proxyEventData["x-Status"] = "Network Error"; _eventClient.SendData(proxyEvent); } - - _eventClient.SendData(proxyEvent); - } catch (Exception ex) { @@ -211,29 +215,34 @@ public async Task TaskRunner() continue; } - proxyEvent.EventData["x-Status"] = "500"; - proxyEvent.EventData["Type"] = "S7P-Exception"; - proxyEvent.EventData["x-Message"] = ex.Message; - - if (ex.Message == "Cannot access a disposed object." || ex.Message.StartsWith("Unable to write data") || ex.Message.Contains("Broken Pipe")) // The client likely closed the connection + proxyEventData["x-Status"] = "500"; + proxyEventData["Type"] = "S7P-Exception"; + proxyEventData["x-Message"] = ex.Message; + var message = ex.Message; + if (message == "Cannot access a disposed object." + || message.StartsWith("Unable to write data") + || message.Contains("Broken Pipe")) // The client likely closed the connection { _logger.LogError($"Client closed connection: {incomingRequest.FullURL}"); - proxyEvent.EventData["x-Status"] = "Network Error"; + proxyEventData["x-Status"] = "Network Error"; _eventClient.SendData(proxyEvent); - continue; } // Log the exception - _logger.LogError($"Exception: {ex.Message}"); + _logger.LogError($"Exception: {message}"); _logger.LogError($"Stack Trace: {ex.StackTrace}"); // Set an appropriate status code for the error - lcontext.Response.StatusCode = 500; + lcontextResponse.StatusCode = 500; var errorMessage = Encoding.UTF8.GetBytes("Internal Server Error"); try { _telemetryClient?.TrackException(ex, proxyEvent.EventData); - await lcontext.Response.OutputStream.WriteAsync(errorMessage, 0, errorMessage.Length).ConfigureAwait(false); + await outputStream.WriteAsync( + errorMessage, + 0, + errorMessage.Length, + _cancellationToken).ConfigureAwait(false); } catch (Exception writeEx) { @@ -242,20 +251,17 @@ public async Task TaskRunner() } finally { - // Let's not track the request if it was retried. if (!requestWasRetried) { // Track the status of the request for circuit breaker - _backends.TrackStatus((int)lcontext.Response.StatusCode); - + _backends.TrackStatus(lcontextResponse.StatusCode); _telemetryClient?.TrackRequest($"{incomingRequest.Method} {incomingRequest.Path}", - DateTimeOffset.UtcNow, new TimeSpan(0, 0, 0), $"{lcontext.Response.StatusCode}", true); + DateTimeOffset.UtcNow, new TimeSpan(0, 0, 0), $"{lcontextResponse.StatusCode}", true); //_telemetryClient?.TrackEvent("ProxyRequest", proxyEvent); - lcontext?.Response.Close(); + lcontextResponse.Close(); } - } } } @@ -297,13 +303,13 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke { if (!CalculateTTL(request)) { - HttpResponseMessage ttlResponseMessage = new(HttpStatusCode.InternalServerError); - - ttlResponseMessage.Content = new StringContent("Invalid TTL format: " + request.Headers["S7PTTL"], Encoding.UTF8); return await WriteProxyDataAsync(new() { StatusCode = HttpStatusCode.BadRequest, - ResponseMessage = ttlResponseMessage + ResponseMessage = new(HttpStatusCode.InternalServerError) + { + Content = new StringContent("Invalid TTL format: " + request.Headers["S7PTTL"], Encoding.UTF8) + } }, cancellationToken); } } @@ -312,14 +318,14 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke if (request.TTLSeconds < DateTimeOffset.UtcNow.ToUnixTimeSeconds()) { HandleProxyRequestError(null, null, request.Timestamp, request.FullURL, HttpStatusCode.Gone, "Request has expired: " + DateTimeOffset.UtcNow.ToLocalTime()); - HttpResponseMessage invalidTTLResponseMessage = new(HttpStatusCode.InternalServerError); - - invalidTTLResponseMessage.Content = new StringContent("Invalid TTL format: " + request.Headers["S7PTTL"], Encoding.UTF8); return await WriteProxyDataAsync(new() { StatusCode = HttpStatusCode.Gone, - ResponseMessage = invalidTTLResponseMessage + ResponseMessage = new(HttpStatusCode.InternalServerError) + { + Content = new StringContent("Invalid TTL format: " + request.Headers["S7PTTL"], Encoding.UTF8) + } }, cancellationToken); } @@ -341,123 +347,121 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke await Task.Yield(); try { - request.Headers.Set("Host", host.host); - var urlWithPath = new UriBuilder(host.url) { Path = request.Path }.Uri.AbsoluteUri; - request.FullURL = System.Net.WebUtility.UrlDecode(urlWithPath); + request.Headers.Set("Host", host.Host); + var urlWithPath = new UriBuilder(host.Url) { Path = request.Path }.Uri.AbsoluteUri; + request.FullURL = WebUtility.UrlDecode(urlWithPath); + + using ByteArrayContent bodyContent = new(bodyBytes); + using HttpRequestMessage proxyRequest = new(new(request.Method), request.FullURL); + proxyRequest.Content = bodyContent; + CopyHeaders(request.Headers, proxyRequest, true); - using (var bodyContent = new ByteArrayContent(bodyBytes)) - using (var proxyRequest = new HttpRequestMessage(new HttpMethod(request.Method), request.FullURL)) + if (bodyBytes.Length > 0) { - proxyRequest.Content = bodyContent; - CopyHeaders(request.Headers, proxyRequest, true); + proxyRequest.Content.Headers.ContentLength = bodyBytes.Length; - if (bodyBytes.Length > 0) + // Preserve the content type if it was provided + var contentType = request.Context?.Request.ContentType; // Default to application/octet-stream if not specified + var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType ?? "application/octet-stream"); + // Preserve the encoding type if it was provided + if (contentType != null && contentType.Contains("charset")) { - proxyRequest.Content.Headers.ContentLength = bodyBytes.Length; - - // Preserve the content type if it was provided - string contentType = request.Context?.Request.ContentType ?? "application/octet-stream"; // Default to application/octet-stream if not specified - var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); - - // Preserve the encoding type if it was provided - if (request.Context?.Request.ContentType != null && request.Context.Request.ContentType.Contains("charset")) + var charset = contentType.Split(';').LastOrDefault(s => s.Trim().StartsWith("charset")); + if (charset != null) { - var charset = request.Context.Request.ContentType.Split(';').LastOrDefault(s => s.Trim().StartsWith("charset")); - if (charset != null) - { - mediaTypeHeaderValue.CharSet = charset.Split('=').Last().Trim(); - } - } - else - { - mediaTypeHeaderValue.CharSet = "utf-8"; + mediaTypeHeaderValue.CharSet = charset.Split('=').Last().Trim(); } - - proxyRequest.Content.Headers.ContentType = mediaTypeHeaderValue; } - - proxyRequest.Headers.ConnectionClose = true; - - if (request.Debug) + else { - _logger.LogDebug($"> {request.Method} {request.FullURL} {bodyBytes.Length} bytes"); - LogHeaders(proxyRequest.Headers, ">"); - LogHeaders(proxyRequest.Content.Headers, " >"); + mediaTypeHeaderValue.CharSet = "utf-8"; } - var ProxyStartDate = DateTime.UtcNow; - using (var proxyResponse = await _options.Client.SendAsync(proxyRequest, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) - { - var responseDate = DateTime.UtcNow; - lastStatusCode = proxyResponse.StatusCode; + proxyRequest.Content.Headers.ContentType = mediaTypeHeaderValue; + } - if (((int)proxyResponse.StatusCode > 300 && (int)proxyResponse.StatusCode < 400) || (int)proxyResponse.StatusCode >= 500) - { - if (request.Debug) - { - try - { - //Why do this? - ProxyData temp_pr = new() - { - ResponseDate = responseDate, - StatusCode = proxyResponse.StatusCode, - FullURL = request.FullURL, - }; - bodyBytes = []; - _logger.LogDebug($"Got: {temp_pr.StatusCode} {temp_pr.FullURL} {temp_pr.ContentHeaders["Content-Length"]} bytes"); - _logger.LogDebug($"< {temp_pr?.Body}"); - } - catch (Exception e) - { - _logger.LogError($"Error reading from backend host: {e.Message}"); - } - - _logger.LogInformation($"Trying next host: Response: {proxyResponse.StatusCode}"); - } - continue; - } + proxyRequest.Headers.ConnectionClose = true; - host.AddPxLatency((responseDate - ProxyStartDate).TotalMilliseconds); + if (request.Debug) + { + _logger.LogDebug($"> {request.Method} {request.FullURL} {bodyBytes.Length} bytes"); + LogHeaders(proxyRequest.Headers, ">"); + LogHeaders(proxyRequest.Content.Headers, " >"); + } - ProxyData pr = new() - { - ResponseDate = responseDate, - StatusCode = proxyResponse.StatusCode, - ResponseMessage = proxyResponse, - FullURL = request.FullURL, - CalculatedHostLatency = host.calculatedAverageLatency, - BackendHostname = host.host - }; - - if ((int)proxyResponse.StatusCode == 429 && proxyResponse.Headers.TryGetValues("S7PREQUEUE", out var values)) - { - // Requeue the request if the response is a 429 and the S7PREQUEUE header is set - var s7PrequeueValue = values.FirstOrDefault(); + var proxyStartDate = DateTime.UtcNow; + if(_options.Client == null) throw new NullReferenceException("Client cannot be null."); + using var proxyResponse = await _options.Client.SendAsync( + proxyRequest, + HttpCompletionOption.ResponseHeadersRead, + _cancellationToken).ConfigureAwait(false); + var responseDate = DateTime.UtcNow; + lastStatusCode = proxyResponse.StatusCode; - if (s7PrequeueValue != null && string.Equals(s7PrequeueValue, "true", StringComparison.OrdinalIgnoreCase)) + if (((int)proxyResponse.StatusCode > 300 && (int)proxyResponse.StatusCode < 400) || (int)proxyResponse.StatusCode >= 500) + { + if (request.Debug) + { + try + { + //Why do this? + ProxyData temp_pr = new() { - throw new S7PRequeueException("Requeue request", pr); - } + ResponseDate = responseDate, + StatusCode = proxyResponse.StatusCode, + FullURL = request.FullURL, + }; + bodyBytes = []; + _logger.LogDebug($"Got: {temp_pr.StatusCode} {temp_pr.FullURL} {temp_pr.ContentHeaders["Content-Length"]} bytes"); + _logger.LogDebug($"< {temp_pr?.Body}"); } - else + catch (Exception e) { - // request was successful, so we can disable the skip - request.SkipDispose = false; + _logger.LogError($"Error reading from backend host: {e.Message}"); } - var context = request.Context - ?? throw new NullReferenceException("Request context cannot be null."); - // TODO: Move to caller to handle writing errors? - await WriteResponseDataAsync(context, pr, cancellationToken); + _logger.LogInformation($"Trying next host: Response: {proxyResponse.StatusCode}"); + } + continue; + } - if (request.Debug) - { - _logger.LogDebug($"Got: {pr.StatusCode} {pr.FullURL}"); - } - return pr; + host.AddPxLatency((responseDate - proxyStartDate).TotalMilliseconds); + + ProxyData pr = new() + { + ResponseDate = responseDate, + StatusCode = proxyResponse.StatusCode, + ResponseMessage = proxyResponse, + FullURL = request.FullURL, + CalculatedHostLatency = host.CalculatedAverageLatency, + BackendHostname = host.Host + }; + + if ((int)proxyResponse.StatusCode == 429 && proxyResponse.Headers.TryGetValues("S7PREQUEUE", out var values)) + { + // Requeue the request if the response is a 429 and the S7PREQUEUE header is set + var s7PrequeueValue = values.FirstOrDefault(); + if (s7PrequeueValue != null && string.Equals(s7PrequeueValue, "true", StringComparison.OrdinalIgnoreCase)) + { + throw new S7PRequeueException("Requeue request", pr); } } + else + { + // request was successful, so we can disable the skip + request.SkipDispose = false; + } + + var context = request.Context + ?? throw new NullReferenceException("Request context cannot be null."); + // TODO: Move to caller to handle writing errors? + await WriteResponseDataAsync(context, pr, cancellationToken); + + if (request.Debug) + { + _logger.LogDebug($"Got: {pr.StatusCode} {pr.FullURL}"); + } + return pr; } catch (S7PRequeueException) { @@ -468,14 +472,14 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke catch (TaskCanceledException) { // 408 Request Timeout - lastStatusCode = HandleProxyRequestError(host, null, request.Timestamp, request.FullURL, HttpStatusCode.RequestTimeout, "Request to " + host.url + " timed out"); + lastStatusCode = HandleProxyRequestError(host, null, request.Timestamp, request.FullURL, HttpStatusCode.RequestTimeout, "Request to " + host.Url + " timed out"); // log the stack trace continue; } catch (OperationCanceledException e) { // 502 Bad Gateway - lastStatusCode = HandleProxyRequestError(host, e, request.Timestamp, request.FullURL, HttpStatusCode.BadGateway, "Request to " + host.url + " was cancelled"); + lastStatusCode = HandleProxyRequestError(host, e, request.Timestamp, request.FullURL, HttpStatusCode.BadGateway, "Request to " + host.Url + " was cancelled"); continue; } catch (HttpRequestException e) @@ -494,13 +498,13 @@ async Task WriteProxyDataAsync(ProxyData data, CancellationToken toke } - HttpResponseMessage responseMessage = new(HttpStatusCode.InternalServerError); - - responseMessage.Content = new StringContent("No active hosts were able to handle the request.", Encoding.UTF8); return await WriteProxyDataAsync(new() { StatusCode = HttpStatusCode.BadGateway, - ResponseMessage = responseMessage + ResponseMessage = new(HttpStatusCode.InternalServerError) + { + Content = new StringContent("No active hosts were able to handle the request.", Encoding.UTF8) + } }, cancellationToken); } @@ -511,11 +515,9 @@ private bool CalculateTTL(RequestData request) { if (request.Headers["S7PTTL"] != null) { - long longSeconds; string ttlString = request.Headers["S7PTTL"] ?? ""; - // TTL can be specified as +300 ( 300 seconds from now ) or as an absolute number of seconds - if (ttlString.StartsWith("+") && long.TryParse(ttlString.Substring(1), out longSeconds)) + if (ttlString.StartsWith('+') && long.TryParse(ttlString[1..], out long longSeconds)) { request.TTLSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + longSeconds; } @@ -542,12 +544,16 @@ private bool CalculateTTL(RequestData request) return true; } - private void CopyHeaders(NameValueCollection sourceHeaders, HttpRequestMessage? targetMessage, bool ignoreHeaders = false) + private static void CopyHeaders( + NameValueCollection sourceHeaders, + HttpRequestMessage? targetMessage, + bool ignoreHeaders = false) { - foreach (string? key in sourceHeaders.AllKeys) + foreach (var key in sourceHeaders.AllKeys) { if (key == null) continue; - if (!ignoreHeaders || (!key.StartsWith("S7P") && !key.StartsWith("X-MS-CLIENT", StringComparison.OrdinalIgnoreCase) && !key.Equals("content-length", StringComparison.OrdinalIgnoreCase))) + if (!ignoreHeaders || (!key.StartsWith("S7P") && !key.StartsWith("X-MS-CLIENT", StringComparison.OrdinalIgnoreCase) + && !key.Equals("content-length", StringComparison.OrdinalIgnoreCase))) { targetMessage?.Headers.TryAddWithoutValidation(key, sourceHeaders[key]); } @@ -562,24 +568,32 @@ private void LogHeaders(IEnumerable>> h } } - private HttpStatusCode HandleProxyRequestError(BackendHost? host, Exception? e, DateTime requestDate, string url, HttpStatusCode statusCode, string? customMessage = null) + private HttpStatusCode HandleProxyRequestError( + BackendHost? host, + Exception? e, + DateTime requestDate, + string url, + HttpStatusCode statusCode, + string? customMessage = null) { // Common operations for all exceptions - if (_telemetryClient != null) { if (e != null) _telemetryClient.TrackException(e); - var telemetry = new EventTelemetry("ProxyRequest"); - telemetry.Properties.Add("URL", url); - telemetry.Properties.Add("RequestDate", requestDate.ToString("o")); - telemetry.Properties.Add("ResponseDate", DateTime.Now.ToString("o")); - telemetry.Properties.Add("StatusCode", statusCode.ToString()); - _telemetryClient.TrackEvent(telemetry); + _telemetryClient.TrackEvent(new("ProxyRequest") + { + Properties = + { + { "URL", url }, + { "RequestDate", requestDate.ToString("o") }, + { "ResponseDate", DateTime.Now.ToString("o") }, + { "StatusCode", statusCode.ToString() } + } + }); } - if (!string.IsNullOrEmpty(customMessage)) { _logger.LogError($"{e?.Message ?? customMessage}"); diff --git a/src/SimpleL7Proxy/Proxy/ProxyWorkerCollection.cs b/src/SimpleL7Proxy/Proxy/ProxyWorkerCollection.cs index 0d74a2e1..f16dea7d 100644 --- a/src/SimpleL7Proxy/Proxy/ProxyWorkerCollection.cs +++ b/src/SimpleL7Proxy/Proxy/ProxyWorkerCollection.cs @@ -25,10 +25,19 @@ public ProxyWorkerCollection( _tasks = []; for (int i = 0; i < backendOptions.Workers; i++) { - var pw = new ProxyWorker(cancellationToken, i, queue, backendOptions, backends, eventClient, telemetryClient, logger, proxyStreamWriter); - _workers.Add(pw); - _cancellationToken = cancellationToken; // FIXME + _workers.Add(new( + i, + queue, + backendOptions, + backends, + eventClient, + telemetryClient, + logger, + proxyStreamWriter, + cancellationToken)); } + + _cancellationToken = cancellationToken; // FIXME } public void StartWorkers() diff --git a/src/SimpleL7Proxy/ProxyData.cs b/src/SimpleL7Proxy/ProxyData.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/SimpleL7Proxy/Queue/BlockingPriorityQueue.cs b/src/SimpleL7Proxy/Queue/BlockingPriorityQueue.cs index 5302aa81..ea8c18db 100644 --- a/src/SimpleL7Proxy/Queue/BlockingPriorityQueue.cs +++ b/src/SimpleL7Proxy/Queue/BlockingPriorityQueue.cs @@ -2,33 +2,24 @@ namespace SimpleL7Proxy.Queue; -public class BlockingPriorityQueue : IBlockingPriorityQueue +public class BlockingPriorityQueue( + PriorityQueue baseQueue, + TaskSignaler taskSignaler, + BackendOptions backendOptions, + ILogger> logger) + : IBlockingPriorityQueue { - private readonly PriorityQueue _priorityQueue; - private readonly object _lock = new object(); - private readonly ManualResetEventSlim _enqueueEvent = new ManualResetEventSlim(false); - private TaskSignaler _taskSignaler; - private ILogger> _logger; + private readonly PriorityQueue _priorityQueue = baseQueue; + private readonly Lock _lock = new(); + private readonly ManualResetEventSlim _enqueueEvent = new(false); + private readonly TaskSignaler _taskSignaler = taskSignaler; + private readonly ILogger> _logger = logger; - public BlockingPriorityQueue(PriorityQueue baseQueue, TaskSignaler taskSignaler, BackendOptions backendOptions, ILogger> logger) - { - MaxQueueLength = backendOptions.MaxQueueLength; - _priorityQueue = baseQueue; - _taskSignaler = taskSignaler; - _logger = logger; - } - - public int MaxQueueLength { get; private set; } + public int MaxQueueLength { get; private set; } = backendOptions.MaxQueueLength; public void StartSignaler(CancellationToken cancellationToken) - { - Task.Run(() => SignalWorker(cancellationToken), cancellationToken); - } - public void Stop() - { - // Shutdown - _taskSignaler.CancelAllTasks(); - } + => Task.Run(() => SignalWorker(cancellationToken), cancellationToken); + public void Stop() => _taskSignaler.CancelAllTasks(); // Thread-safe Count property public int Count @@ -50,10 +41,9 @@ public bool Enqueue(T item, int priority, DateTime timestamp) { return false; } - var queueItem = new PriorityQueueItem(item, priority, timestamp); + PriorityQueueItem queueItem = new(item, priority, timestamp); _priorityQueue.Enqueue(queueItem); _enqueueEvent.Set(); // Signal that an item has been added - } return true; @@ -63,17 +53,15 @@ public bool Requeue(T item, int priority, DateTime timestamp) { lock (_lock) { - var queueItem = new PriorityQueueItem(item, priority, timestamp); + PriorityQueueItem queueItem = new(item, priority, timestamp); _priorityQueue.Enqueue(queueItem); _enqueueEvent.Set(); // Signal that an item has been added - - //Monitor.Pulse(_lock); // Signal that an item has been added } return true; } - public async Task SignalWorker(CancellationToken cancellationToken) + public Task SignalWorker(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { @@ -93,7 +81,7 @@ public async Task SignalWorker(CancellationToken cancellationToken) } else { - Task.Delay(10).Wait(); // Wait for 10 ms for a Task Worker to be ready + Task.Delay(10, cancellationToken).Wait(cancellationToken); // Wait for 10 ms for a Task Worker to be ready } } } @@ -101,19 +89,18 @@ public async Task SignalWorker(CancellationToken cancellationToken) // Shutdown _taskSignaler.CancelAllTasks(); + return Task.CompletedTask; // TODO: refactor this to a proper task completion source implementation. } - public async Task Dequeue(CancellationToken cancellationToken, string id) + public async Task Dequeue(string id, CancellationToken cancellationToken) { try { - var parameter = await _taskSignaler.WaitForSignalAsync(id); - return parameter; + return await _taskSignaler.WaitForSignalAsync(id, cancellationToken); } catch (TaskCanceledException) { throw; } } - } diff --git a/src/SimpleL7Proxy/Queue/IBlockingPriorityQueue.cs b/src/SimpleL7Proxy/Queue/IBlockingPriorityQueue.cs index 9d1924a1..9fac070f 100644 --- a/src/SimpleL7Proxy/Queue/IBlockingPriorityQueue.cs +++ b/src/SimpleL7Proxy/Queue/IBlockingPriorityQueue.cs @@ -8,5 +8,5 @@ public interface IBlockingPriorityQueue int Count { get; } bool Enqueue(T item, int priority, DateTime timestamp); bool Requeue(T item, int priority, DateTime timestamp); - Task Dequeue(CancellationToken cancellationToken, string id); + Task Dequeue(string id, CancellationToken cancellationToken); } diff --git a/src/SimpleL7Proxy/Queue/PriorityQueue.cs b/src/SimpleL7Proxy/Queue/PriorityQueue.cs index 1b7244c8..efe19222 100644 --- a/src/SimpleL7Proxy/Queue/PriorityQueue.cs +++ b/src/SimpleL7Proxy/Queue/PriorityQueue.cs @@ -3,7 +3,7 @@ namespace SimpleL7Proxy.Queue; public class PriorityQueue { private readonly List> _items = []; - private static readonly PriorityQueueItemComparer Comparer = new PriorityQueueItemComparer(); + private static readonly PriorityQueueItemComparer Comparer = new(); public int Count => _items.Count; @@ -16,9 +16,7 @@ public void Enqueue(PriorityQueueItem queueItem) } public string GetItemsAsCommaSeparatedString() - { - return string.Join(", ", _items.Select(i => $"{i.Priority} ")); - } + => string.Join(", ", _items.Select(i => $"{i.Priority} ")); public T Dequeue() { @@ -31,29 +29,3 @@ public T Dequeue() return item.Item; } } - -public class PriorityQueueItemComparer : IComparer> -{ - public int Compare(PriorityQueueItem? x, PriorityQueueItem? y) - { - if (x == null) - { - return y == null ? 0 : -1; // If x is null and y is not, x is considered smaller - } - if (y == null) - { - return 1; // If y is null and x is not, x is considered larger - } - - int priorityComparison = x.Priority.CompareTo(y.Priority); - if (priorityComparison == 0) - { - // If priorities are equal, sort by timestamp (older items are "bigger") - //return y.Timestamp.CompareTo(x.Timestamp); - return x.Timestamp.CompareTo(y.Timestamp); - } - return priorityComparison; - } -} - - diff --git a/src/SimpleL7Proxy/Queue/PriorityQueueItem.cs b/src/SimpleL7Proxy/Queue/PriorityQueueItem.cs index deed6824..9769c210 100644 --- a/src/SimpleL7Proxy/Queue/PriorityQueueItem.cs +++ b/src/SimpleL7Proxy/Queue/PriorityQueueItem.cs @@ -1,15 +1,8 @@ namespace SimpleL7Proxy.Queue; -public class PriorityQueueItem +public class PriorityQueueItem(T item, int priority, DateTime timestamp) { - public T Item { get; } - public int Priority { get; } - public DateTime Timestamp { get; } - - public PriorityQueueItem(T item, int priority, DateTime timestamp) - { - Item = item; - Priority = priority; - Timestamp = timestamp; - } + public T Item { get; } = item; + public int Priority { get; } = priority; + public DateTime Timestamp { get; } = timestamp; } diff --git a/src/SimpleL7Proxy/Queue/PriorityQueueItemComparer.cs b/src/SimpleL7Proxy/Queue/PriorityQueueItemComparer.cs new file mode 100644 index 00000000..935ab75c --- /dev/null +++ b/src/SimpleL7Proxy/Queue/PriorityQueueItemComparer.cs @@ -0,0 +1,23 @@ +namespace SimpleL7Proxy.Queue; + +public class PriorityQueueItemComparer : IComparer> +{ + public int Compare(PriorityQueueItem? x, PriorityQueueItem? y) + { + if (x == null) + { + return y == null ? 0 : -1; // If x is null and y is not, x is considered smaller + } + if (y == null) + { + return 1; // If y is null and x is not, x is considered larger + } + + int priorityComparison = x.Priority.CompareTo(y.Priority); + return priorityComparison == 0 + ? x.Timestamp.CompareTo(y.Timestamp) + : priorityComparison; + } +} + + diff --git a/src/SimpleL7Proxy/Queue/QueueServiceCollectionExtensions.cs b/src/SimpleL7Proxy/Queue/QueueServiceCollectionExtensions.cs deleted file mode 100644 index 21bfd493..00000000 --- a/src/SimpleL7Proxy/Queue/QueueServiceCollectionExtensions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace SimpleL7Proxy.Queue -{ - internal class QueueServiceCollectionExtensions - { - } -} diff --git a/src/SimpleL7Proxy/Queue/TaskSignaler.cs b/src/SimpleL7Proxy/Queue/TaskSignaler.cs index 7960f9f8..ded8e071 100644 --- a/src/SimpleL7Proxy/Queue/TaskSignaler.cs +++ b/src/SimpleL7Proxy/Queue/TaskSignaler.cs @@ -4,12 +4,13 @@ namespace SimpleL7Proxy.Queue; public class TaskSignaler { - private readonly ConcurrentDictionary> _taskCompletionSources = new ConcurrentDictionary>(); - private readonly Random _random = new Random(); + private readonly ConcurrentDictionary> _taskCompletionSources = new(); + private readonly Random _random = new(); - public Task WaitForSignalAsync(string taskId) + public Task WaitForSignalAsync(string taskId, CancellationToken cancellationToken) { - var tcs = new TaskCompletionSource(); + //TODO: Implement cancellation token + TaskCompletionSource tcs = new(); _taskCompletionSources[taskId] = tcs; return tcs.Task; } @@ -47,8 +48,5 @@ public void CancelAllTasks() } } - public bool HasWaitingTasks() - { - return !_taskCompletionSources.IsEmpty; - } + public bool HasWaitingTasks() => !_taskCompletionSources.IsEmpty; } diff --git a/src/SimpleL7Proxy/RequestData.cs b/src/SimpleL7Proxy/RequestData.cs index d3ea9ee1..f9c4ec68 100644 --- a/src/SimpleL7Proxy/RequestData.cs +++ b/src/SimpleL7Proxy/RequestData.cs @@ -66,7 +66,6 @@ public async Task CacheBodyAsync() { // Implement IDisposable public void Dispose() { - if (SkipDispose) { return; @@ -74,7 +73,6 @@ public void Dispose() Dispose(true); GC.SuppressFinalize(this); - } protected virtual void Dispose(bool disposing) @@ -114,8 +112,6 @@ public async ValueTask DisposeAsync() protected virtual async ValueTask DisposeAsyncCore() { - - if (SkipDispose) { return; @@ -156,4 +152,4 @@ protected virtual async ValueTask DisposeAsyncCore() { Dispose(false); } -} \ No newline at end of file +} diff --git a/src/SimpleL7Proxy/S7PRequeueException.cs b/src/SimpleL7Proxy/S7PRequeueException.cs index 66ec9d5b..a644bc53 100644 --- a/src/SimpleL7Proxy/S7PRequeueException.cs +++ b/src/SimpleL7Proxy/S7PRequeueException.cs @@ -1,21 +1,20 @@ -using SimpleL7Proxy.Proxy; +namespace SimpleL7Proxy; +using Proxy; + // This class represents the request received from the upstream client. -public class S7PRequeueException : Exception, IDisposable +public class S7PRequeueException(string message, ProxyData pd) + : Exception(message), IDisposable { + public ProxyData Pr { get; set; } = pd; - public ProxyData pr { get; set; } - public S7PRequeueException(string message, ProxyData pd) : base(message) - { - pr = pd; - } - public void Dispose() + void IDisposable.Dispose() { - // Dispose of unmanaged resources here + // TODO: Dispose of unmanaged resources here } public ValueTask DisposeAsync() { - Dispose(); + ((IDisposable)this).Dispose(); return ValueTask.CompletedTask; } } diff --git a/src/SimpleL7Proxy/server.cs b/src/SimpleL7Proxy/server.cs index b26c9a1c..1c6c745e 100644 --- a/src/SimpleL7Proxy/server.cs +++ b/src/SimpleL7Proxy/server.cs @@ -4,19 +4,20 @@ using SimpleL7Proxy.Events; using SimpleL7Proxy.Queue; using Microsoft.Extensions.Logging; +namespace SimpleL7Proxy; // This class represents a server that listens for HTTP requests and processes them. // It uses a priority queue to manage incoming requests and supports telemetry for monitoring. // If the incoming request has the S7PPriorityKey header, it will be assigned a priority based the S7PPriority header. public class Server : IServer { - private BackendOptions? _options; + private readonly BackendOptions? _options; private readonly TelemetryClient? _telemetryClient; // Add this line - private HttpListener httpListener; + private readonly HttpListener _httpListener; - private Backends _backends; + private readonly Backends _backends; private CancellationToken _cancellationToken; - private IBlockingPriorityQueue _requestsQueue; + private readonly IBlockingPriorityQueue _requestsQueue; private readonly IEventClient? _eventHubClient; private readonly ILogger _logger; @@ -41,25 +42,22 @@ public Server( var _listeningUrl = $"http://+:{_options.Port}/"; - httpListener = new HttpListener(); - httpListener.Prefixes.Add(_listeningUrl); + _httpListener = new HttpListener(); + _httpListener.Prefixes.Add(_listeningUrl); var timeoutTime = TimeSpan.FromMilliseconds(_options.Timeout).ToString(@"hh\:mm\:ss\.fff"); _logger.LogInformation($"Server configuration: Port: {_options.Port} Timeout: {timeoutTime} Workers: {_options.Workers}"); } - public IBlockingPriorityQueue Queue() - { - return _requestsQueue; - } + public IBlockingPriorityQueue Queue() => _requestsQueue; - // Method to start the server and begin processing requests. - public void Start(CancellationToken cancellationToken) + // Method to start the server and begin processing requests. + public void Start(CancellationToken cancellationToken) { try { _cancellationToken = cancellationToken; - httpListener.Start(); + _httpListener.Start(); _logger.LogInformation($"Listening on {_options?.Port}"); // Additional setup or async start operations can be performed here @@ -95,28 +93,23 @@ public async Task Run() try { // Use the CancellationToken to asynchronously wait for an HTTP request. - var getContextTask = httpListener.GetContextAsync(); - using (var delayCts = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken)) - { - var delayTask = Task.Delay(Timeout.Infinite, delayCts.Token); - - var completedTask = await Task.WhenAny(getContextTask, delayTask).ConfigureAwait(false); + var getContextTask = _httpListener.GetContextAsync(); + using var delayCts = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken); + var delayTask = Task.Delay(Timeout.Infinite, delayCts.Token); + var completedTask = await Task.WhenAny(getContextTask, delayTask).ConfigureAwait(false); - // control to allow other tasks to run .. doesn't make sense here - // await Task.Yield(); - - // Cancel the delay task immedietly if the getContextTask completes first - if (completedTask == getContextTask) - { + // Cancel the delay task immedietly if the getContextTask completes first + if (completedTask == getContextTask) + { var mid = ""; try { - mid = _options.IDStr + counter++.ToString(); + mid = _options.IDStr + counter++.ToString(); } catch (OverflowException) { - mid = _options.IDStr + "0"; - counter = 1; + mid = _options.IDStr + "0"; + counter = 1; } delayCts.Cancel(); @@ -125,11 +118,11 @@ public async Task Run() var priorityKey = rd.Headers.Get("S7PPriorityKey"); if (!string.IsNullOrEmpty(priorityKey) && _options.PriorityKeys.Contains(priorityKey)) //lookup the priority { - var index = _options.PriorityKeys.IndexOf(priorityKey); - if (index >= 0) - { - priority = _options.PriorityValues[index]; - } + var index = _options.PriorityKeys.IndexOf(priorityKey); + if (index >= 0) + { + priority = _options.PriorityValues[index]; + } } rd.Priority = priority; rd.EnqueueTime = DateTime.UtcNow; @@ -137,92 +130,86 @@ public async Task Run() var return429 = false; var retrymsg = ""; - var ed = new Dictionary(); + Dictionary ed = []; // Check circuit breaker status and enqueue the request if (_backends.CheckFailedStatus()) { - return429 = true; + return429 = true; - ed["Type"] = "S7P-CircuitBreaker"; - ed["Message"] = "Circuit breaker on - 429"; - ed["QueueLength"] = _requestsQueue.Count.ToString(); - ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); - retrymsg = "Too many failures in last 10 seconds"; + ed["Type"] = "S7P-CircuitBreaker"; + ed["Message"] = "Circuit breaker on - 429"; + ed["QueueLength"] = _requestsQueue.Count.ToString(); + ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); + retrymsg = "Too many failures in last 10 seconds"; - _logger.LogError($"Circuit breaker on => 429: Queue Length: {_requestsQueue.Count}, Active Hosts: {_backends.ActiveHostCount()}"); + _logger.LogError($"Circuit breaker on => 429: Queue Length: {_requestsQueue.Count}, Active Hosts: {_backends.ActiveHostCount()}"); } else if (_requestsQueue.Count >= _options.MaxQueueLength) { - return429 = true; + return429 = true; - ed["Type"] = "S7P-QueueFull"; - ed["Message"] = "Queue is full"; - ed["QueueLength"] = _requestsQueue.Count.ToString(); - ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); - retrymsg = "Queue is full"; + ed["Type"] = "S7P-QueueFull"; + ed["Message"] = "Queue is full"; + ed["QueueLength"] = _requestsQueue.Count.ToString(); + ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); + retrymsg = "Queue is full"; - _logger.LogError($"Queue is full => 429: Queue Length: {_requestsQueue.Count}, Active Hosts: {_backends.ActiveHostCount()}"); + _logger.LogError($"Queue is full => 429: Queue Length: {_requestsQueue.Count}, Active Hosts: {_backends.ActiveHostCount()}"); } else if (_backends.ActiveHostCount() == 0) { - return429 = true; + return429 = true; - ed["Type"] = "S7P-NoActiveHosts"; - ed["Message"] = "No active hosts"; - ed["QueueLength"] = _requestsQueue.Count.ToString(); - ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); - retrymsg = "No active hosts"; + ed["Type"] = "S7P-NoActiveHosts"; + ed["Message"] = "No active hosts"; + ed["QueueLength"] = _requestsQueue.Count.ToString(); + ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); + retrymsg = "No active hosts"; - _logger.LogError($"No active hosts => 429: Queue Length: {_requestsQueue.Count}, Active Hosts: {_backends.ActiveHostCount()}", ed); + _logger.LogError($"No active hosts => 429: Queue Length: {_requestsQueue.Count}, Active Hosts: {_backends.ActiveHostCount()}", ed); } - // Enqueue the request - else if (!_requestsQueue.Enqueue(rd, priority, rd.EnqueueTime)) { - return429 = true; + return429 = true; - ed["Type"] = "S7P-EnqueueFailed"; - ed["Message"] = "Failed to enqueue request"; - ed["QueueLength"] = _requestsQueue.Count.ToString(); - ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); - retrymsg = "Failed to enqueue request"; + ed["Type"] = "S7P-EnqueueFailed"; + ed["Message"] = "Failed to enqueue request"; + ed["QueueLength"] = _requestsQueue.Count.ToString(); + ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); + retrymsg = "Failed to enqueue request"; - _logger.LogError($"Failed to enqueue request => 429: Queue Length: {_requestsQueue.Count}, Active Hosts: {_backends.ActiveHostCount()}", ed); + _logger.LogError($"Failed to enqueue request => 429: Queue Length: {_requestsQueue.Count}, Active Hosts: {_backends.ActiveHostCount()}", ed); } if (return429) { - - if (rd.Context is not null) - { - // send a 429 response to client in the number of milliseconds specified in Retry-After header - rd.Context.Response.StatusCode = 429; - rd.Context.Response.Headers["Retry-After"] = (_backends.ActiveHostCount() == 0) ? _options.PollInterval.ToString() : "500"; - - using (var writer = new System.IO.StreamWriter(rd.Context.Response.OutputStream)) + if (rd.Context is not null) { - await writer.WriteAsync(retrymsg); + // send a 429 response to client in the number of milliseconds specified in Retry-After header + rd.Context.Response.StatusCode = 429; + rd.Context.Response.Headers["Retry-After"] = (_backends.ActiveHostCount() == 0) ? _options.PollInterval.ToString() : "500"; + + using StreamWriter writer = new(rd.Context.Response.OutputStream); + await writer.WriteAsync(retrymsg); + rd.Context.Response.Close(); + _logger.LogError($"Pri: {priority} Stat: 429 Path: {rd.Path}"); } - rd.Context.Response.Close(); - _logger.LogError($"Pri: {priority} Stat: 429 Path: {rd.Path}"); - } } else { - ed["Type"] = "S7P-Enqueue"; - ed["Message"] = "Enqueued request"; - ed["QueueLength"] = _requestsQueue.Count.ToString(); - ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); - ed["Priority"] = priority.ToString(); + ed["Type"] = "S7P-Enqueue"; + ed["Message"] = "Enqueued request"; + ed["QueueLength"] = _requestsQueue.Count.ToString(); + ed["ActiveHosts"] = _backends.ActiveHostCount().ToString(); + ed["Priority"] = priority.ToString(); - _logger.LogInformation($"Enqueued request. Pri: {priority} Queue Length: {_requestsQueue.Count} Status: {_backends.CheckFailedStatus()} Active Hosts: {_backends.ActiveHostCount()}", ed); + _logger.LogInformation($"Enqueued request. Pri: {priority} Queue Length: {_requestsQueue.Count} Status: {_backends.CheckFailedStatus()} Active Hosts: {_backends.ActiveHostCount()}", ed); } - } - else - { + } + else + { _cancellationToken.ThrowIfCancellationRequested(); // This will throw if the token is cancelled while waiting for a request. - } } } catch (IOException ioEx) @@ -244,5 +231,4 @@ public async Task Run() _requestsQueue.Stop(); _logger.LogInformation("Listener task stopped."); } - -} \ No newline at end of file +} diff --git a/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs b/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs index 51e94070..ba78370a 100644 --- a/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs +++ b/test/ProxyWorkerTests/ProxyStreamWriterTestFixture.cs @@ -12,7 +12,7 @@ public async Task StreamingYieldsPerChunkTest() { // Arrange ProxyStreamWriter concern = new(); - var listenerResponse = new FakeHttpListenerResponse(); + FakeHttpListenerResponse listenerResponse = new(); const string proxyBody = "Hello, World!"; ProxyData proxyData = new() { From 3d8e199dc76a5fd3974dd82b0b2cf658dc429ad0 Mon Sep 17 00:00:00 2001 From: Tyler Kendrick <145080887+Tyler-R-Kendrick@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:22:59 +0000 Subject: [PATCH 10/10] fixed merge issues. --- SimpleL7Proxy.Test/SimpleL7Proxy.Test.csproj | 2 +- SimpleL7Proxy.sln | 98 ++-- src-rx/Program.cs | 11 - src-rx/SimpleL7Proxy2.csproj | 28 -- src/SimpleL7Proxy/BackendHost.cs | 29 +- src/SimpleL7Proxy/Backends.cs | 4 +- src/SimpleL7Proxy/Program.cs | 485 +++++++++---------- 7 files changed, 305 insertions(+), 352 deletions(-) delete mode 100644 src-rx/Program.cs delete mode 100644 src-rx/SimpleL7Proxy2.csproj diff --git a/SimpleL7Proxy.Test/SimpleL7Proxy.Test.csproj b/SimpleL7Proxy.Test/SimpleL7Proxy.Test.csproj index c028ee3e..f53d174c 100644 --- a/SimpleL7Proxy.Test/SimpleL7Proxy.Test.csproj +++ b/SimpleL7Proxy.Test/SimpleL7Proxy.Test.csproj @@ -1,4 +1,4 @@ - + net9.0 diff --git a/SimpleL7Proxy.sln b/SimpleL7Proxy.sln index 73b8d0b5..8c100101 100644 --- a/SimpleL7Proxy.sln +++ b/SimpleL7Proxy.sln @@ -3,29 +3,29 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestClient", "TestClient\TestClient.csproj", "{98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestClient", "TestClient\TestClient.csproj", "{02F9FFC2-D1EE-469F-B869-80FEF83BF78E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleL7Proxy2", "src-rx\SimpleL7Proxy2.csproj", "{90879664-7F50-411F-B1EA-B805918E43B8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleL7Proxy.Test", "SimpleL7Proxy.Test\SimpleL7Proxy.Test.csproj", "{16114946-D4F2-4604-A732-D5AFFEF295A1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2F43777E-BC06-4A84-9D37-76E9FE34E6BE}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{806C4881-406B-4504-BCC9-781F89BFCCB9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleL7Proxy", "src\SimpleL7Proxy\SimpleL7Proxy.csproj", "{30819770-EC5D-4D6F-8C98-A6780E4633A1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleL7Proxy", "src\SimpleL7Proxy\SimpleL7Proxy.csproj", "{1BEAB9EB-9991-41ED-99B5-F7A8D374491A}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A9F0DA11-B3E4-4060-9A76-10B37D3B09CA}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7FB4896E-B4A7-4030-A112-06B8E8A701B7}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "test\ProxyWorkerTests\Tests.csproj", "{8ED7BB66-0076-43B0-832C-600C2F62E66D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "test\ProxyWorkerTests\Tests.csproj", "{F562EE95-23FC-48A2-B5CD-21438BFE8926}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nullserver", "nullserver", "{3DE5977E-1C8E-461E-9AD2-DD9E57ABADD1}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nullserver", "nullserver", "{8911A389-8D4E-4268-90B7-9AC12C59861E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nullserver", "test\nullserver\nullserver\nullserver.csproj", "{56F72749-1C09-49EE-90E2-A11D571AE1AA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nullserver", "test\nullserver\nullserver\nullserver.csproj", "{9F502DB0-81A5-4AAB-B915-AAABAA55B0A3}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "generator", "generator", "{5C8DAEFB-1752-4182-988E-AAB97ABE1F28}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "generator", "generator", "{5315AE06-D9C2-456F-BE0C-4B6996540CC9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "generator_one", "test\generator\generator_one\generator_one.csproj", "{B1136BDF-743B-4581-9F6A-EDED51F4D00F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "generator_one", "test\generator\generator_one\generator_one.csproj", "{F045A36C-FFC3-4732-B898-1FF6ED71B4AE}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "identity-test", "identity-test", "{3460927E-7A06-4E4D-A5ED-1F6A0D4E2D52}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "identity-test", "identity-test", "{B7220CAA-AAAA-4E8E-BECE-6420B7002D4A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test-identity", "test\identity-test\dotnet\test-identity.csproj", "{0594D29A-88EE-4D53-AC9F-A2980438999F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "test-identity", "test\identity-test\dotnet\test-identity.csproj", "{0811BDC2-EB4B-4457-840B-6C53FD8C80B3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,49 +33,49 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98EBFEC4-9C8B-4FA8-AF7C-3F9BF88A699B}.Release|Any CPU.Build.0 = Release|Any CPU - {90879664-7F50-411F-B1EA-B805918E43B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {90879664-7F50-411F-B1EA-B805918E43B8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {90879664-7F50-411F-B1EA-B805918E43B8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {90879664-7F50-411F-B1EA-B805918E43B8}.Release|Any CPU.Build.0 = Release|Any CPU - {30819770-EC5D-4D6F-8C98-A6780E4633A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {30819770-EC5D-4D6F-8C98-A6780E4633A1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {30819770-EC5D-4D6F-8C98-A6780E4633A1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {30819770-EC5D-4D6F-8C98-A6780E4633A1}.Release|Any CPU.Build.0 = Release|Any CPU - {8ED7BB66-0076-43B0-832C-600C2F62E66D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8ED7BB66-0076-43B0-832C-600C2F62E66D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8ED7BB66-0076-43B0-832C-600C2F62E66D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8ED7BB66-0076-43B0-832C-600C2F62E66D}.Release|Any CPU.Build.0 = Release|Any CPU - {56F72749-1C09-49EE-90E2-A11D571AE1AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56F72749-1C09-49EE-90E2-A11D571AE1AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56F72749-1C09-49EE-90E2-A11D571AE1AA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56F72749-1C09-49EE-90E2-A11D571AE1AA}.Release|Any CPU.Build.0 = Release|Any CPU - {B1136BDF-743B-4581-9F6A-EDED51F4D00F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1136BDF-743B-4581-9F6A-EDED51F4D00F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1136BDF-743B-4581-9F6A-EDED51F4D00F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1136BDF-743B-4581-9F6A-EDED51F4D00F}.Release|Any CPU.Build.0 = Release|Any CPU - {0594D29A-88EE-4D53-AC9F-A2980438999F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0594D29A-88EE-4D53-AC9F-A2980438999F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0594D29A-88EE-4D53-AC9F-A2980438999F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0594D29A-88EE-4D53-AC9F-A2980438999F}.Release|Any CPU.Build.0 = Release|Any CPU + {02F9FFC2-D1EE-469F-B869-80FEF83BF78E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02F9FFC2-D1EE-469F-B869-80FEF83BF78E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02F9FFC2-D1EE-469F-B869-80FEF83BF78E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02F9FFC2-D1EE-469F-B869-80FEF83BF78E}.Release|Any CPU.Build.0 = Release|Any CPU + {16114946-D4F2-4604-A732-D5AFFEF295A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16114946-D4F2-4604-A732-D5AFFEF295A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16114946-D4F2-4604-A732-D5AFFEF295A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16114946-D4F2-4604-A732-D5AFFEF295A1}.Release|Any CPU.Build.0 = Release|Any CPU + {1BEAB9EB-9991-41ED-99B5-F7A8D374491A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BEAB9EB-9991-41ED-99B5-F7A8D374491A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BEAB9EB-9991-41ED-99B5-F7A8D374491A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BEAB9EB-9991-41ED-99B5-F7A8D374491A}.Release|Any CPU.Build.0 = Release|Any CPU + {F562EE95-23FC-48A2-B5CD-21438BFE8926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F562EE95-23FC-48A2-B5CD-21438BFE8926}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F562EE95-23FC-48A2-B5CD-21438BFE8926}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F562EE95-23FC-48A2-B5CD-21438BFE8926}.Release|Any CPU.Build.0 = Release|Any CPU + {9F502DB0-81A5-4AAB-B915-AAABAA55B0A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F502DB0-81A5-4AAB-B915-AAABAA55B0A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F502DB0-81A5-4AAB-B915-AAABAA55B0A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F502DB0-81A5-4AAB-B915-AAABAA55B0A3}.Release|Any CPU.Build.0 = Release|Any CPU + {F045A36C-FFC3-4732-B898-1FF6ED71B4AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F045A36C-FFC3-4732-B898-1FF6ED71B4AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F045A36C-FFC3-4732-B898-1FF6ED71B4AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F045A36C-FFC3-4732-B898-1FF6ED71B4AE}.Release|Any CPU.Build.0 = Release|Any CPU + {0811BDC2-EB4B-4457-840B-6C53FD8C80B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0811BDC2-EB4B-4457-840B-6C53FD8C80B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0811BDC2-EB4B-4457-840B-6C53FD8C80B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0811BDC2-EB4B-4457-840B-6C53FD8C80B3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {30819770-EC5D-4D6F-8C98-A6780E4633A1} = {2F43777E-BC06-4A84-9D37-76E9FE34E6BE} - {8ED7BB66-0076-43B0-832C-600C2F62E66D} = {A9F0DA11-B3E4-4060-9A76-10B37D3B09CA} - {3DE5977E-1C8E-461E-9AD2-DD9E57ABADD1} = {A9F0DA11-B3E4-4060-9A76-10B37D3B09CA} - {56F72749-1C09-49EE-90E2-A11D571AE1AA} = {3DE5977E-1C8E-461E-9AD2-DD9E57ABADD1} - {5C8DAEFB-1752-4182-988E-AAB97ABE1F28} = {A9F0DA11-B3E4-4060-9A76-10B37D3B09CA} - {B1136BDF-743B-4581-9F6A-EDED51F4D00F} = {5C8DAEFB-1752-4182-988E-AAB97ABE1F28} - {3460927E-7A06-4E4D-A5ED-1F6A0D4E2D52} = {A9F0DA11-B3E4-4060-9A76-10B37D3B09CA} - {0594D29A-88EE-4D53-AC9F-A2980438999F} = {3460927E-7A06-4E4D-A5ED-1F6A0D4E2D52} + {1BEAB9EB-9991-41ED-99B5-F7A8D374491A} = {806C4881-406B-4504-BCC9-781F89BFCCB9} + {F562EE95-23FC-48A2-B5CD-21438BFE8926} = {7FB4896E-B4A7-4030-A112-06B8E8A701B7} + {8911A389-8D4E-4268-90B7-9AC12C59861E} = {7FB4896E-B4A7-4030-A112-06B8E8A701B7} + {9F502DB0-81A5-4AAB-B915-AAABAA55B0A3} = {8911A389-8D4E-4268-90B7-9AC12C59861E} + {5315AE06-D9C2-456F-BE0C-4B6996540CC9} = {7FB4896E-B4A7-4030-A112-06B8E8A701B7} + {F045A36C-FFC3-4732-B898-1FF6ED71B4AE} = {5315AE06-D9C2-456F-BE0C-4B6996540CC9} + {B7220CAA-AAAA-4E8E-BECE-6420B7002D4A} = {7FB4896E-B4A7-4030-A112-06B8E8A701B7} + {0811BDC2-EB4B-4457-840B-6C53FD8C80B3} = {B7220CAA-AAAA-4E8E-BECE-6420B7002D4A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {4FFFA080-AA7B-44E5-BF2D-BA66D6396B12} + SolutionGuid = {66E0495D-8686-4347-B5AF-6D58FBD667A7} EndGlobalSection EndGlobal diff --git a/src-rx/Program.cs b/src-rx/Program.cs deleted file mode 100644 index 559118ca..00000000 --- a/src-rx/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - -var builder = WebApplication.CreateBuilder(args); -builder.Services.AddSingleton(); -var host = builder.Build(); -await host.RunAsync(); - -public class Worker -{ -} \ No newline at end of file diff --git a/src-rx/SimpleL7Proxy2.csproj b/src-rx/SimpleL7Proxy2.csproj deleted file mode 100644 index 06021b51..00000000 --- a/src-rx/SimpleL7Proxy2.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - Exe - net9.0 - enable - enable - - - - - - - - - - - - - - - - - - - - - diff --git a/src/SimpleL7Proxy/BackendHost.cs b/src/SimpleL7Proxy/BackendHost.cs index 80619fa6..f54c22c5 100644 --- a/src/SimpleL7Proxy/BackendHost.cs +++ b/src/SimpleL7Proxy/BackendHost.cs @@ -1,6 +1,7 @@ - using Microsoft.Extensions.Logging; +namespace SimpleL7Proxy; + public class BackendHost { public string Host { get; set; } @@ -9,23 +10,21 @@ public class BackendHost public string Protocol { get; set; } public string ProbePath { get; set; } - private string? _url = null; - private string? _probeurl = null; - public string Url => _url ??= new UriBuilder(Protocol, IpAddr ?? Host, Port).Uri.AbsoluteUri; + public string Url => new UriBuilder(Protocol, IpAddr ?? Host, Port).Uri.AbsoluteUri; - public string ProbeUrl => _probeurl ??= System.Net.WebUtility.UrlDecode($"{Url}/{ProbePath}"); + public string ProbeUrl => System.Net.WebUtility.UrlDecode(Path.Combine(Url, ProbePath)); private const int MaxData = 50; private readonly Queue latencies = new(); private readonly Queue callSuccess = new(); - public double CalculatedAverageLatency { get; set; } + public double CalculatedAverageLatency { get; set; } private Queue PxLatency = new(); private int errors = 0; private readonly Lock lockObj = new(); private readonly ILogger _logger; - public BackendHost(string hostname, string? probepath, string? ipaddress, ILogger logger) + public BackendHost(string hostname, string? probepath, ILogger logger) { // If host does not have a protocol, add one if (!hostname.StartsWith("http://") && !hostname.StartsWith("https://")) @@ -44,21 +43,15 @@ public BackendHost(string hostname, string? probepath, string? ipaddress, ILogge Protocol = uri.Scheme; Port = uri.Port; Host = uri.Host; - - ProbePath = probepath ?? "echo/resource?param1=sample"; + ProbePath = probepath?.TrimStart('/') ?? "echo/resource?param1=sample"; _logger = logger; - if (ProbePath.StartsWith('/')) - { - ProbePath = ProbePath[1..]; - } - _logger.LogInformation($"Adding backend host: {Host} probe path: {ProbePath}"); - } + } public override string ToString() => $"{Protocol}://{Host}:{Port}"; public void AddPxLatency(double latency) { - lock(lockObj) + lock (lockObj) { PxLatency.Enqueue(latency); } @@ -66,7 +59,7 @@ public void AddPxLatency(double latency) public void AddError() { - lock(lockObj) + lock (lockObj) { errors++; } @@ -143,4 +136,4 @@ public double SuccessRate() // Otherwise, return the success rate return (double)callSuccess.Count(x => x) / callSuccess.Count; } -} +} \ No newline at end of file diff --git a/src/SimpleL7Proxy/Backends.cs b/src/SimpleL7Proxy/Backends.cs index 076d6e96..2c9d59e6 100644 --- a/src/SimpleL7Proxy/Backends.cs +++ b/src/SimpleL7Proxy/Backends.cs @@ -237,7 +237,7 @@ private async Task GetHostStatus(BackendHost host, HttpClient client) if (_debug) _logger.LogDebug($"Checking host {host.Url + host.ProbePath}"); - HttpRequestMessage request = new(HttpMethod.Get, host.ProbePath); + HttpRequestMessage request = new(HttpMethod.Get, host.ProbeUrl); if (_options.UseOAuth) { request.Headers.Authorization = new("Bearer", OAuth2Token()); @@ -410,7 +410,7 @@ public void GetToken() { public static string FormatMilliseconds(double milliseconds) { - TimeSpan timeSpan = TimeSpan.FromMilliseconds(milliseconds); + var timeSpan = TimeSpan.FromMilliseconds(milliseconds); return string.Format("{0:D2}:{1:D2}:{2:D2} {3:D3} milliseconds", timeSpan.Hours, timeSpan.Minutes, diff --git a/src/SimpleL7Proxy/Program.cs b/src/SimpleL7Proxy/Program.cs index 6964ad84..297c9fff 100644 --- a/src/SimpleL7Proxy/Program.cs +++ b/src/SimpleL7Proxy/Program.cs @@ -4,13 +4,15 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using SimpleL7Proxy; using SimpleL7Proxy.Events; using SimpleL7Proxy.Proxy; using SimpleL7Proxy.Queue; using System.Net; using System.Text; +namespace SimpleL7Proxy; + + // This code serves as the entry point for the .NET application. // It sets up the necessary configurations, including logging and telemetry. // The Main method is asynchronous and initializes the application, @@ -24,30 +26,30 @@ public class Program { - private static readonly TelemetryClient? _telemetryClient; - private static ILogger? _logger; - - static readonly CancellationTokenSource cancellationTokenSource = new(); - public string OAuthAudience { get; set; } = ""; + private static readonly TelemetryClient? _telemetryClient; + private static ILogger? _logger; - public static async Task Main(string[] args) - { - var cancellationToken = cancellationTokenSource.Token; - ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); - var backendOptions = LoadBackendOptions(loggerFactory); + static readonly CancellationTokenSource cancellationTokenSource = new(); + public string OAuthAudience { get; set; } = ""; - Console.CancelKeyPress += (sender, e) => - { - Console.WriteLine("Shutdown signal received. Initiating shutdown..."); - e.Cancel = true; // Prevent the process from terminating immediately. - cancellationTokenSource.Cancel(); // Signal the application to shut down. - }; - - var hostBuilder = Host.CreateDefaultBuilder(args).ConfigureServices((hostContext, services) => - { - // Register the configured BackendOptions instance with DI - services.Configure(options => - { + public static async Task Main(string[] args) + { + var cancellationToken = cancellationTokenSource.Token; + ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + var backendOptions = LoadBackendOptions(loggerFactory); + + Console.CancelKeyPress += (sender, e) => + { + Console.WriteLine("Shutdown signal received. Initiating shutdown..."); + e.Cancel = true; // Prevent the process from terminating immediately. + cancellationTokenSource.Cancel(); // Signal the application to shut down. + }; + + var hostBuilder = Host.CreateDefaultBuilder(args).ConfigureServices((hostContext, services) => + { + // Register the configured BackendOptions instance with DI + services.Configure(options => + { options.Client = backendOptions.Client; options.DefaultPriority = backendOptions.DefaultPriority; options.DefaultTTLSecs = backendOptions.DefaultTTLSecs; @@ -66,275 +68,272 @@ public static async Task Main(string[] args) options.Timeout = backendOptions.Timeout; options.UseOAuth = backendOptions.UseOAuth; options.Workers = backendOptions.Workers; - }); + }); - var aiConnectionString = Environment.GetEnvironmentVariable("APPINSIGHTS_CONNECTIONSTRING") ?? ""; + var aiConnectionString = Environment.GetEnvironmentVariable("APPINSIGHTS_CONNECTIONSTRING") ?? ""; - services.AddLogging( - loggingBuilder => { - if (!string.IsNullOrEmpty(aiConnectionString)) { - loggingBuilder.AddApplicationInsights( + services.AddLogging( + loggingBuilder => + { + if (!string.IsNullOrEmpty(aiConnectionString)) + { + loggingBuilder.AddApplicationInsights( configureTelemetryConfiguration: (config) => config.ConnectionString = aiConnectionString, - configureApplicationInsightsLoggerOptions: (options) => { } + configureApplicationInsightsLoggerOptions: (options) => { } ); - } - //loggingBuilder.AddFilter("Category", LogLevel.Information) - loggingBuilder.AddConsole(); - }); - - if (aiConnectionString != null) - { - services.AddApplicationInsightsTelemetryWorkerService((ApplicationInsightsServiceOptions options) => options.ConnectionString = aiConnectionString); - services.AddApplicationInsightsTelemetry(options => - { - options.EnableRequestTrackingTelemetryModule = true; + } + loggingBuilder.AddConsole(); }); - Console.WriteLine("AppInsights initialized"); - } - // Add the proxy event client - var eventHubConnectionString = Environment.GetEnvironmentVariable("EVENTHUB_CONNECTIONSTRING"); - var eventHubName = Environment.GetEnvironmentVariable("EVENTHUB_NAME"); - services.AddProxyEventClient(eventHubConnectionString, eventHubName, aiConnectionString); + if (aiConnectionString != null) + { + services.AddApplicationInsightsTelemetryWorkerService( + (options) => options.ConnectionString = aiConnectionString); + services.AddApplicationInsightsTelemetry(options => + { + options.EnableRequestTrackingTelemetryModule = true; + }); + Console.WriteLine("AppInsights initialized"); + } - //services.AddHttpLogging(o => { }); - services.AddSingleton(backendOptions); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + // Add the proxy event client + var eventHubConnectionString = Environment.GetEnvironmentVariable("EVENTHUB_CONNECTIONSTRING"); + var eventHubName = Environment.GetEnvironmentVariable("EVENTHUB_NAME"); + services.AddProxyEventClient(eventHubConnectionString, eventHubName, aiConnectionString); + services.AddSingleton(backendOptions); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton>(); + services.AddSingleton>(); + services.AddSingleton, BlockingPriorityQueue>(); + services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton>(); - services.AddSingleton>(); - services.AddSingleton, BlockingPriorityQueue>(); + services.AddSingleton(typeof(CancellationToken), cancellationToken); + }); - services.AddSingleton(); + var frameworkHost = hostBuilder.Build(); + var serviceProvider = frameworkHost.Services; - services.AddSingleton(typeof(CancellationToken), cancellationToken); - }); + var backends = serviceProvider.GetRequiredService(); - var frameworkHost = hostBuilder.Build(); - var serviceProvider = frameworkHost.Services; + _logger = serviceProvider.GetRequiredService>(); - var backends = serviceProvider.GetRequiredService(); - - _logger = serviceProvider.GetRequiredService>(); - - backends.Start(cancellationToken); + backends.Start(cancellationToken); - var server = serviceProvider.GetRequiredService(); - var eventClient = serviceProvider.GetRequiredService(); + var server = serviceProvider.GetRequiredService(); + var eventClient = serviceProvider.GetRequiredService(); - var pwCollection = serviceProvider.GetRequiredService(); - try - { - await backends.WaitForStartup(20); // wait for up to 20 seconds for startup - server.Start(cancellationToken); + var pwCollection = serviceProvider.GetRequiredService(); + try + { + await backends.WaitForStartup(20); // wait for up to 20 seconds for startup + server.Start(cancellationToken); - pwCollection.StartWorkers(); - } - catch (Exception e) - { - _logger.LogError(e, "Exiting: {Message}", e.Message); + pwCollection.StartWorkers(); + } + catch (Exception e) + { + _logger.LogError(e, "Exiting: {Message}", e.Message); Environment.Exit(1); - } + } - try - { - await server.Run(); - _logger.LogInformation("Waiting for tasks to complete for maximum 10 seconds"); - var timeoutTask = Task.Delay(10000); // 10 seconds timeout - var allTasks = pwCollection.GetAllProxyWorkerTasks(); - var completedTask = await Task.WhenAny(allTasks, timeoutTask); - } - catch (Exception e) - { - _telemetryClient?.TrackException(e); - _logger.LogError($"Error: {e.Message}"); - _logger.LogError($"Stack Trace: {e.StackTrace}"); - } + try + { + await server.Run(); + _logger.LogInformation("Waiting for tasks to complete for maximum 10 seconds"); + var timeoutTask = Task.Delay(10000); // 10 seconds timeout + var allTasks = pwCollection.GetAllProxyWorkerTasks(); + var completedTask = await Task.WhenAny(allTasks, timeoutTask); + } + catch (Exception e) + { + _telemetryClient?.TrackException(e); + _logger.LogError($"Error: {e.Message}"); + _logger.LogError($"Stack Trace: {e.StackTrace}"); + } - try - { - // Pass the CancellationToken to RunAsync - await frameworkHost.RunAsync(cancellationToken); - } - catch (OperationCanceledException) - { - // Don't use logger here, as our logger may have been disposed - Console.WriteLine("Operation was canceled."); - } - catch (Exception e) - { - // Handle other exceptions that might occur - Console.WriteLine($"An unexpected error occurred: {e.Message}"); + try + { + // Pass the CancellationToken to RunAsync + await frameworkHost.RunAsync(cancellationToken); + } + catch (OperationCanceledException) + { + // Don't use logger here, as our logger may have been disposed + Console.WriteLine("Operation was canceled."); + } + catch (Exception e) + { + // Handle other exceptions that might occur + Console.WriteLine($"An unexpected error occurred: {e.Message}"); + } } - } - // Rreads an environment variable and returns its value as an integer. - // If the environment variable is not set, it returns the provided default value. - private static int ReadEnvironmentVariableOrDefault(string variableName, int defaultValue) - { - if (!int.TryParse(Environment.GetEnvironmentVariable(variableName), out var value)) + // Rreads an environment variable and returns its value as an integer. + // If the environment variable is not set, it returns the provided default value. + private static int ReadEnvironmentVariableOrDefault(string variableName, int defaultValue) { - Console.WriteLine($"Using default: {variableName}: {defaultValue}"); - return defaultValue; + if (!int.TryParse(Environment.GetEnvironmentVariable(variableName), out var value)) + { + Console.WriteLine($"Using default: {variableName}: {defaultValue}"); + return defaultValue; + } + return value; } - return value; - } - - // Rreads an environment variable and returns its value as a string. - // If the environment variable is not set, it returns the provided default value. - private static string ReadEnvironmentVariableOrDefault(string variableName, string defaultValue) - { - var envValue = Environment.GetEnvironmentVariable(variableName); - if (string.IsNullOrEmpty(envValue)) + + // Rreads an environment variable and returns its value as a string. + // If the environment variable is not set, it returns the provided default value. + private static string ReadEnvironmentVariableOrDefault(string variableName, string defaultValue) { - _logger?.LogInformation($"Using default: {variableName}: {defaultValue}"); - return defaultValue; + var envValue = Environment.GetEnvironmentVariable(variableName); + if (string.IsNullOrEmpty(envValue)) + { + _logger?.LogInformation($"Using default: {variableName}: {defaultValue}"); + return defaultValue; + } + return envValue.Trim(); } - return envValue.Trim(); - } // Converts a comma-separated string to a list of strings. private static List ToListOfString(string s) => s.Split(',').Select(p => p.Trim()).ToList(); // Converts a comma-separated string to a list of integers. - private static List ToListOfInt(string s) - { - // parse each value in the list - List ints = []; - foreach (var item in s.Split(',')) + private static List ToListOfInt(string s) { - if (int.TryParse(item.Trim(), out int value)) - { - ints.Add(value); - } - else - { - _logger?.LogError($"Could not parse {item} as an integer, defaulting to 5"); - ints.Add(5); - } + // parse each value in the list + List ints = []; + foreach (var item in s.Split(',')) + { + if (int.TryParse(item.Trim(), out int value)) + { + ints.Add(value); + } + else + { + _logger?.LogError($"Could not parse {item} as an integer, defaulting to 5"); + ints.Add(5); + } + } + + return s.Split(',').Select(p => int.Parse(p.Trim())).ToList(); } - return s.Split(',').Select(p => int.Parse(p.Trim())).ToList(); - } - - // Loads backend options from environment variables or uses default values if the variables are not set. - // It also configures the DNS refresh timeout and sets up an HttpClient instance. - // If the IgnoreSSLCert environment variable is set to true, it configures the HttpClient to ignore SSL certificate errors. - // If the AppendHostsFile environment variable is set to true, it appends the IP addresses and hostnames to the /etc/hosts file. - private static BackendOptions LoadBackendOptions(ILoggerFactory loggerFactory) - { - var logger = loggerFactory.CreateLogger(); - // Read and set the DNS refresh timeout from environment variables or use the default value - var DNSTimeout = ReadEnvironmentVariableOrDefault("DnsRefreshTimeout", 120000); - ServicePointManager.DnsRefreshTimeout = DNSTimeout; - - // Initialize HttpClient and configure it to ignore SSL certificate errors if specified in environment variables. - HttpClient _client = new(); - if (Environment.GetEnvironmentVariable("IgnoreSSLCert")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true) + // Loads backend options from environment variables or uses default values if the variables are not set. + // It also configures the DNS refresh timeout and sets up an HttpClient instance. + // If the IgnoreSSLCert environment variable is set to true, it configures the HttpClient to ignore SSL certificate errors. + // If the AppendHostsFile environment variable is set to true, it appends the IP addresses and hostnames to the /etc/hosts file. + private static BackendOptions LoadBackendOptions(ILoggerFactory loggerFactory) { - _client = new(new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true - }); - } + var logger = loggerFactory.CreateLogger(); + // Read and set the DNS refresh timeout from environment variables or use the default value + var DNSTimeout = ReadEnvironmentVariableOrDefault("DnsRefreshTimeout", 120000); + ServicePointManager.DnsRefreshTimeout = DNSTimeout; + + // Initialize HttpClient and configure it to ignore SSL certificate errors if specified in environment variables. + HttpClient _client = new(); + if (Environment.GetEnvironmentVariable("IgnoreSSLCert")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true) + { + _client = new(new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true + }); + } - string replicaID = ReadEnvironmentVariableOrDefault("CONTAINER_APP_REPLICA_NAME", "01"); + string replicaID = ReadEnvironmentVariableOrDefault("CONTAINER_APP_REPLICA_NAME", "01"); #if DEBUG - // Load appsettings.json only in Debug mode - var configuration = new ConfigurationBuilder() - .AddJsonFile("appsettings.json", optional: true) - .AddEnvironmentVariables() - .Build(); + // Load appsettings.json only in Debug mode + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: true) + .AddEnvironmentVariables() + .Build(); - foreach (var setting in configuration.GetSection("Settings").GetChildren()) - { - Environment.SetEnvironmentVariable(setting.Key, setting.Value); - } + foreach (var setting in configuration.GetSection("Settings").GetChildren()) + { + Environment.SetEnvironmentVariable(setting.Key, setting.Value); + } #endif - // Create and return a BackendOptions object populated with values from environment variables or default values. - var backendOptions = new BackendOptions - { - Client = _client, - DefaultPriority = ReadEnvironmentVariableOrDefault("DefaultPriority", 2), - DefaultTTLSecs = ReadEnvironmentVariableOrDefault("DefaultTTLSecs", 300), - HostName = ReadEnvironmentVariableOrDefault("Hostname", "Default"), - Hosts = [], - IDStr = ReadEnvironmentVariableOrDefault("RequestIDPrefix", "S7P") + "-" + replicaID + "-", - LogHeaders = ReadEnvironmentVariableOrDefault("LogHeaders", "").Split(',').Select(x => x.Trim()).ToList(), - MaxQueueLength = ReadEnvironmentVariableOrDefault("MaxQueueLength", 10), - OAuthAudience = ReadEnvironmentVariableOrDefault("OAuthAudience", ""), - Port = ReadEnvironmentVariableOrDefault("Port", 443), - PollInterval = ReadEnvironmentVariableOrDefault("PollInterval", 15000), - PollTimeout = ReadEnvironmentVariableOrDefault("PollTimeout", 3000), - PriorityKeys = ToListOfString(ReadEnvironmentVariableOrDefault("PriorityKeys", "12345,234")), - PriorityValues = ToListOfInt(ReadEnvironmentVariableOrDefault("PriorityValues", "1,3")), - SuccessRate = ReadEnvironmentVariableOrDefault("SuccessRate", 80), - Timeout = ReadEnvironmentVariableOrDefault("Timeout", 3000), - UseOAuth = ReadEnvironmentVariableOrDefault("UseOAuth", "false").Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true, - Workers = ReadEnvironmentVariableOrDefault("Workers", 10), - }; - - backendOptions.Client.Timeout = TimeSpan.FromMilliseconds(backendOptions.Timeout); - - int i = 1; - StringBuilder sb = new(); - while (true) - { + // Create and return a BackendOptions object populated with values from environment variables or default values. + var backendOptions = new BackendOptions + { + Client = _client, + DefaultPriority = ReadEnvironmentVariableOrDefault("DefaultPriority", 2), + DefaultTTLSecs = ReadEnvironmentVariableOrDefault("DefaultTTLSecs", 300), + HostName = ReadEnvironmentVariableOrDefault("Hostname", "Default"), + Hosts = [], + IDStr = ReadEnvironmentVariableOrDefault("RequestIDPrefix", "S7P") + "-" + replicaID + "-", + LogHeaders = ReadEnvironmentVariableOrDefault("LogHeaders", "").Split(',').Select(x => x.Trim()).ToList(), + MaxQueueLength = ReadEnvironmentVariableOrDefault("MaxQueueLength", 10), + OAuthAudience = ReadEnvironmentVariableOrDefault("OAuthAudience", ""), + Port = ReadEnvironmentVariableOrDefault("Port", 443), + PollInterval = ReadEnvironmentVariableOrDefault("PollInterval", 15000), + PollTimeout = ReadEnvironmentVariableOrDefault("PollTimeout", 3000), + PriorityKeys = ToListOfString(ReadEnvironmentVariableOrDefault("PriorityKeys", "12345,234")), + PriorityValues = ToListOfInt(ReadEnvironmentVariableOrDefault("PriorityValues", "1,3")), + SuccessRate = ReadEnvironmentVariableOrDefault("SuccessRate", 80), + Timeout = ReadEnvironmentVariableOrDefault("Timeout", 3000), + UseOAuth = ReadEnvironmentVariableOrDefault("UseOAuth", "false").Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true, + Workers = ReadEnvironmentVariableOrDefault("Workers", 10), + }; - var hostname = Environment.GetEnvironmentVariable($"Host{i}")?.Trim(); - if (string.IsNullOrEmpty(hostname)) break; + backendOptions.Client.Timeout = TimeSpan.FromMilliseconds(backendOptions.Timeout); - var probePath = Environment.GetEnvironmentVariable($"Probe_path{i}")?.Trim(); - var ip = Environment.GetEnvironmentVariable($"IP{i}")?.Trim(); + int i = 1; + StringBuilder sb = new(); + while (true) + { - try - { - _logger?.LogInformation($"Found host {hostname} with probe path {probePath} and IP {ip}"); + var hostname = Environment.GetEnvironmentVariable($"Host{i}")?.Trim(); + if (string.IsNullOrEmpty(hostname)) break; - BackendHost bh = new(hostname, probePath, ip, logger); - backendOptions.Hosts.Add(bh); + var probePath = Environment.GetEnvironmentVariable($"Probe_path{i}")?.Trim(); + var ip = Environment.GetEnvironmentVariable($"IP{i}")?.Trim(); - sb.AppendLine($"{ip} {bh.Host}"); + try + { + _logger?.LogInformation($"Found host {hostname} with probe path {probePath} and IP {ip}"); - } - catch (UriFormatException e) - { - _logger?.LogError($"Could not add Host{i} with {hostname} : {e.Message}"); - } + BackendHost bh = new(hostname, probePath, logger); + backendOptions.Hosts.Add(bh); - i++; - } + sb.AppendLine($"{ip} {bh.Host}"); + } + catch (UriFormatException e) + { + _logger?.LogError($"Could not add Host{i} with {hostname} : {e.Message}"); + } - if (Environment.GetEnvironmentVariable("APPENDHOSTSFILE")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true || - Environment.GetEnvironmentVariable("AppendHostsFile")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true) - { - _logger?.LogInformation($"Appending {sb.ToString()} to /etc/hosts"); + i++; + } + + if (Environment.GetEnvironmentVariable("APPENDHOSTSFILE")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true || + Environment.GetEnvironmentVariable("AppendHostsFile")?.Trim().Equals("true", StringComparison.OrdinalIgnoreCase) == true) + { + _logger?.LogInformation($"Appending {sb} to /etc/hosts"); using StreamWriter sw = File.AppendText("/etc/hosts"); sw.WriteLine(sb.ToString()); } - // confirm the number of priority keys and values match - if (backendOptions.PriorityKeys.Count != backendOptions.PriorityValues.Count) - { - Console.WriteLine("The number of PriorityKeys and PriorityValues do not match in length, defaulting all values to 5"); - backendOptions.PriorityValues = Enumerable.Repeat(5, backendOptions.PriorityKeys.Count).ToList(); - } + // confirm the number of priority keys and values match + if (backendOptions.PriorityKeys.Count != backendOptions.PriorityValues.Count) + { + Console.WriteLine("The number of PriorityKeys and PriorityValues do not match in length, defaulting all values to 5"); + backendOptions.PriorityValues = Enumerable.Repeat(5, backendOptions.PriorityKeys.Count).ToList(); + } - Console.WriteLine("======================================================================================="); - Console.WriteLine(" ##### # ####### "); - Console.WriteLine("# # # # # ##### # ###### # # # ##### ##### #### # # # #"); - Console.WriteLine("# # ## ## # # # # # # # # # # # # # # # #"); - Console.WriteLine(" ##### # # ## # # # # ##### # # # # # # # # ## #"); - Console.WriteLine(" # # # # ##### # # # # ##### ##### # # ## #"); - Console.WriteLine("# # # # # # # # # # # # # # # # # #"); - Console.WriteLine(" ##### # # # # ###### ###### ####### # # # # #### # # #"); - Console.WriteLine("======================================================================================="); - Console.WriteLine("Version: 2.0.0"); - - return backendOptions; - } -} \ No newline at end of file + Console.WriteLine("======================================================================================="); + Console.WriteLine(" ##### # ####### "); + Console.WriteLine("# # # # # ##### # ###### # # # ##### ##### #### # # # #"); + Console.WriteLine("# # ## ## # # # # # # # # # # # # # # # #"); + Console.WriteLine(" ##### # # ## # # # # ##### # # # # # # # # ## #"); + Console.WriteLine(" # # # # ##### # # # # ##### ##### # # ## #"); + Console.WriteLine("# # # # # # # # # # # # # # # # # #"); + Console.WriteLine(" ##### # # # # ###### ###### ####### # # # # #### # # #"); + Console.WriteLine("======================================================================================="); + Console.WriteLine("Version: 2.0.0"); + + return backendOptions; + } +}