using System.Net; namespace GmRelay.Bot.Infrastructure.Health; public sealed class BotHealthCheckHostedService : IHostedService { private readonly ILogger _logger; private readonly string _prefix; private HttpListener? _listener; private CancellationTokenSource? _cts; private Task? _listenerTask; public BotHealthCheckHostedService( ILogger logger, IConfiguration configuration) { _logger = logger; _prefix = configuration.GetValue("HealthCheck:Prefix", "http://+:8081/")!; } public Task StartAsync(CancellationToken cancellationToken) { _cts = new CancellationTokenSource(); _listener = new HttpListener(); _listener.Prefixes.Add(_prefix); _listener.Start(); _logger.LogInformation("Health check server started on {Prefix}", _prefix); _listenerTask = Task.Run(async () => await ListenAsync(_cts.Token), cancellationToken); return Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { _cts?.Cancel(); _listener?.Stop(); if (_listenerTask != null) { await Task.WhenAny(_listenerTask, Task.Delay(TimeSpan.FromSeconds(5), cancellationToken)); } _listener?.Close(); _logger.LogInformation("Health check server stopped"); } private async Task ListenAsync(CancellationToken cancellationToken) { while (_listener?.IsListening == true && !cancellationToken.IsCancellationRequested) { try { var context = await _listener.GetContextAsync(); _ = Task.Run(() => HandleRequestAsync(context), cancellationToken); } catch (HttpListenerException) when (cancellationToken.IsCancellationRequested) { break; } catch (ObjectDisposedException) { break; } catch (Exception ex) { _logger.LogError(ex, "Error in health check listener"); } } } private async Task HandleRequestAsync(HttpListenerContext context) { var response = context.Response; try { var request = context.Request; if (request.Url?.AbsolutePath == "/health") { response.StatusCode = (int)HttpStatusCode.OK; response.ContentType = "application/json"; var body = "{\"status\":\"healthy\"}"u8.ToArray(); await response.OutputStream.WriteAsync(body); } else { response.StatusCode = (int)HttpStatusCode.NotFound; } } catch (Exception ex) { _logger.LogError(ex, "Error handling health check request"); } finally { response.Close(); } } }