deprecate(lmxproxy): move all LmxProxy code, tests, and docs to deprecated/
LmxProxy is no longer needed. Moved the entire lmxproxy/ workspace, DCL adapter files, and related docs to deprecated/. Removed LmxProxy registration from DataConnectionFactory, project reference from DCL, protocol option from UI, and cleaned up all requirement docs.
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ZB.MOM.WW.LmxProxy.Host.Configuration;
|
||||
|
||||
namespace ZB.MOM.WW.LmxProxy.Host.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP web server that serves status information for the LmxProxy service
|
||||
/// </summary>
|
||||
public class StatusWebServer : IDisposable
|
||||
{
|
||||
private static readonly ILogger Logger = Log.ForContext<StatusWebServer>();
|
||||
|
||||
private readonly WebServerConfiguration _configuration;
|
||||
private readonly StatusReportService _statusReportService;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private bool _disposed;
|
||||
private HttpListener? _httpListener;
|
||||
private Task? _listenerTask;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the StatusWebServer class
|
||||
/// </summary>
|
||||
/// <param name="configuration">Web server configuration</param>
|
||||
/// <param name="statusReportService">Service for collecting status information</param>
|
||||
public StatusWebServer(WebServerConfiguration configuration, StatusReportService statusReportService)
|
||||
{
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_statusReportService = statusReportService ?? throw new ArgumentNullException(nameof(statusReportService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the web server and releases resources
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
Stop();
|
||||
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_httpListener?.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the HTTP web server
|
||||
/// </summary>
|
||||
/// <returns>True if started successfully, false otherwise</returns>
|
||||
public bool Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_configuration.Enabled)
|
||||
{
|
||||
Logger.Information("Status web server is disabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger.Information("Starting status web server on port {Port}", _configuration.Port);
|
||||
|
||||
_httpListener = new HttpListener();
|
||||
|
||||
// Configure the URL prefix
|
||||
string prefix = _configuration.Prefix ?? $"http://+:{_configuration.Port}/";
|
||||
if (!prefix.EndsWith("/"))
|
||||
{
|
||||
prefix += "/";
|
||||
}
|
||||
|
||||
_httpListener.Prefixes.Add(prefix);
|
||||
_httpListener.Start();
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_listenerTask = Task.Run(() => HandleRequestsAsync(_cancellationTokenSource.Token));
|
||||
|
||||
Logger.Information("Status web server started successfully on {Prefix}", prefix);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Failed to start status web server");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the HTTP web server
|
||||
/// </summary>
|
||||
/// <returns>True if stopped successfully, false otherwise</returns>
|
||||
public bool Stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_configuration.Enabled || _httpListener == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Logger.Information("Stopping status web server");
|
||||
|
||||
_cancellationTokenSource?.Cancel();
|
||||
|
||||
if (_listenerTask != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_listenerTask.Wait(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning(ex, "Error waiting for listener task to complete");
|
||||
}
|
||||
}
|
||||
|
||||
_httpListener?.Stop();
|
||||
_httpListener?.Close();
|
||||
|
||||
Logger.Information("Status web server stopped successfully");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Error stopping status web server");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main request handling loop
|
||||
/// </summary>
|
||||
private async Task HandleRequestsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.Information("Status web server listener started");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested && _httpListener != null && _httpListener.IsListening)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpListenerContext? context = await _httpListener.GetContextAsync();
|
||||
|
||||
// Handle request asynchronously without waiting
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await HandleRequestAsync(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Error handling HTTP request from {RemoteEndPoint}",
|
||||
context.Request.RemoteEndPoint);
|
||||
}
|
||||
}, cancellationToken);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Expected when stopping the listener
|
||||
break;
|
||||
}
|
||||
catch (HttpListenerException ex) when (ex.ErrorCode == 995) // ERROR_OPERATION_ABORTED
|
||||
{
|
||||
// Expected when stopping the listener
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Error in request listener loop");
|
||||
|
||||
// Brief delay before continuing to avoid tight error loops
|
||||
try
|
||||
{
|
||||
await Task.Delay(1000, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Information("Status web server listener stopped");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles a single HTTP request
|
||||
/// </summary>
|
||||
private async Task HandleRequestAsync(HttpListenerContext context)
|
||||
{
|
||||
HttpListenerRequest? request = context.Request;
|
||||
HttpListenerResponse response = context.Response;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Debug("Handling {Method} request to {Url} from {RemoteEndPoint}",
|
||||
request.HttpMethod, request.Url?.AbsolutePath, request.RemoteEndPoint);
|
||||
|
||||
// Only allow GET requests
|
||||
if (request.HttpMethod != "GET")
|
||||
{
|
||||
response.StatusCode = 405; // Method Not Allowed
|
||||
response.StatusDescription = "Method Not Allowed";
|
||||
await WriteResponseAsync(response, "Only GET requests are supported", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
string path = request.Url?.AbsolutePath?.ToLowerInvariant() ?? "/";
|
||||
|
||||
switch (path)
|
||||
{
|
||||
case "/":
|
||||
await HandleStatusPageAsync(response);
|
||||
break;
|
||||
|
||||
case "/api/status":
|
||||
await HandleStatusApiAsync(response);
|
||||
break;
|
||||
|
||||
case "/api/health":
|
||||
await HandleHealthApiAsync(response);
|
||||
break;
|
||||
|
||||
default:
|
||||
response.StatusCode = 404; // Not Found
|
||||
response.StatusDescription = "Not Found";
|
||||
await WriteResponseAsync(response, "Resource not found", "text/plain");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, "Error handling HTTP request");
|
||||
|
||||
try
|
||||
{
|
||||
response.StatusCode = 500; // Internal Server Error
|
||||
response.StatusDescription = "Internal Server Error";
|
||||
await WriteResponseAsync(response, "Internal server error", "text/plain");
|
||||
}
|
||||
catch (Exception responseEx)
|
||||
{
|
||||
Logger.Error(responseEx, "Error writing error response");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
response.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning(ex, "Error closing HTTP response");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the main status page (HTML)
|
||||
/// </summary>
|
||||
private async Task HandleStatusPageAsync(HttpListenerResponse response)
|
||||
{
|
||||
string statusHtml = await _statusReportService.GenerateHtmlReportAsync();
|
||||
await WriteResponseAsync(response, statusHtml, "text/html; charset=utf-8");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the status API endpoint (JSON)
|
||||
/// </summary>
|
||||
private async Task HandleStatusApiAsync(HttpListenerResponse response)
|
||||
{
|
||||
string statusJson = await _statusReportService.GenerateJsonReportAsync();
|
||||
await WriteResponseAsync(response, statusJson, "application/json; charset=utf-8");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the health API endpoint (simple text)
|
||||
/// </summary>
|
||||
private async Task HandleHealthApiAsync(HttpListenerResponse response)
|
||||
{
|
||||
bool isHealthy = await _statusReportService.IsHealthyAsync();
|
||||
string healthText = isHealthy ? "OK" : "UNHEALTHY";
|
||||
response.StatusCode = isHealthy ? 200 : 503; // Service Unavailable if unhealthy
|
||||
await WriteResponseAsync(response, healthText, "text/plain");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a response to the HTTP context
|
||||
/// </summary>
|
||||
private static async Task WriteResponseAsync(HttpListenerResponse response, string content, string contentType)
|
||||
{
|
||||
response.ContentType = contentType;
|
||||
response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
response.Headers.Add("Pragma", "no-cache");
|
||||
response.Headers.Add("Expires", "0");
|
||||
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(content);
|
||||
response.ContentLength64 = buffer.Length;
|
||||
|
||||
using (Stream? output = response.OutputStream)
|
||||
{
|
||||
await output.WriteAsync(buffer, 0, buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user