.NET WinForms-Anwendung (Core, Modules/CongressTrading, UI). Enthaelt .gitignore und settings.example.json als Konfigurationsvorlage. Echte settings.json mit Zugangsdaten ist bewusst ausgeschlossen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
3.6 KiB
C#
105 lines
3.6 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 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.
|
||
/// </summary>
|
||
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";
|
||
}
|
||
|
||
/// <summary>Setzt die WorkerEngine nach DI-Aufbau (zirkulare Abhängigkeit vermeiden).</summary>
|
||
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 { }
|
||
}
|
||
}
|