Files
IBKRTrader/Core/Workers/BuiltIn/WebApiService.cs
T
RichardandClaude Opus 4.8 ebeb035e92 Initial commit: IBKRTrader
.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>
2026-07-26 18:19:47 +02:00

105 lines
3.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { }
}
}