using System.Net; using System.Text; using System.Text.Json; using IBKRTrader.Core.Database; using IBKRTrader.Core.Logging; using IBKRTrader.Core.Settings; namespace IBKRTrader.Core.Workers.BuiltIn; /// /// WebApiService – permanenter REST-API-Service auf Basis von HttpListener. /// Basis-Routen: GET /api/status, GET /api/workers /// Wird später schrittweise um vollständige API-Endpunkte erweitert. /// public class WebApiService : WorkerBase { private readonly SettingsService _settings; private WorkerEngine? _engine; // wird nach DI-Aufbau gesetzt (kein readonly) private HttpListener? _listener; private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; public override string Name => "WebApiService"; public override string Module => "Core"; public override WorkerType Type => WorkerType.Service; protected override TimeSpan? Interval => null; public WebApiService(LoggingService logger, DatabaseService db, SettingsService settings) : base(logger, db) { _settings = settings; Info.Active = settings.Settings.Webserver.Enabled; Info.RunEvery = "Service"; } /// Setzt die WorkerEngine nach DI-Aufbau (zirkulare Abhängigkeit vermeiden). public void SetEngine(WorkerEngine engine) => _engine = engine; protected override async Task ExecuteAsync(CancellationToken ct) { var port = _settings.Settings.Webserver.Port + 1; // API auf Port+1 _listener = new HttpListener(); _listener.Prefixes.Add($"http://localhost:{port}/api/"); try { _listener.Start(); Logger.Info(Module, $"WebAPI gestartet auf http://localhost:{port}/api/"); Info.Info = $"http://localhost:{port}/api/"; while (!ct.IsCancellationRequested) { var contextTask = _listener.GetContextAsync(); var cancelTask = Task.Delay(Timeout.Infinite, ct); var completed = await Task.WhenAny(contextTask, cancelTask); if (completed == cancelTask) break; var ctx = await contextTask; _ = RouteAsync(ctx); } } finally { _listener.Stop(); Logger.Info(Module, "WebAPI gestoppt."); } } private async Task RouteAsync(HttpListenerContext ctx) { try { var path = ctx.Request.Url?.AbsolutePath.TrimEnd('/').ToLowerInvariant() ?? ""; object? result = path switch { "/api/status" => new { status = "ok", time = DateTime.UtcNow }, "/api/workers" => _engine?.WorkerInfos .Select(w => new { w.WorkerName, w.Module, w.Type, w.Info, w.Active }) ?? [], _ => null }; if (result == null) { ctx.Response.StatusCode = 404; ctx.Response.Close(); return; } var json = JsonSerializer.Serialize(result, JsonOpts); var bytes = Encoding.UTF8.GetBytes(json); ctx.Response.ContentType = "application/json"; ctx.Response.ContentLength64 = bytes.Length; ctx.Response.StatusCode = 200; await ctx.Response.OutputStream.WriteAsync(bytes); ctx.Response.Close(); } catch { } } }