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,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user