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:
Joseph Doherty
2026-04-08 15:56:23 -04:00
parent 8423915ba1
commit 9dccf8e72f
220 changed files with 25 additions and 132 deletions

View File

@@ -0,0 +1,215 @@
using System;
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.Status
{
/// <summary>
/// HTTP status server providing an HTML dashboard, JSON API, and health endpoint.
/// </summary>
public class StatusWebServer : IDisposable
{
private static readonly ILogger Logger = Log.ForContext<StatusWebServer>();
private readonly WebServerConfiguration _configuration;
private readonly StatusReportService _statusReportService;
private HttpListener? _httpListener;
private CancellationTokenSource? _cancellationTokenSource;
private Task? _listenerTask;
private bool _disposed;
public StatusWebServer(WebServerConfiguration configuration, StatusReportService statusReportService)
{
_configuration = configuration;
_statusReportService = statusReportService;
}
public bool Start()
{
if (!_configuration.Enabled)
{
Logger.Information("Status web server is disabled");
return true;
}
try
{
_httpListener = new HttpListener();
var 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 on {Prefix}", prefix);
return true;
}
catch (Exception ex)
{
Logger.Error(ex, "Failed to start status web server");
return false;
}
}
public bool Stop()
{
if (!_configuration.Enabled || _httpListener == null)
return true;
try
{
_cancellationTokenSource?.Cancel();
if (_listenerTask != null)
{
_listenerTask.Wait(TimeSpan.FromSeconds(5));
}
_httpListener.Stop();
_httpListener.Close();
Logger.Information("Status web server stopped");
return true;
}
catch (Exception ex)
{
Logger.Error(ex, "Error stopping status web server");
return false;
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
Stop();
_cancellationTokenSource?.Dispose();
if (_httpListener != null)
{
((IDisposable)_httpListener).Dispose();
}
}
private async Task HandleRequestsAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested && _httpListener != null && _httpListener.IsListening)
{
try
{
var context = await _httpListener.GetContextAsync();
_ = Task.Run(() => HandleRequestAsync(context));
}
catch (ObjectDisposedException)
{
// Expected during shutdown
break;
}
catch (HttpListenerException ex) when (ex.ErrorCode == 995)
{
// ERROR_OPERATION_ABORTED — expected during shutdown
break;
}
catch (Exception ex)
{
Logger.Error(ex, "Error accepting HTTP request");
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
}
}
}
private async Task HandleRequestAsync(HttpListenerContext context)
{
try
{
if (context.Request.HttpMethod != "GET")
{
context.Response.StatusCode = 405;
await WriteResponseAsync(context.Response, "Method Not Allowed", "text/plain");
return;
}
var path = context.Request.Url?.AbsolutePath?.ToLowerInvariant() ?? "/";
switch (path)
{
case "/":
await HandleStatusPageAsync(context.Response);
break;
case "/api/status":
await HandleStatusApiAsync(context.Response);
break;
case "/api/health":
await HandleHealthApiAsync(context.Response);
break;
default:
context.Response.StatusCode = 404;
await WriteResponseAsync(context.Response, "Not Found", "text/plain");
break;
}
}
catch (Exception ex)
{
Logger.Error(ex, "Error handling HTTP request");
try
{
context.Response.StatusCode = 500;
await WriteResponseAsync(context.Response, "Internal Server Error", "text/plain");
}
catch
{
// Ignore errors writing error response
}
}
}
private async Task HandleStatusPageAsync(HttpListenerResponse response)
{
var html = await _statusReportService.GenerateHtmlReportAsync();
await WriteResponseAsync(response, html, "text/html; charset=utf-8");
}
private async Task HandleStatusApiAsync(HttpListenerResponse response)
{
var json = await _statusReportService.GenerateJsonReportAsync();
await WriteResponseAsync(response, json, "application/json; charset=utf-8");
}
private async Task HandleHealthApiAsync(HttpListenerResponse response)
{
var isHealthy = await _statusReportService.IsHealthyAsync();
if (isHealthy)
{
response.StatusCode = 200;
await WriteResponseAsync(response, "OK", "text/plain");
}
else
{
response.StatusCode = 503;
await WriteResponseAsync(response, "UNHEALTHY", "text/plain");
}
}
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");
var buffer = Encoding.UTF8.GetBytes(content);
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
response.OutputStream.Close();
}
}
}